1.4 指针

1. 指针是一个变量, 存储的是值的地址, 而不是值本身. 获取常规变量的地址, 我们通常使用地址操作符&就可以获得它的地址, 例如home是一个变量, 那么&home是它的地址.

2. 面向对象编程与传统的过程性编程的区别在于, OOP强调的是在运行阶段(而不是编译阶段)进行决策. 运行阶段指程序正在运行时, 编译阶段指编译器将程序组合起来时. 运行阶段决策就好比度假时选择那些景点取决于天气和当时的心情; 而编译阶段决策更像不管在什么条件下, 都坚持设定的日程安排. 运行阶段决策能够更灵活, 例如确定数组的长度这件事情, C++使用关键字new请求正确数量的内存以及使用指针来跟踪新分配的内存的位置, 这样能避免内存分配不足和浪费内存空间的情况发生.

3. 指针存储的是地址,*操作符称为间接值或解除引言操作符,将其应用于指针,可以得到该地址处存储的值。例如manly是一个指针,则manly表示的是一个地址,而*manly表示村粗在该地址的值。我们可以理解为*作用于地址,结果是该地址处存储的值。例如一个变量home,&home是它的地址,而*(&home)是存储在该地址的值,也就是home,记为home=*(&home)。

4. 声明和初始化指针。
    int *p1;
    表明*p1的类型是int,由于*操作符被用于指针,因此p1变量本身必须是指针,我们称p1的类型是指向int的指针,或int*。注意下面de int* p1,p2;将创建一个指针p1和一个int型变量p2,若要声明两个指针变量,则为int* p1,*p2。

5. 指针的危险。C++在创建指针时,计算机将分配用来存储地址的内存,但是不会分配用来存储指针所指向数据的内存。下面一段代码是危险的;int* fellow; *fellow=333; fellow是一个指针,但是程序并没有说明它指向哪里,那么我们也不知道333被存储到了哪里。这是指针最危险的地方。因此,定义指针后,一定要将其初始化为一个确定的、适当的地址或者为其分配一段明确的内存(如后面的例子一样,数组指针),这是关于使用指针的金科玉律。比如int* p;p=(int*)malloc(4*sizeof(int));这里p是一个数组指针,指向了含有4个int类型大小的空间的首地址,p也就相当于数组的首地址,p是一个含有4个整型数据的数组。

6. 如果有已命名的内存,那么可以通过内存名来访问内存,指针的用处对于这种情况也就不太大了。指针的真正用武之地在于,在运行阶段分配未命名的内存以存储值,在这种情况下,只能通过指针来访问内存。

7. 使用new分配内存。在运行阶段为一个int值分配未命名的内存,并且使用指针来访问这个值,使用new操作符,程序员要告诉new操作符需要为哪种数据类型分配内存,new将找到一个长度正确的内存块,并返回该内存块的地址,程序员的责任是将该地址赋给一个指针。例如,int* pn=new int;new int告诉程序,需要适合存储int的内存,new操作符根据数据类型来确定需要多少字节的内存,然后它找到这样的内存,并返回其地址。接下来,降低至赋值给pn。pn指向的是一个数据对象。为一个数据对象(可以是基本类型和结构体)获得并指定分配内存的通用格式如下,typeName pointer_name=new typeName;当然,如果已经声明了一个指针,可以直接为它赋值,例如有int* p;p=new int。

8. 内存被耗尽。当计算机没有足够的内存满足new的请求的时候,new将返回0。那么0将会赋给指针,C++中值为0的指针被称为空指针,C++确保空指针不会指向任何有效的数据,因此空指针常被用来表示操作符或者函数失败。

9. 使用delete来释放内存, 不过delete只能释放new分配的内存. delete后面加上指向由new分配的内存块的指针. 例如
    int *ps=new int;
    ps[0]=0;ps[1]=1;ps[2]=2;
    delete ps; //最后一句将释放ps指向的内存块, 但是不会删除ps指针本身, 还可以重新指向另一个新分配的内存块, 例如下面的句子
    int *ps=new int[5];
    ... ...
    delete ps;
    操作符new和delete一定要配对的使用, 否则会发生内存泄露, 比如对指针ps使用了new, 再使用delete, 再使用了delete, 第一个delete已经将ps指向的内存块释放了, 但是没有删除指针本身, 删除ps原型指向的内存块后, ps的指向我们就无法得知了, 接着再一个delete就会删除未知位置的数据, 这样非常不安全. 所以操作符new和delete一定要配对使用. 如果只是用new而不使用delete, 那么分配内存却不释放内存, 系统可用内存会越来越少, 久了就会内存不足了. 有特殊的情况, 对空指针使用delete是安全的.

10. 使用new创建动态数组. 动态数组就是程序在运行时给数组分配内存, 有时候程序是否需要数组是由程序在运行过程中用户输入的信息决定的, 需要用数组时才给分配内存, 而静态数组是在编译时给数组分配内存, 不管程序最终是否使用数组, 数组都在那里, 占用了内存. 在上述情况下, 使用动态数组明显使得程序更灵活, 更能合理使用内存. 创建方式如下:int *psome=new int[10]; new操作符返回第一个元素的地址, 该地址被赋予指针psome, 也就是说psome[0]的地址被赋予了指针psome. 使用delete释放一个数组, 格式为: delete [] psome, 方括号[]告诉程序, 应该释放整个数组, 而不仅仅是指针指向的元素. 数组名即是数组第一个元素的地址, 与指针等值. 而int array[10]是一个静态数组.

11. 使用动态数组. 先分配动态数组int *psome=new int[10]; psome指向数组的第一个元素, 因此*psome是第一个元素的值, 要访问其余九个元素, 只要把指针当做数组名就可以了, 第一个元素可以使用psome[0], 第二个元素是psome[1], ... ,第十个元素是psome[9].

12. 指针和数组名的根本差别. 在使用动态数组的时候, 我们可以把指针当做数组名使用, 但是指针和数组名还是有差别的, 根本差别在于我们不能修改数组名的值, 数组名的值永远是数组第一个元素的地址, 但是指针是变量, 可以修改它的值. psome指向第一个元素的值, psome+1指向第二个元素的值.

13. 指针,数组和指针算术. 指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式. C++将数组名解释为地址, 即为数组第一个元素的地址. 对于算术, 整数变量加1后, 其值将增加1, 但是指针变量加1后, 增加的量等于它指向的类型的字节数, 即指向了下一个此类型的数据. 例如将指向double的指针加1后, 如果系统对double使用8个字节存储, 那么指针的数值将增加8. 指针变量加1后, 其增加的值等于指向的类型占用的字节数.

14. 编译器对数组表示法的解释. 数组表示法arrayname[i],  C++编译器将其解释为*(arrayname+i), 如果指针pointername指向数组arrayname, 那么也可以用指针名来表示数组pointername[i]等价于arrayname[i], 将被C++编译器解释为*(pointername+i).

15. 指针和数组的另一个区别. 对数组使用sizeof操作符得到的是数组的长度, 而对指针使用sizeof操作符得到的是指针的长度. 例如动态数组int array=new int[4]={1, 2, 3, 4};指针pointer指向array数组. 那么sizeof(array)得到的是数组的长度, 共四个元素, 每个元素4字节, 共16字节, 而sizeof(pointer)得到的是指针的长度, 即指针存储的地址的长度, 32位机是4个字节.

16. 指针和字符串. 根据前面的内容, 对于字符串char flower[10]="rose";将会分配是个char空间, 至少留一个给空字符, 将rose填入空间后, 剩下的空间都填空字符. 也可以这样写char flowe[10]={'r','o','s','e'}; 剩下的空间都填空字符. 数组名flower的值也就是数组的首地址.例如下面的语句
    char flower[10]="rose";
    cout<<flower<<"s are red/n"; 结果将输出flowers are red
    数组名flower是字符串的首地址, 因此cout语句中flower是字符r的char元素的地址. cout对象认为char的地址是字符串的地址, 因此它会答应该地址处的字符, 然后继续打印后面的字符, 知道遇到空字符/0为止. 总结来说, 如果给cout提供一个字符的地址, 它将从该字符开始打印, 直到遇到空字符为止. 我们之前提到给cout提供指针,那么cout将会输出指针的值, 也就是一个地址, 那么我们给cout一个提供字符的地址, 如flower, 也相当于一个指针, 它将不会输出字符指针flower的值, 而是输出字符指针所指向的字符, 从它开始打印, 知道遇到空字符为止. 即如果指针类型为int*, 那么cout将输出指针的值, 如果指针类型为char *, 那么将输出字符. 那么我们怎么输出字符的地址呢? 将字符的指针转换成int*类型即可. 例如要输出字符串flower的地址, 那么这样就可以: cout<<(int*)flower;
   下面看这句中cout<<flower<<"s are red/n";中cout如何处理"s are red/n", 与cout对字符的指针处理相一致, 在C++中, 用括号引起的字符串像数组名一样, 也是第一个元素的地址, 是字符's'的地址, 上述代码不会讲"s are red/n"整个字符串发送给cout, 而是指发送该字符串的地址, 即's'的地址. 我们可以这样总结说, 在cout和多数C++表达式中, char数组名, 指向char的指针以及用引号括起来的字符串常量都被解释为字符串第一个字符的地址.

17. 使用char指针, 指向一个字符串. char * animal="dog";(这句就等效于char animal[]="dog";) 这句话将"dog"的地址赋值给animal指针, 一般来说, 编译器在内存留出一些空间, 以存储程序源代码中所有用括号括起来的字符串(如"dog"), 并将每个被存储的字符串与其地址关联起来. "dog"的值是一个地址, 被赋值给指针animal.

18. 要获得字符串的副本, 需分配空间来存储此副本, 要分配合适的空间才行, 例如要获得animal的副本, 则分配一段内存存储此副本, 使得指针pointer指向此副本, 分配空间可以这样: char * pointer=new char[strlen(animal)+1]; 注意strlen(animal)求得的是animal的长度, 不包含空字符, 结果是3. 接下来要将animal数组中的字符串复制到新分配的空间中, 不能仅仅将animal赋值给pointer, 这样只是让animal与pointer指向同一个字符串, 根本没有创建副本. 我们用库函数strcpy将animal的字符串赋值给pointer所指向的内存空间. 使用这个语句: strcpy(pointer, animal); 有两个参数, 第一个参数pointer是目标地址, 第二个参数animal是要复制的字符串的地址, 整句话的意思是将animal指向的字符串复制一份给pointer指向的空间, 这样就获得了animal的副本了. 在理解了C++处理括号括起来的字符串的方式后, 我们也就理解了下面的语句了, strcpy(pointer, "dog"); 第二个参数是字符串, 编译器在解释的时候, 第二个参数实际上被解释成字符串"dog"的地址, 也就是相当于一个指针了. 意义即是将字符串"dog"的地址指向的字符串(也就是字符串"dog"本身)复制一份给pointer指向的内存空间.
    如果要复制的字符串长度超过目标地址所指向空间的长度, 会怎么办呢? 例如 char food[5];(或者 char* food=new char[5]), 而使用下面语句: strcpy(food, "CarrotsCabbage"); 字符串"CarrotsCabbage"长度是14, 而food长度仅为5, 在这种情况下, 函数除了将前五个字符复制到food所指内存中外, 还将字符串中剩余的部分复制到数组food后面的内存字节中, 这可能会覆盖程序正在使用的其他内存, 这可能会发生错误. 要避免这种情况, 就是用strncpy()函数, 它将接受第三个参数, 即要复制的最大字符数. 要注意的是, 如果该字符在达到字符串结尾之前, 目标内存已经用完, 则它将不会添加空字符, 而一个字符串数组没有空字符是非法的, 必须有空字符, 那么我们应该让要复制的最大字符数比字符串数组的空间长度小1, 至少留一个空间给空字符. 如果在达到字符串尾部时, 目标内存还未用完, 那么剩下的内存都将存空字符. 还是上面的语句, 这样使用: strncpy(food, "CarrotsCabbage", 4); food[4]='/0'; 复制完后, 在尾部添加空字符.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值