• C语言指针详解


     1为什么使用指针

    1.使用指针型变量在很多时候占用更小的内存空间。
    2.变量为了表示数据,指针可以更好的传递数据。
    (显然,方案二更好一些,方案二类似使用指针传递地址,方案一将内存中的内容重新“复制”了一份,效率比较低。)
    3.在数据传递时,如果数据块较大,可以使用指针传递地址而不是实际数据,即提高传输速度,又节省大量内存。
    4.数据转换,利用指针的灵活的类型转换,可以用来做数据类型转换.
    5.指针的机制比较简单,其功能可以被集中重新实现成更抽象化的引用数据形式
    6.数据转换,利用指针的灵活的类型转换,可以用来做数据类型转换,比较常用于通讯缓冲区的填充。
    7.指针的机制比较简单,其功能可以被集中重新实现成更抽象化的引用数据形式
    8.函数指针,形如: #define PMYFUN (void*)(int,int) ,可以用在大量分支处理的实例当中,
    9.在数据结构中,链表、树、图等大量的应用都离不开指针

    2 指针是什么?

    操作系统将硬件和软件结合起来,给程序员提供的一种对内存使用的抽象,这种抽象机制使得程序使用的是虚拟存储器,而不是直接操作和使用真实存在的物理存储器。
    所有的虚拟地址形成的集合就是虚拟地址空间。
     
    内存是一个很大的线性的字节数组,每个字节固定由 8 个二进制位组成,每个字节都有唯一的编号.
    当程序使用的数据载入内存时,都有自己唯一的一个编号,这个编号就是这个数据的内存指针地址。指针就是这样形成的。

    指针不仅可以表示变量的地址,还可以存储各种类型数据的地址,指针变量是用来保存这些地址的变量,
    与数组类似,依据地址存放的数据类型,指针也分为 int 指针类型,  double 指针类型, char 指针类型等等。


    综上,指针的实质就是数据在内存中的地址,而指针变量是用来保存这些内存地址的变量。


    3.指针变量 和 指向关系

    用来保存 指针 的变量,就是指针变量。

    int num = 97;
    int* p = & num; 指针变量p保存了变量 num的地址,p指向了变量num,p指向了num所在的内存块,
    int **pp = &p;  指针变量pp指向了p所在的内存块, 

     int型的num值为97占4个字节,内存地址为:0113F924 

     

    • num的地址为:0113F924num的值为 97 ,指针 p 指向 num 的内存块,指针 p 地址为:0113F90Cp的内存保存的值就是num的地址0113F924

     

    • 指针变量 pp 指向 指针 p,指针 pp 内存值为 指针 p 的地址:0113F90C,形成了指向指针的指针。

     

     声明一个指针变量

     

    C语言中,定义变量时,在变量名 前 写一个 * 星号,这个变量就变成了对应变量类型的指针变量。必要时要加( ) 来避免优先级的问题。

    指针其实就是一个变量,指针的声明方式与一般的变量声明类似.

    声明一个指针变量并不会自动分配任何内存。在对指针进行间接访问之前,指针必须进行初始化:或是使他指向现有的内存,或者给他动态分配内存,否则我们并不知道指针指向哪儿,这个问题需要特别关注。

    引申:C语言中,定义变量时,在定义的最前面写上typedef ,那么这个变量名就成了一种类型,即这个类型的同义词。

    取地址

    既然有了指针变量,那就得让他保存其它类型变量的内存地址,使用& 运算符取得一个变量的地址。

    特殊的情况,他们并不一定需要使用&取地址:
        
        数组名的值就是这个数组的第一个元素的地址。arr=&arr
        函数名的值就是这个函数的地址。  add=&add; 
        字符串字面值常量作为右值时,就是这个字符串对应的字符数组的名称,也就是这个字符串在内存中的地址。
        

    1. #include
    2. using namespace std;
    3. int add(int a , int b)
    4. {
    5. printf("a+b=%d\n", a + b);
    6. return a + b;
    7. }
    8. int mainxiaoyuzhizhen(){
    9. int* p ; //int* 变量p // 声明一个 int 类型的指针变量 p,该指针指向一个int类型的对象
    10. int* p_int; //int* 变量p_int // 声明一个 int 类型的指针变量p_int,该指针指向一个int类型的对象
    11. char * cp; // 声明一个 char 类型的指针变量 cp,该指针指向一个char类型的对象
    12. double* p_double; //指向double类型变量的指针 p_double
    13. struct Student* p_struct; //结构体类型的指针p_struct
    14. int** p_pointer; //指向 一个整形变量指针的指针
    15. int num = 97;//int 类型变量num
    16. int* p_num = #// 指针变量p_num 保存了变量num的内存地址&num
    17. float score = 10.00F;//float 类型的变量score
    18. float* p_score = &score; // 指针变量p_score 保存了变量score的内存地址&score
    19. int *arr0[10]; // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针 【每一个元素都是指针】
    20. int (*arr1)[10]; // 声明一个数组指针,该指针指向一个 int 类型的一维数组
    21. int arr[3] = {1,2,3}; //arr是包含3个int元素的数组变量
    22. arr; &arr;//int类型的数组arr的内存地址arr &arr
    23. int(*p_arr)[3]=&arr; //一个指向 含有3个int元素的数组arr 的指针变量 p_arr 【数组arr的指针变量p_arr】
    24. printf("指针变量p_arr=%p\n",p_arr); //指针变量p_arr=00A4FEF8
    25. // 或者这样写
    26. int(*p_arr2)[3];
    27. p_arr2=&arr;
    28. printf("指针变量 p_arr2 =%p\n",p_arr2); //指针变量 p_arr2 =00A4FEF4
    29. int(*p_func)(int,int); //指向返回类型为int,有2个int形参的函数的指针变量 p_func 【函数add的指针变量p_func】
    30. add; &add; //函数add的内存地址add 或者 &add
    31. // 指针变量p_func 指向 函数add的内存地址add
    32. p_func=add;
    33. add(1,2); // a+b=3
    34. // 指针变量p_func 指向 函数add的内存地址 &add
    35. p_func=&add;
    36. add(2,2); //a+b=4
    37. // 也可以这样写
    38. int(*p_funcc)(int,int)=&add;
    39. add(3,2); // a+b=5
    40. int arrr[5] = {1,2,3,4,5};
    41. int* p_first = arr; // 数组名arrr作为右值的时候,就是第一个元素的地址
    42. int first =*p_first;
    43. printf("输出数组arrr第一个元素first=%d\n",first); //输出数组arrr第一个元素first=1
    44. p_first++;
    45. int second =*p_first;
    46. printf("输出数组arrr第二个元素second=%d\n",second); //输出数组arrr第二个元素second=2
    47. }

    解地址

     对一个指针解地址,就可以取到这个内存数据,解地址 的写法,就是在指针的前面加一个 * 号。
        解指针的实质是:从指针指向的内存块中取出这个内存数据。

     int age = 19;
        int*p_age = &age;
        *p_age  = 20;  //通过指针修改指向的内存数据

    空指针

      空指针在概念上不同于未初始化的指针。
        空指针可以确保不指向任何对象或函数;
        而未初始化的指针则可能指向任何地方。空指针不是野指针。

    在C语言中,我们让指针变量赋值为NULL表示一个空指针,而C语言中,NULL实质是 ((void*)0) ,
        在C++中,NULL实质是0。

    void*类型指针

    void是一种特殊的指针类型,可以用来存放任意对象的地址。一个void指针存放着一个地址,这一点和其他指针类似。不同的是,我们对它到底储存的是什么对象的地址并不了解。

    由于void是空类型,只保存了指针的值,而丢失了类型信息,我们不知道他指向的数据是什么类型的,只指定这个数据在内存中的起始地址,如果想要完整的提取指向的数据,程序员就必须对这个指针做出正确的类型转换,然后再解指针。

    1. double a=2.3;
    2. void *p=&a;
    3. cout<//输出了a的地址
    4. int b=5;
    5. p=&b;
    6. cout<//输出了b的地址
    7. //cout<<*p<

     4.数组和指针

     同类型指针变量可以相互赋值,数组不行,只能一个一个元素的赋值或拷贝
        数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下进行访问的。
        指针很灵活,它可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。
        
        
        数组所占存储空间的内存:sizeof(数组名)
        数组的大小:sizeof(数组名)/sizeof(数据类型),

        在32位平台下,无论指针的类型是什么,sizeof(指针名)都是 4 ,
        在 64 位平台下,无论指针的类型是什么,sizeof(指针名)都是 8 。
        
        
         数组名作为右值的时候,就是第一个元素的地址
        
        
        指向数组元素的指针 支持 递增 递减 运算。
        p= p+1意思是,让p指向原来指向的内存块的下一个相邻的相同类型的内存块。
        在数组中相邻内存就是相邻下标元素。

    1. int arrr[5] = {1,2,3,4,5};
    2. int* p_first = arr; // 数组名arrr作为右值的时候,就是第一个元素的地址
    3. int first =*p_first;
    4. printf("输出数组arrr第一个元素first=%d\n",first); //输出数组arrr第一个元素first=1
    5. p_first++;
    6. int second =*p_first;
    7. printf("输出数组arrr第二个元素second=%d\n",second); //输出数组arrr第二个元素second=2

    5.函数与指针

     (1)函数的参数和指针

    C语言中,实参传递给形参,是按值传递的,也就是说,函数中的形参是实参的拷贝份,形参和实参只是在值上面一样,而不是同一个内存数据对象。这就意味着:这种数据传递是单向的,即从调用者传递给被调函数,而被调函数无法修改传递的参数达到回传的效果。

    C语言中,实参传递给形参,是按值传递的,也就是说,函数中的形参是实参的拷贝份,形参和实参只是在值上面一样,
        而不是同一个内存数据对象。
        这就意味着:这种数据传递是单向的,即从调用者传递给被调函数,而被调函数无法修改传递的参数达到回传的效果。

       有时候我们可以使用函数的返回值来回传数据,在简单的情况下是可以的,
        但是如果返回值有其它用途(例如返回函数的执行状态量),
        或者要回传的数据不止一个,返回值就解决不了了。

    1. void change(int a)
    2. {
    3. a++; //在函数中改变的只是这个函数的局部变量a,而随着函数执行结束,a被销毁。age还是原来的age,纹丝不动。
    4. }
    5. //传递变量的指针可以轻松解决上述问题。
    6. void change(int* pa)
    7. {
    8. (*pa)++; //因为传递的是age的地址,因此pa指向内存数据age。当在函数中对指针pa解地址时,
    9. //会直接去内存中找到age这个数据,然后把它增1。
    10. }
    11. int main(void)
    12. {
    13. int age = 60;
    14. change(age);
    15. printf("age = %d",age); // age = 60
    16. change(&age);
    17. printf("age = %d",age); // age = 61
    18. return 0;
    19. }

    (2)函数的指针

    函数指针定义:函数指针是指向函数的指针变量。因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。

    一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址。
         我们可以把函数的这个首地址赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。
        这种指针就是函数指针。

       函数指针的定义格式如下:

        函数返回值类型     (*函数指针变量名称)      (函数参数列表)
        returnType (*pointerName)(param list);

     returnType 为函数返回值类型

     pointerNmae 函数指针变量名称

     param list 为函数参数列表 【参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称,这一点和函数原型非常类似。】

    typedef int(*p_func)(int,int);//函数指针的定义

    typedef void (*fun)(void); //函数指针fun的定义 //无参数和返回值

    typedef int (*fun)(void)  //函数指针fun的定义

    typedef void* (*Fun4)(void*);//参数和返回值都为void*指针

    typedef int (*fun)(int )  //函数指针fun的定义

    typedef int (*fun)(int x)  //函数指针fun的定义

    typedef int (*fun)(int ,int ) //函数指针fun的定义
    typedef int (*fun)(int x,int y) //函数指针fun的定义

    typedef   char* (*pFunction)(char* p1,char* p2);  //函数指针pFunction的定义

     函数指针的赋值

        函数指针和其他指针一样定义之后使用之前也是需要初始化。
        函数指针有两个用途:调用函数和做函数的参数

          p_func=add;  
          p_func=&add;   
          pFunction=&function;  
    

    函数指针的调用 

     我们使用指针的时候,需要通过钥匙 * 来取其指向的内存里面的值,函数指针使用也如此。通过用(*pf)取出存在这个地址上的函数,然后调用它。

    用函数指针调用函数的格式如下:

    (【*】【函数指针名称】)(【参数列表】)

     int result =  (*p_func)(1,2);
     int result2 =  (*p_func)(2,2);
     char* result4  = (*pFunction)("aa","bb");不能直接用函数指针加上参数就直接调用
    

    1. #include
    2. #include
    3. using namespace std;
    4. int add(int a , int b)
    5. {
    6. printf("a+b=%d\n", a + b);
    7. return a + b;
    8. }
    9. char* function(char* p1, char* p2) {
    10. int i = 0;
    11. i = strcmp(p1,p2);//比较2个字符串是否相等
    12. if(0 == i){
    13. return p1;
    14. }else{
    15. return p2;
    16. }
    17. }
    18. int main(){ //xiaoyuzhizhen
    19. int* p ; //int* 变量p // 声明一个 int 类型的指针变量 p,该指针指向一个int类型的对象
    20. int* p_int; //int* 变量p_int // 声明一个 int 类型的指针变量p_int,该指针指向一个int类型的对象
    21. char * cp; // 声明一个 char 类型的指针变量 cp,该指针指向一个char类型的对象
    22. double* p_double; //指向double类型变量的指针 p_double
    23. struct Student* p_struct; //结构体类型的指针p_struct
    24. int** p_pointer; //指向 一个整形变量指针的指针
    25. int num = 97;//int 类型变量num
    26. int* p_num = #// 指针变量p_num 保存了变量num的内存地址&num
    27. float score = 10.00F;//float 类型的变量score
    28. float* p_score = &score; // 指针变量p_score 保存了变量score的内存地址&score
    29. int *arr0[10]; // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针 【每一个元素都是指针】
    30. int (*arr1)[10]; // 声明一个数组指针,该指针指向一个 int 类型的一维数组
    31. int arr[3] = {1,2,3}; //arr是包含3个int元素的数组变量
    32. arr; &arr;//int类型的数组arr的内存地址arr &arr
    33. int(*p_arr)[3]=&arr; //一个指向 含有3个int元素的数组arr 的指针变量 p_arr 【数组arr的指针变量p_arr】
    34. printf("指针变量p_arr=%p\n",p_arr); //指针变量p_arr=00A4FEF8
    35. // 或者这样写
    36. int(*p_arr2)[3];
    37. p_arr2=&arr;
    38. printf("指针变量 p_arr2 =%p\n",p_arr2); //指针变量 p_arr2 =00A4FEF4
    39. // 函数指针的赋值
    40. 函数指针的定义
    41. int(*p_func)(int,int); //指向返回类型为int,有2个int形参的函数的指针变量 p_func 【函数add的指针变量p_func】
    42. add; &add; //函数add的内存地址add 或者 &add
    43. //给函数指针p_func的赋值 add 方式1
    44. //给函数指针赋值时,可以用=&function或直接用函数名function。这是因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。因为一个函数标识符就表示了它的地址,并且赋值的时候函数不需要带圆括号.
    45. p_func=add; // 指针变量p_func 指向 函数add的内存地址add
    46. //函数指针的调用方式
    47. int result = (*p_func)(1,2); // a+b=3
    48. printf("result=%d\n", result); // result=3
    49. //给函数指针p_func的赋值&add 方式2
    50. p_func=&add; // 指针变量p_func 指向 函数add的内存地址 &add
    51. //函数指针的调用方式
    52. int result2 = (*p_func)(2,2); //a+b=4
    53. printf("result2=%d\n", result2); //result2=4
    54. //函数指针 p_funcc 的赋值&add 方式3
    55. int(*p_funcc)(int,int)=&add;
    56. //函数指针的调用方式
    57. int result3 = (*p_funcc)(3,2); // a+b=5
    58. printf("result3=%d\n", result3); // result3=5
    59. 函数指针的定义
    60. char* (*pFunction)(char* p1,char* p2);
    61. //给 函数指针 pFunction 的赋值 &function 方式2
    62. //给函数指针赋值时,可以用=&function或直接用函数名function。这是因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。因为一个函数标识符就表示了它的地址,并且赋值的时候函数不需要带圆括号.
    63. pFunction=&function;
    64. //函数指针pFunction 的调用方式
    65. char* result4 = (*pFunction)("aa","bb");
    66. printf("result4=%s\n", result4); // result4=bb
    67. int arrr[5] = {1,2,3,4,5};
    68. int* p_first = arr; // 数组名arrr作为右值的时候,就是第一个元素的地址
    69. int first =*p_first;
    70. printf("输出数组arrr第一个元素first=%d\n",first); //输出数组arrr第一个元素first=1
    71. p_first++;
    72. int second =*p_first;
    73. printf("输出数组arrr第二个元素second=%d\n",second); //输出数组arrr第二个元素second=2
    74. }

    (3)指针函数

    指针函数定义

    指针函数的落脚点是一个函数,这个函数的返回值是一个指针,与普通函数int function(int,int)类似,只是返回的数据类型不一样而已。

    指针函数定义:
        _type_ *function(int, int) //函数function 返回类型是指针地址
         
        int* fun(int x)        //指针函数的定义
        int* fun(int x,int y)  //指针函数的定义

    指针函数的调用

    在调用指针函数时,需要一个同类型的指针来接收其函数的返回值。

    int* result = fun(1);
        int* result = fun(1,2);

    1. typedef struct _Data{
    2. int a;
    3. int b;
    4. }Data;
    5. //指针函数
    6. Data* f(int a,int b){
    7. Data * data = new Data;
    8. data->a = a;
    9. data->b = b;
    10. return data;
    11. }
    12. int main(int argc, char *argv[])
    13. {
    14. QApplication a(argc, argv);
    15. //调用指针函数
    16. Data * myData = f(4,5);
    17. qDebug() << "f(4,5) = " << myData->a << myData->b;
    18. return a.exec();
    19. }

    函数指针与指针函数区别

    (1)定义不同
    指针函数本质是一个函数,其返回值为指针。
    函数指针本质是一个指针,其指向一个函数。

    (2) 写法不同
    指针函数:int* fun(int x,int y);
    函数指针:int (*fun)(int x,int y);  

    (3) 星号* 归属不同

     指针函数的*是属于数据类型的
    而函数指针的星号*是属于函数名的。 函数名带括号的就是函数指针  (*fun)()

     (4)用法不同
      函数指针的用法会更多,相对而言难度也更大,例如函数指针与回调函数

    6.结构体和指针

      结构体指针有特殊的语法: -> 符号
        如果p是一个结构体指针,则可以使用 p ->【成员】 的方法访问结构体的成员

      

    1. typedef struct
    2. {
    3. char name[31];
    4. int age;
    5. float score;
    6. }Student;
    7. int main(void)
    8. {
    9. Student stu = {"Bob" , 19, 98.0};
    10. Student*ps = &stu;
    11. ps->age = 20;
    12. ps->score = 99.0;
    13. printf("name:%s age:%d
    14. ",ps->name,ps->age);
    15. return 0;
    16. }

    7.深拷贝和浅拷贝

    如果2个程序单元(例如2个函数)是通过拷贝 他们所共享的数据的 指针来工作的,这就是浅拷贝,因为真正要访问的数据并没有被拷贝。
        
        如果被访问的数据被拷贝了,在每个单元中都有自己的一份,对目标数据的操作相互 不受影响,则叫做深拷贝。

    1. #include
    2. #include
    3. using namespace std;
    4. class CopyDemo
    5. {
    6. public:
    7. int a; //定义一个整型的数据成员
    8. char *str; //字符串指针 //一般结构体成员有指针 要自定义深拷贝构造函数,
    9. CopyDemo(int a, char * str) //构造函数,两个参数
    10. {
    11. this->a=a;
    12. // this->str=str;
    13. this->str = new char[1024]; //指针数组,动态的用new在堆上分配存储空间
    14. strcpy(this->str,str); //拷贝过来
    15. }
    16. // C++会自动默认 帮我们实现一个浅拷贝制构造函数,浅拷贝只复制指针, 有问题使用深拷贝
    17. //浅拷贝
    18. /* CopyDemo(CopyDemo& obj){
    19. this->a=obj.a;
    20. this->str=obj.str; //这里是浅拷贝会出问题,要深拷贝
    21. }*/
    22. //一般结构体成员有指针 要自定义深拷贝构造函数,
    23. CopyDemo(CopyDemo& obj){
    24. this->a=obj.a;
    25. // this->str=obj.str; //这里是浅拷贝会出问题,要深拷贝
    26. this->str = new char[1024]; //指针数组,动态的用new在堆上分配存储空间
    27. if(this->str!=0){
    28. strcpy(this->str,obj.str); //如果成功,把内容深拷贝过来
    29. }
    30. }
    31. //析构函数
    32. ~CopyDemo() {
    33. delete str;
    34. }
    35. };
    36. int main(){
    37. CopyDemo A(100,"hello");
    38. CopyDemo B = A; //复制构造函数,把A的10和hello!!!复制给B
    39. cout <<"A.a="<< A.a << ",A.str=" <// A.a=100,A.str=hello
    40. cout <<"B.a="<< B.a << ",B.str=" <//B.a=100,B.str=hello
    41. //修改后,发现A,B都被改变,原因就是浅拷贝,A,B指针指向同一地方,修改后都改变 所以要使用深拷贝
    42. B.a = 80;
    43. B.str = "word";
    44. //B.str[0] = 'k';
    45. cout <<"A.a="<< A.a << ",A.str=" <// A.a=100,A.str=hello
    46. cout <<"B.a="<< B.a << ",B.str=" <//B.a=80,B.str=word
    47. }

  • 相关阅读:
    Node实现数据加密
    没网络也能安装.Net 3.5!如何脱机安装.NET Framework 3.5
    Spring事务
    Maven:通过相对路径向jar中添加依赖项
    高压放大器选型标准规范要求有哪些
    【Linux】System V 共享内存
    我的 2023 年,35岁、父亲肺癌,失业,失恋、上岸
    AWS启示录:创新作帆,云计算的征途是汪洋大海
    ChainForge:衡量Prompt性能和模型稳健性的GUI工具包
    暗恋云匹配匿名交友聊天系统开发
  • 原文地址:https://blog.csdn.net/qq_33552379/article/details/132849593