• C++ 指针与引用 知识点 小结


    【摘要】

    指针能够指向变量、数组、字符串、函数、甚至结构体。即指针能够指向不同数据对象。指针问题 包含 常量指针、数组指针、函数指针、this指针、指针传值、指向指针的指针 等。

    主要知识点包含:1.指针与引用在概念上的三个差别及其在const、sizeof、自增运算上的差异;2.熟记经典swap函数的指针实现与引用实现,并能反映输出错误的两个函数的思想弊端;3.熟记GetMem函数的错误形式以及错误产生的原因;4.比較数组、指针与静态变量作为函数返回值的差异;5.str、*str以及&str三者的关系。6.指针继承复指向中虚函数、实函数及变量间的关系。7.写出const指针,指向const的指针。指向const的const指针。8.分析高维数组指针与高维数组在取址上的差别;9.区分悬浮指针与空指针;10.new和malloc的常见问题,本质差别,为什么产生new,为什么不提出malloc。11.this指针3个点,为什么会有this。this什么时候用。this怎么传给函数的。12.句柄和智能指针。



    【正文】

    考点:指针与引用的差别

    • 指针须要进行合法性检測而引用不须要;
    • 指针能够赋值又一次指向新的对象(不是地址、不是类型),可是引用仅仅能指向初始化被指定的对象不能改变;
    • 指向一个对象且指向的对象不改变时应使用引用。若存在不指向不论什么对象或指向的对象存在改变时应採用指针;
    • 指针能够指向空值,可是引用不能够指向空值。

      因此,程序猿可能有指向空值的时候。即同意变量为空的时候。应该使用指针;

    • 引用的代码效率要高于指针(详细体如今于是否能在初始化时候不赋值)。


    【补充】
    1)引用没有const。指针有const,这里需要切记,使用const声明的同一时候必需要初始化。
    2)指针指向一块内存。它的内容是所指向存的地址,引用时某块内存的别名;
    3)sizeof 引用 得到的是所指向的变量的大小。sizeof 指针得到的是指针本身的大小;
    4)指针和引用的自增运算意义不一样。
    【例】

    int &ref; 
    // 错误。引用不能为空且须要同一时候初始化,因此错误。
    int *p = 5。 
    // 错误,指针位置向实际内存空间。赋值之后不知道存放的地址。没有指向。因此错误。
    int *ptra。*ptrb;
    int ptra = *ptrb;
    // 错误,指针位置向实际内存空间,赋值之后不知道存放的地址。因此错误!

    const int num。 // 错误,const 常量赋值必须同一时候初始化,因此错误!

    考点:经典的 swap(int &a。int &b)函数
    指针方式:传入的是地址,接收的是指针。处理的是改变指针的指向关系;
    引用方式:传入的是变量(即,swap(int a,int b)),接收的是引用(地址,即 swap(int &ref_a,int&ref_b))。处理的是改变引用的指向关系,也就是引用变量本身;

    误区
    • 採用变地址方式交换终于会释放改变成果;
    • 函数若採用指针变量做介质会导致内存泄露;

    考点:指针申请内存空间
    函数源代码

    void GetMemA(char *p,int num)
    {
    	p = (char*)malloc(sizeof(char)*num);
    }
    void GetMemB(char **p。int num)
    {
    	*p = (char*)malloc(sizeof(char)*num);
    }

    【解析】

    由于,函数不能传值仅仅能传址,所以,在函数内採用指针申请内存空间(即。void GetMem(char *p。int num))是不会成功的。那么为实现空间申请我们应该採用指向指针的指针(即。void GetMem(char **p。int num))。

    如果,在 main() 函数中,变量 p 地址为 1 指向地址为 2(能够想象成变量 p 值为 2),*p 地址为 2 指向地址为 3(能够想象成变量 *p 值为 3),**p 地址为 3 存放一 char 型变量。

    在 void GetMemA(char *p。int num)中,临时称函数变量为 GetMemA.p 吧,非常明显 GetMemA.p 是一个地址。指向一个 char 型变量。函数申请栈区进入函数后,GetMemA.p 地址为 4 ,指针变量获取一块长度为 num 的 char 型内存空间。函数调用结束,弹栈销毁 变量GetMemA.p 及其申请的空间。那么,主函数中的变量 p 没有得到不论什么改变;

    在 void GetMemB(char **p,int num)中,临时称函数变量为 GetMemB.*p 吧。非常明显 GetMemB.*p 是一个地址,指向一个 char 型变量。

    函数申请栈区进入函数后,GetMemB.*p 地址为 5 。指针变量获取一块长度为 num 的 char 型内存空间。

    函数调用结束,弹栈销毁 变量 GetMemB.p 及其申请的空间(注意,这里是变量 GetMemB.p 而不是 变量 GetMemB.*p)。那么,GetMemB.*p 自然而然的保留了申请得到的内存空间。主函数中的变量 p 没有得到不论什么改变,而 *p 获得了一段长度为 num 的 char 型内存空间;

    当然,函数还可写成这样直接返回内存空间。

    char* GetMemC(char *p。int num)
    {
    	p = (char*)malloc(sizeof(char)*num);
    }
    【整型数据的源代码传递】
    #include <iostream>
    using namespace std;
    void GetMemory2(int *z)
    {
        *z=5;
    };
    int main()
    {
        int v;
        GetMemory2(&v);
        cout << v << endl;
        return 0;
    }
    【注】
    总之,改变的是函数中元素指向的变量就能有效改变变量的数值,假设改变的是函数中元素本身。不论元素是地址还是别的。都会在函数调用结束后被释放掉。

    字符串的关键知识点
    区分数组字符串和指针字符串

    char *strA()
    {
        char str[] = "hello world";
        return str;
    }
    解析:
    这个str里存在的地址是函数strA栈里“hello world”的首地址。

    函数调用完毕,栈帧恢复调用strA之前的状态。暂时空间被重置,堆栈“回缩”,strA栈帧不再属于应该訪问的范围。这段程序能够正确输出结果。可是这样的訪问方法违背了函数的栈帧机制。

    仅仅要另外一个函数调用的话,你就会发现。这样的方式的不合理及危急性。【未理解这句话。】
    ——后记
    OS觉得这段内存是能够被使用的,该段内存内容的改变或者其它操作会使得该段内存不再被 str 正确调用。
    假设想获得正确的函数,改成以下这样就能够:

    char *strA()
    {
        char *str = "hello world";
        return str;
    }
    首先要搞清楚char *str 和 char str[] :
    char str[] = "hello world";是分配一个局部数组。

    局部数组是局部变量,它所相应的是内存中的栈。局部变量的生命周期结束后该变量不存在了。


    char *str = "hello world";是指向了常量区的字符串,位于静态存储区。它在程序生命期内恒定不变,所以字符串还在。

    不管什么时候调用 strA。它返回的始终是同一个“仅仅读”的内存块。
    它返回的始终是同一个“仅仅读”的内存块。重要的事情说三遍!

    它返回的始终是同一个“仅仅读”的内存块。
    另外想要改动上述字符数组变量的不合理分配,也能够这样:

    char *strA()
    {
        static char str[] = "hello world";
        return str;
    }
    通过static开辟一段静态存贮空间。
    答案:
    由于这个函数返回的是局部变量的地址,当调用这个函数后,这个局部变量str就释放了,所以返回的结果是不确定的且不安全,随时都有被收回的可能。


    【总之。返回值不能是数组。最好是指针!】


    【另】

    char *str = “hello”;
    // 那么:*str = “h”。str = “hello”;&str = (地址)
    【另】
    char *a[ ] = { "hello","the","world"};
    char **ptra = a;
    // ptra++; cout<< *ptra << endl; // 输出 为 the
    // cout<< *ptra++ << endl; // 输出 为 hello
    【换言之,‘*’的优先级高于‘++’】
    字符串 和 数组的理解分析 详见 深入理解 字符串 和 数组;
    MSBD.V.4.0 - P.69.3 ~ 70.5 ~ 72.7 ~ 73.9- IMPT !

    !!

    内存偏移
    内存中两个指针 ptra 和 ptrb。当中,(ptra - ptrb)的值不是实际地址的数学差。(如果 ptra 和 ptrb 指向整型变量)实质上它们在编译当中的运算为(ptra - ptrb)/sizeof(int)。
    偏移地址,代码演示样例 01

    #include ...
    class A{ ... };
    class B{ ... };
    int main( )
    {
          A a;
          B *pb = (B*)(&a);
    }
    【解析】
    这里将对象 a 的地址赋给了一个指向 B 类对象的指针,这种赋值是非常野蛮的。尽管。不一定报错,造成的后果是强制把 a 地址内容看成是一个 B 类对象,pb 指向的是 a 类的内存空间。
    赋值和继承 导致 成员变量数值传递 的 比較分析 
    详见:C++ 类继承与对象赋值 情况下 成员变量的覆盖 浅析
    详址:http://blog.csdn.net/u013630349/article/details/46722893

    偏移地址。代码演示样例 02:

    #include ...
    int main( )
    {
          int *ptr;
          ptr = (int*)0x8000;
          *ptr = oxaabb;
    }
    【解析】
    代码的执行将会报错。这段代码的本意是将 0x8000 内存空间 指向一个 整型数据的地址。当中,0x8000 指向的空间存储 oxaa ,0x8001 指向的空间存储 oxbb 。指针随机分配地址是不被同意的。除非全部的资源为程序猿操作。否则,尽量避免操作底层。
    偏移地址,代码演示样例 03:

    #include<iostream>
    using namespace std;
    struct S
    {
    	int i;
    	int *p;
    }
    main( )
    {
    	S s;
    	int *p = &s.i;
    	p[0] = 1;
    	p[1] = 5;
    
    	cout<<"s 的地址 	"<<&s<<endl;
    	cout<<"s.i 的地址 	"<<&s.i<<endl;
    	cout<<"p 的值 		"<<p<<endl;
    	cout<<"p[0] 的地址 	"<<&p[0]<<endl;
    	cout<<"s.p 的地址 	"<<&s.p<<endl;
    	cout<<"p[1] 的地址 	"<<&p[1]<<endl;
    	cout<<"s.i 的值 	"<<s.i<<endl;
    	cout<<"s.p 的值 	"<<s.p<<endl;
    	cout<<"*s.p 的值 	"<<"报错"<<endl;	
    	cout<<"s.*p 的值 	"<<"报错"<<endl;
    	cout<<"&s.*p 的值 	"<<"报错"<<endl;
    
    	s.p = p;
    	cout<<"************************"<<endl;
    	cout<<"运行s.p = p; 	"<<"之后"<<endl;
    	cout<<"************************"<<endl;
    	cout<<"s 的地址 	"<<&s<<endl;
    	cout<<"s.i 的地址 	"<<&s.i<<endl;
    	cout<<"p 的值 		"<<p<<endl;
    	cout<<"p[0] 的地址 	"<<&p[0]<<endl;
    	cout<<"s.p 的值 	"<<s.p<<endl;
    	cout<<"s.p 的地址 	"<<&s.p<<endl;
    	cout<<"s.p[0] 的值 	"<<s.p[0]<<endl;
    	cout<<"s.p[1] 的值 	"<<s.p[1]<<endl;
    	cout<<"p[1] 的地址 	"<<&p[1]<<endl;
    	cout<<"p[0] 的值 	"<<p[0]<<endl;
    	cout<<"p[1] 的值 	"<<p[1]<<endl;
    
    	s.p[1] = 1;
    	cout<<"************************"<<endl;
    	cout<<"运行s.p[1] = 1;	"<<"之后"<<endl;
    	cout<<"************************"<<endl;
    	cout<<"s.p = "<<s.p<<endl;
    	cout<<"s.p+1 = "<<s.p+1<<endl;
    //	cout<<"*s.p = "<<*s.p<<endl; // 崩溃
    }
    【代码输出】
    s 的地址        0018FF40
    s.i 的地址      0018FF40
    p 的值           0018FF40
    p[0] 的地址   0018FF40
    s.p 的地址     0018FF44
    p[1] 的地址   0018FF44
    s.i 的值         1
    s.p 的值        00000005
    *s.p 的值	     报错
    s.*p 的值      报错
    &s.*p 的值   报错
    ************************
    运行s.p = p;    之后
    ************************
    s 的地址        0018FF40
    s.i 的地址      0018FF40
    p 的值          0018FF40
    p[0] 的地址     0018FF40
    s.p 的值        0018FF40
    s.p 的地址      0018FF44
    s.p[0] 的值     1
    s.p[1] 的值     1638208
    p[1] 的地址     0018FF44
    p[0] 的值       1
    p[1] 的值       1638208
    ************************
    运行s.p[1] = 1; 之后
    ************************
    s.p = 00000001
    s.p+1 = 00000005
    Press any key to continue
    【代码分析】
    int *p = &s.i;
    // 等价于 p = &p[0] = &s = &s.i
    // 且还有 p+1 = &p[1] = &s+1 = &s.i+1 = &s.p
    s.p = p;
    // 要知道 (s.*p) = s.p = p
    // 等价有 *(p+1) = *&p[1] = *&s+1 = *&s.i+1 = *&s.p = p
    // 简化得 *(p+1) = p[1] = s+1 = s.i+1 = s.p = p
    // 已知道 p = &p[0] = s.p = &s.p[0]
    // 所以有 &p[0] = p[1] 和 &s.p[0] = s.p[1]
    s.p[1] = 1
    // 此时再给,s.p[0]或者p[0]赋值,等价于在内存地址为 1 的空间写入变量。

    // 所以,此句临时不会报错。一旦对指向地址赋值,就会导致程序崩溃。

    考点:各种类型的指针表示
    优先级
    ()> 数据类型(如: int 、 char etc.)> [ ] > * > ++
    函数指针 void (*ptr)()。
    函数返回指针 int *func();
    const 指针 const int *ptr。
    指向 const 的指针 int *const ptr;
    指向 const 的 const 指针 const int *const ptr;
    一个有10个指针的数组,指针指向一个函数,该函数有一个整形參数 并返回一个整型
    int ( *ptr ( int ))[10]
    int (*(*ptr)(int。int))(int)
    ptr是一个输入參数为两个整型变量的函数指针,其返回值是一个输入參数为一个整形变量返回值为整型的的函数指针


    函数指针声明方式
    ...
    int max(int,int);// 已有求最大值函数 
    ...

    int *ptr(int,int) = &max;

    详见:

    C++ 高维指针数组 与 高维数组指针(一)

    C++ 高维指针数组 与 高维数组指针(二)

    C++ 数组指针 指针数组 以及 函数指针 指针函数

    详址:

    http://blog.csdn.net/u013630349/article/details/44998689

    http://blog.csdn.net/u013630349/article/details/44195523

    http://blog.csdn.net/u013630349/article/details/45098899


    【代码演示样例】

    int a[] = {1,2,3,4,5};

    int *ptr = (int*)(&a+1);           

    printf("%d %d", *(a+1), *(ptr-1));

    说明:*(a+1) 直接就为2 简单&a+1   因为本身a就为数组名也就是指针,加上& 相当于双指针 也就相当于**(a+1) 所以加1 就是数组总体加一行,ptr指向a的第6个元素,所以*(ptr-1)为5

    详见:C++ 数组指针 指针数组 以及 函数指针 指针函数

    详址:http://blog.csdn.net/u013630349/article/details/45098899


    考点:空指针和悬浮指针
    空指针为申请了内存空间。可是。指向为 NULL 或者 0 的指针。即 ptr = 0 ,此时,对指针操作尽管会导致崩溃,但,调试起来比悬浮指针方便很多;

    悬浮指针为申请了内存。经过一系列操作,释放内存后的指针。

    指针虽不再指向固定内存,可是,指针还是存在,且指向随机区域。此时对指针操作十分危急的。


    考点:malloc/free 与 new/delete

    malloc free new delete 的差别、比較、分析 

    详见:C++ 中 malloc/free 与 new/delete 浅析

    详址:http://blog.csdn.net/u013630349/article/details/44947255


    考点:指针与句柄
    Windows採用句柄标记系统资源,隐藏系统信息。你仅仅须要知道有这个东西。然后去调用就好了,句柄是一个32位无符号整型数。
    指针则是标记某个物理内存地址。

    auto_ptr
    std::auto_ptr <Object> pObj(new Object);
    auto_ptr 优点在于在析构时会自己主动删除此指针,可是不要误用;
    1)不能共享全部权。即不要让两个auto_ptr指向同一个对象;
    2)不能指向数组,由于在析构时候调用delete,而不是delete[];
    3)不能作为容器的对象;

    考点:this 指针

    详见:C++ this 指针 浅析

    详址:http://blog.csdn.net/u013630349/article/details/46412485

    C++的编译系统仅仅用了一段空间来存放在公共函数代码。在调用各个对象的成员函数时,都要调用这个公共的函数代码
    this指针的值为当前调用成员函数的对象的起始地址。
    一种情况就是。在类的非静态成员函数中返回类对象本身的时候。直接使用 return *this;第二种情况是当參数与成员变量名同样时使用this指针。如this->num = num (不能写成num = num)。


    this指针仅仅能在成员函数中使用。

    全局函数,静态函数都不能使用this。 成员函数默认第一个參数为 T* const this。
    this是通过函数的首參来传递的。this指针是在调用之前生成的。类实例后的函数,没有这个说法。类在实例化时,仅仅分配类中的变量空间。并没有为函数分配空间,类的函数在定义完毕后。它就在那儿,不会跑的。   

    遗留问题:
    【野指针】
    句柄
    智能指针

  • 相关阅读:
    leetcode 1. 两数之和
    leetcode 671. 二叉树中第二小的节点
    leetcode 100. 相同的树
    leetcode 110. 平衡二叉树
    leetcode 144. 二叉树的前序遍历
    1066. Root of AVL Tree (25)
    leetcode 100 相同的树
    leeCode 515 在每个树行中找最大值
    LeetCode 31.下一个排列
    面向对象UML中类关系
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/7087011.html
Copyright © 2020-2023  润新知