【C++ Primer Plus】学习条记 5【指针 上】

[复制链接]
发表于 2026-2-8 05:16:22 | 显示全部楼层 |阅读模式

媒介

终于进入最难的指针啦,加油加油

一、指针和自由存储空间

指针是一个变量,其存储的是值的地点,而不是值自己。在讨论指针之前,我们先看一看怎样找到通例变量的地点。只需对变量应用地点运算符(&),就可以得到它的位置:比方,假如home是一个变量,则&home是它的地点。
   指针与 C++根本原理
面向对象编程与传统的过程性编程的区别在于,OOP夸大的是在运行阶段(而不是编译阶段)举行决定。运行阶段指的是步调正在运行时,编译阶段指的是编译器将步调组合起来时。运行阶段决定就好比度假时,选择观光哪些景点取决于气候和当时的心情;而编译阶段决定更像不管在什么条件下,都对峙预先设定的日程安排。
运行阶段决定提供了机动性,可以根据当时的环境举行调解。比方,思量为数组分配内存的环境。传统的方法是声明一个数组。要在C++中声明数组,必须指定命组的长度。因此,数组长度在步调编译时就设定好了;这就是编译阶段决定。大概在80%的环境下,一个包罗20个元素的数组富足了,但步调偶尔须要处置惩罚200个元素。为了安全起见,使用了一个包罗200个元素的数组。如许,步调在大多数环境下都浪费了内存。OOP通过将如许的决定推迟到运行阶段举行,使步调更机动。在步调运行后,可以这次告诉它只须要 20个元素,而还可以下次告诉它须要 205个元素。为使用这种方法,语言必须允许在步调运行时创建数组。C++接纳的方法是,使用关键字new哀求精确数量标内存以及使用指针来跟踪新分配的内存的位置。
在运行阶段做决定并非 OOP 独有的,但使用 C++编写如许的代码比使用C语言简朴。
  将地点视为指定的量,而将值视为派生量。一种特殊范例的变量——指针用于存储值的地点。因此,指针名表现的是地点。* 运算符被称为间接值或排除引用运算符,将其应用于指针,可以得到该地点处存储的值(这和乘法使用的符号雷同;C+根据上下文来确定所指的是乘法还是排除引用)。比方,假设 manly 是一个指针,则 manly 表现的是一个地点,而*manly 表现存储在该地点处的值。*manly 与通例 int 变量等效。示例代码如下:
  1. // pointer.cpp--our first pointer variable
  2. #include <iostream>
  3. int main()
  4. {
  5.         using namespace std;
  6.         int updates =6;
  7.         int *p_updates;
  8.         p_updates = &updates;//assign address of int to pointer
  9.        
  10.         cout <<"Values:updates="<<updates;
  11.         cout << ", *p_updates="<<*p_updates << endl;
  12.         cout<<"Addresses:&updates="<<&updates;
  13.         cout<<",p_updates="<<p_updates << endl;
  14.        
  15.         *p_updates =*p_updates +1;
  16.         cout<<"Now updates="<< updates << endl;
  17.         return 0;
  18. }
复制代码

从中可知,int 变量 updates 和指针变量p_updates 只不外是同一枚硬币的两面。变量 updates 表现值,并使用&运算符来得到地点;而变量p_updates表现地点,并使用运算符来得到值。由于p_updates 指向 updates,因此p_updates 和 updates 完全等价。可以像使用 int 变量那样使用p_updates,以致可以将值赋给p_updates。如许做将修改指向的值,即 updates。

1. 声明和初始化指针

盘算机须要跟踪指针指向的值的范例。比方,char的地点与double的地点看上去没什么两样,但 char 和 double 使用的字节数是差别的,它们存储值时使用的内部格式也差别。因此,指针声明必须指定指针指向的数据的范例。
比方,前一个示例包罗如许的声明:
  1. int *p_updates;
复制代码
这表明,* p_updates 的范例为int。由于 * 运算符被用于指针,因此p_updates 变量自己必须是指针。我们说 p_updates 指向 int 范例,我们还说 p_updates 的范例是指向 int 的指针,或 int*。可以如许说,p_updates是指针(地点),而*p_updates是int,而不是指针。

趁便说一句, * 运算符两边的空格是可选的。
C语言使用这种格式int *ptr; ,这夸大 *ptr 是一个int范例的值。
C++使用这种格式 int* ptr;,这夸大的是 int * 是一种指向int 的指针范例。
在那边添加空格对于编译器来说没有任何区别,以致可以如许写int*ptr;
但要知道的是,下面的声明是创建一个指针(p1)和一个int变量(p2):
  1. int* p1,p2;
复制代码
对每个指针变量名,都须要使用一个*。
注意:在C++中,int*是一种复合范例,是指向int的指针。
可以用同样的句法来声明指向其他范例的指针。
可以在声明语句中初始化指针。在这种环境下,被初始化的是指针,而不是它指向的值。也就是说,下面的语句将pt(而不是*pt)的值设置为&higgens:
  1. int higgens =5;
  2. int *pt= &higgens;
复制代码
2. 指针的伤害

在C++中创建指针时,盘算机将分配用来存储地点的内存,但不会分配用来存储指针所指向的数据的内存。为数据提供空间是一个独立的步调,忽略这一步无疑是自找贫困,如下所示:
  1. long *fellow;//create a pointer-to-long
  2. *fellow = 223323;//place a value in never-never land
复制代码
fellow 确实是一个指针,但它指向那边呢?上述代码没有将地点赋给fellow。那么 223323 将被放在那边呢?我们不知道。由于fellow 没有被初始化,它大概有任何值。不管值是什么,步调都将它表明为存储223323的地点。假如fellow的值可巧为1200,盘算机将把数据放在地点1200上,纵然这可巧是步调代码的地点。fellow指向的地方很大概并不是所要存储223323的地方。这种错误大概会导致一些 bug。
肯定要在对指针应用排除引用运算符(*)之前,将指针初始化为一个确定的、得当的地点。
3. 指针和数字

指针不是整型,固然盘算机通常把地点看成整数来处置惩罚。从概念上看,指针与整数是截然差别的范例。整数是可以实验加、减、除等运算的数字,而指针形貌的是位置,将两个地点相乘没有任何意义。从可以对整数和指针实验的使用上看,它们也是相互差别的。因此,不能简朴地将整数赋给指针:
  1. int * pt;
  2. pt = 0xB8000000;//type mismatch
复制代码
左边是指向int的指针,因此可以把它赋给地点,但右边是一个整数,这条语句并没有告诉步调,这个数字就是一个地点。要将数字值作为地点来使用,应通过逼迫范例转换将数字转换为得当的地点范例:
  1. int *pt;
  2. pt =(int*)0xB8000000;//types now match
复制代码
如许,赋值语句的两边都是整数的地点,赋值有用。注意,pt是int值的地点并不意味着 pt自己的范例是int。比方,在有些平台中,int范例是个2字节值,而地点是个4字节值。
4. 使用 new 来分配内存

对指针的工作方式有肯定相识后,来看看它怎样实现在步调运行时分配内存。前面我们都将指针初始化为变量的地点,变量是在编译时分配的著名称的内存,而指针只是为可以通过名称直接访问的内存提供了一个别名。指针真正的用武之地在于,在运行阶段分配未定名的内存以存储值。在这种环境下,只能通过指针来访问内存。在C语言中,可以用库函数malloc()来分配内存;在C++中仍旧可以如许做,但 C++另有更好的方法——new 运算符,下面是示例:
  1. int *pn = new int;
复制代码
new int 告诉步调,须要得当存储 int 的内存。new 运算符根据范例来确定须要多少字节的内存。然后,它找到如许的内存,并返回其地点。接下来,将地点赋给pn,pn是被声明为指向int的指针。现在,pn是地点,而*pn是存储在那边的值。将这种方法与将变量的地点赋给指针举行比力:
  1. int higgens;
  2. int *pt= &higgens;
复制代码
在这两种环境(pn 和 pt)下,都是将一个int 变量的地点赋给了指针。在第二种环境下,可以通过名称 higgens 来访问该 int,在第一种环境下,则只能通过该指针举行访问。这引出了一个标题:pn指向的内存没著名称,怎样称谓它呢? 我们说 pn指向一个数据对象,这里的“对象”不是“面向对象编程”中的对象,而是一样“东西”。术语“数据对象”比“变量”更通用,它指的是为数据项分配的内存块。因此,变量也是数据对象,但pn指向的内存不是变量。乍一看,处置惩罚数据对象的指针方法大概不太好用,但它使步调在管理内存方面有更大的控制权。
为一个数据对象(可以是结构,也可以是根本范例)得到并指定分配内存的通用格式如下:
  1. typeName *pointer_name = new typeName;
复制代码
须要在两个地方指定命据范例:用来指定须要什么样的内存和用来声明符合的指针。固然,假如已经声明白相应范例的指针,则可以使用该指针,而不消再声明一个新的指针。
  1. using namespace std;
  2. int nights=1001;
  3. int *pt =new int;//allocate space for an int
  4. *pt = 1001;//store a value there
  5. cout <<"nights value ="<<nights<<":location"<< &nights << endl;
  6. cout << "int value="<<*pt<<":location="<< pt << endl;
  7. double *pd=new double;//allocate space for a double
  8. *pd = 10000001.0;//store a double there
  9. cout << "double value ="<<*pd<<":location="<< pd << endl;
  10. cout <<"location of pointer pd:"<< &pd << endl;
  11. cout <<"size of pt="<< sizeof(pt);
  12. cout <<":size of *pt="<< sizeof(*pt)<< endl;
  13. cout<<"size of pd="<< sizeof pd;
  14. cout<<":size of *pd="<< sizeof(*pd)<< endl;
  15. return 0;
复制代码

该步调指出了必须声明指针所指向的范例的缘故起因之一:地点自己只指出了对象存储地点的开始,而没有指出其范例(使用的字节数)。从这两个值的地点可以知道,它们都只是数字,并没有提供范例或长度信息。别的,指向int的指针的长度与指向 double 的指针雷同。它们都是地点,但由于声明白指针的范例,因此步调知道*pd是8个字节的double值,*pt是4个字节的int值。
对于指针, new分配的内存块通常与通例变量声明分配的内存块差别。变量nights和 pd 的值都存储在被称为栈(stack)的内存地域中,而new从被称为堆(heap)或自由存储区(free store)的内存地域分配内存。第9章将更具体地讨论这一点。
   内存被耗尽?
盘算机大概会由于没有富足的内存而无法满意 new 的哀求。在这种环境下,new 通常会引发非常,在第15章讨论的错误处置惩罚技能; 而在较老的实现中,new将返回0。
在 C++中,值为0的指针被称为空指针。C++确保空指针不会指向有用的数据,因此它常被用来表现运算符或函数失败(假如乐成,它们将返回一个有用的指针)。
  5. 使用 delete 开释内存

当须要内存时,可以使用new来哀求,在使用完内存后用delete运算符可以大概将其归还给内存池。归还或开释(free)的内存可供步调的其他部分使用。使用delete时,背面要加上指向内存块的指针(这些内存块最初是用new分配的):
  1. int *ps =new int;//allocate memory with new
  2. delete ps;//free memory with delete when done
复制代码
这将开释 ps指向的内存,但不会删除指针 ps自己。比方,可以将 ps 重新指向另一个新分配的内存块。肯定要配对地使用new和delete;否则将发生内存走漏,也就是说,被分配的内存再也无法使用了。假如内存走漏严肃,则步调将由于不停探求更多内存而停止。
不要实验开释已经开释的内存块,C++标准指出,如许做的效果将是不确定的,这意味着什么环境都大概发生。别的,不能使用delete 来开释声明变量所得到的内存:
  1. int *ps =new int;// ok
  2. delete ps;//ok
  3. delete ps;//not ok now
  4. int jugs =5;// ok
  5. int *pi = &jugs;// ok
  6. delete pi;// not allowed,memory not allocated by new
复制代码
只能用 delete 来开释使用 new分配的内存。不外,对空指针使用delete 是安全的。
注意,使用 delete 的关键在于,将它用于开释 new 分配的内存。这并不意味着要使用用于new 的指针,而是用于new的地点:
  1. int *ps =new int;//allocate memory
  2. int *pq = ps;//set second pointer to same block
  3. delete pq;//delete with second pointer
复制代码
一样寻常来说,不要创建两个指向同一个内存块的指针,由于这将增长错误地删除同一个内存块两次的大概性。
6. 使用 new 来创建动态数组

假如步调只须要一个值,则大概会声明一个简朴变量,由于对于管理一个小型数据对象来说,如许做比使用 new 和指针更简朴。而对于大型数据(如数组、字符串和结构),应使用 new。比方,假设要编写一个步调,它是否须要数组取决于运行时用户提供的信息。假如通过声明来创建数组,则在步调被编译时将为它分配内存空间。不管步调终极是否使用数组,数组都在那边,它占用了内存。在编译时给数组分配内存被称为静态联编,意味着数组是在编译时到场到步调中的。但使用 new 时,假如在运行阶段须要数组,则创建它;假如不须要,则不创建。还可以在步调运行时选择数组的长度。这被称为动态联编,意味着数组是在步调运行时创建的。这种数组叫作动态数组(dynamic array)。使用静态联编时,必须在编写步调时指定命组的长度;使用动态联编时,步调将在运行时确定命组的长度。
下面来看一下关于动态数组的两个根本标题:怎样使用 C++的 new 运算符创建数组以及怎样使用指针访问数组元素。
1.使用new创建动态数组

在 C++中,创建动态数组很轻易;只要将数组的元素范例和元素数量告诉new即可。必须在范例名后加上方括号,此中包罗元素数量。比方,要创建一个包罗10个int元素的数组,可以如许做:
  1. int *psome =new int[10];//get a block of 10 ints
复制代码
new运算符返回第一个元素的地点。在这个例子中,该地点被赋给指针psome。当步调使用完 new 分配的内存块时,应使用 delete 开释它们。然而,对于使用 new 创建的数组,应使用另一种格式的 delete 来开释:
  1. delete []psome;
  2. //free a dynamic array
复制代码
方括号告诉步调,应开释整个数组,而不但仅是指针指向的元素。请注意 delete 和指针之间的方括号。假如使用 new时,不带方括号,则使用 delete 时,也不应带方括号。假如使用new 时带方括号,则使用 delete时也应带方括号。
使用 new和 delete 时,应服从以下规则。


  • 不要使用 delete 来开释不是 new 分配的内存。
  • 不要使用 delete 开释同一个内存块两次。
  • 假如使用 new[ ]为数组分配内存,则应使用delete[ ]来开释。
  • 假如使用 new[ ]为一个实体分配内存,则应使用delete(没有方括号)来开释.
  • 对空指针应用 delete 是安全的。
现在我们回过头来讨论动态数组。psome是指向一个int(数组第一个元素)的指针。由于编译器不能对psome 是指向10个整数中的第1个这种环境举行跟踪,不能使用 sizeof运算符来确定动态分配的数组包罗的字节数。为数组分配内存的通用格式如下:
  1. type_name *pointer_name = new type_name [num elements];
复制代码
使用 new 运算符可以确保内存块足以存储num_elements 个范例为type_name 的元素,而 pointer_name将指向第1个元素。下面将会看到,可以以使用数组名的方式来使用pointer_name。
2.使用动态数组

下面的语句创建指针psome,它指向包罗10个int值的内存块中的第1个元素:
  1. int *psome =new int[10];//get a block of 10 ints
复制代码
可以将它看作是一根指向该元素的手指。假设int占4个字节,则将手指沿精确的方向移动4个字节,手指将指向第2个元素。统共有10个元素,这就是手指的移动范围。因此,new语句提供了辨认内存块中每个元素所需的全部信息。
对于第1个元素,可以使用 psome[0],而不是*psome;对于第2个元素,可以使用psome[1],依此类推。如许,使用指针来访问动态数组就非常简朴了,固然还不知道为何这种方法管用。可以如许做的缘故起因是,C和 C++内部都使用指针来处置惩罚数组。数组和指针根本等价是C和C++的优点之一(这在偶尔候也是个标题,但这是另一码事)。
  1. using namespace std;
  2. double *p3=new double[3];//space for 3 doubles
  3. p3[0]=0.2;
  4. p3[1]=0.5;
  5. p3[2]=0.8;
  6. cout << "p3[1] is " << p3[1] << ".\n";
  7. p3 = p3 + 1;// increment the pointer
  8. cout <<"Now p3[0] is"<<p3[0]<<" and ";
  9. cout << "p3[1] is "<< p3[1]<< ".\n";
  10. p3 = p3 - 1;// point back to beginning
  11. delete [] p3;// free the memory
  12. return 0;
复制代码

以上代码将指针p3看成数组名来使用,p3[0]为第1个元素,依次类推。下面的代码行指出了数组名和指针之间的根本差异:p3=p3+1;不能修改数组名的值,但指针是变量,因此可以修改它的值。请注意将p3加1的效果。表达式 p3[0]现在指的是数组的第2个值。因此,将p3加1导致它指向第2个元素而不是第1个。将它减1后,指针将指向原来的值,如许步调便可以给 delete[]提供精确的地点。
相邻的 int 地点通常相差2个字节或4个字节,而将p3加1后,它将指向下一个元素的地点,这表明指针算术有一些特殊的地方.
二、指针、数组和指针算术

指针和数组根本等价的缘故起因在于指针算术和 C++内部处置惩罚数组的方式。算术将整数变量加1后,其值将增长1;但将指针变量加1后,增长的量即是它指向的范例的字节数。
将指向double的指针加1后,假如体系对double使用8个字节存储,则数值将增长8;将指向short 的指针加1后,假如体系对 short 使用2个字节存储,则指针值将增长 2。
C++将数组名表明为地点,因此,在很多环境下,可以雷同的方式使用指针名和数组名。对于它们,可以使用数组方括号表现法,也可以使用排除引用运算符(*)。在多数表达式中,它们都表现地点。区别之一是,可以修改指针的值,而数组名是常量。另一个区别是,对数组应用 sizeof运算符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度,纵然指针指向的是一个数组。
1. 指针小结

1.声明指针

要声明指向特定范例的指针,请使用下面的格式:
  1. typeName *pointerName;
复制代码
2.给指针赋值

应将内存地点赋给指针。可以对变量名应用&运算符,来得到被定名的内存的地点,new 运算符返回未定名的内存的地点。
下面是一些示例:
  1. double *pn;
  2. double *pa;
  3. char *pc;
  4. double bubble=3.2;
  5. pn = &bubble;
  6. pc = new char;
  7. pa =new double[30];
复制代码
3.对指针排除引用

对指针排除引用意味着得到指针指向的值。
假如像上面的例子中那样,pn是指向 bubble 的指针,则*pn是指向的值,下面是一些示例:
  1. cout <<*pn;//print the value of bubble
  2. *pc = 'S';//place 's'into the memory location whose address is pc
复制代码
另一种对指针排除引用的方法是使用数组表现法,比方,p[0]与*pn 是一样的。决不要对未被初始化为得本地点的指针排除引用。
4.区分指针和指针所指向的值

假如 pt 是指向 int 的指针,则*pt 不是指向 int 的指针,而是完全等同于一个 int 范例的变量。pt才是指针。
5.数组名

在多数环境下,C++将数组名视为数组的第一个元素的地点。一种例外环境是,将sizeof运算符用于数组名用时,此时将返回整个数组的长度(单位为字节)。
6.指针算术

C++允许将指针和整数相加。加1的效果即是原来的地点值加上指向的对象占用的总字节数。还可以将一个指针减去另一个指针,得到两个指针的差。后一种运算将得到一个整数,仅当两个指针指向同一个数组(也可以指向超出末了的一个位置)时,这种运算才故意义,这将得到两个元素的隔断。
7.数组的动态联编和静态联编

使用数组声明来创建数组时,将接纳静态联编,即数组的长度在编译时设置;
使用 new[]运算符创建数组时,将接纳动态联编(动态数组),即将在运行时为数组分配空间,其长度也将在运行时设置。使用完这种数组后,应使用delete[]开释其占用的内存。
2.指针和字符串

  1. char flower[10]="rose";
  2. cout << flower<<"s are red\n";
复制代码
数组名是第一个元素的地点,因此cout语句中的 flower 是包罗字符r的char 元素的地点。cout 对象以为 char 的地点是字符串的地点,因此它打印该地点处的字符,然后继承打印背面的字符,直到遇到空字符(\0)为止。总之,假如给 cout提供一个字符的地点,则它将从该字符开始打印,直到遇到空字符为止。
这里的关键不在于 flower是数组名,而在于flower 是一个char 的地点。这意味着可以将指向 char 的指针变量作为 cout 的参数,由于它也是 char 的地点。固然,该指针指向字符串的开头,稍后将核实这一点。前面的 cout 语句中末了一部分的环境怎样呢?假如flower 是字符串第一个字符的地点,则表达式“s are red\n"是什么呢?为了与 cout对字符串输出的处置惩罚保持同等,这个用引号括起的字符串也应当是一个地点。在 C++中,用引号括起的字符串像数组名一样,也是第一个元素的地点。上述代码不会将整个字符串发送给cout,而只是发送该字符串的地点。这意味着对于数组中的字符串、用引号括起的字符串常量以及指针所形貌的字符串,处置惩罚的方式是一样的,都将通报它们的地点。与逐个通报字符串中的全部字符相比,如许做的工作量确实要少。
注意:在cout和多数 C++表达式中,char数组名、char 指针以及用引号括起的字符串常量都被表明为字符串第一个字符的地点。
应使用strcpy()或strncpy(),而不是赋值运算符来将字符串赋给数组。

指针这章知识点太多,分成两章发吧~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表