C语言要点总结-指针

目录

3.5指针

3.5.1万能指针

3.5.2指针和数组

3.5.3关于指针的加减

3.5.4指针和字符串(字符数组和字符指针)

3.5.5指针的步长及取值操作 

3.5.6指针和函数

3.5.7二级指针

3.5.8指针和数组再理解(数组指针)

3.5.9数组指针


3.5指针

指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样

注意:

指针类型存储的是所指向变量的地址,所以32位机器只需要32bit,而64位机器需要64bit。

同一设备下,指针类型的字节长度都是一样的,32位机器为32bit,及8byte;64位机器同理,64bit,16byte。

以下以32位机器作为演示:

printf("int * = %d\n",sizeof(int *));
printf("char * = %d\n",sizeof(char *));
printf("short * = %d\n",sizeof(short *));
printf("long * = %d\n",sizeof(long *));
printf("float * = %d\n",sizeof(float *));
printf("double * = %d\n",sizeof(double *));
int * = 4;
char * = 4;
short * = 4;
long * = 4;
float * = 4;
double * = 4;

此例子足以证明上述内容。64位机器输出结果为8

指针在内存中是如何储存的呢?以0xff00储存int为例

                                 

指针也是储存在内存中,也有对应的指向储存指针内存的指针,只不过这个储存在内存中的是地址。

关于指针的定义方式,及为什么要如此定义

指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

type *var-name;

关于type可以分为两种,第一种为常规数据类型,第二种为void,及万能指针。

指针在内存中占用的字节数和操作系统有关,与指针的类型无关,那么定义指针变量的时候,为什么要指定数据类型呢?如果指定为void又该如何使用该指针呢?

关于第一个问题

其一:

指定指针的类型,是为了方便操作该指针指向的这段内存中的内容,输出或改变指针指向的内存中的内容,需要知道这段内容的数据类型。

以int为例:

int a = 10;//定义整形变量a
int * p = &a;//定义指针,并指向int型变量a
*p = 20;//修改a为20

                                    

//变量a的地址
printf("Address of a variable: %p\n", &a  );
 
//在指针变量中存储的地址 ,其中%p表示输出指针的值
printf("Address stored in p variable: %p\n", p );
//假设程序输出如下:
Address of a variable: ff00
Address stored in p variable: ff00

通过打印p,我们可以知道,p为0xff00,那么p只是保存了a的首地址,在*p= 20;操作时,系统该如何判断,到底该修改对少字节内存中的数据?这个时候就要看p的数据类型了,这也是为什么我们要声明指针数据类型的原因了,在修改所指向内存中的内容时,我们需要知道,我们应该修改多少个字节中内存中的内容。

再举一个例子,打印数组的例子,字符串

其二:

输出阶段,我们注意printf的输出格式

printf("<格式化字符串>", <参量表>);

其中参量表代表的就是指向该内存的首地址(大部分),而格式化字符串中,也是表明从首地址开始,要包含多少个字节,以什么变量形式,输出内容。

综上,这就是为什么要声明指针变量的数据类型。

3.5.1万能指针

万能指针的赋值和常规其他指针一样,但是其他操作稍有不同。

定义方式如下:

void * p;

在进行赋值操作的时候要务必注意,需要对其类型进行强制转换:

int b = 10;//定义变量
void * p = &b;//定义万能指针,并完成初始化
*(int *)p = 100;//修改b的内容
printf("%d\n",b);
printf("%d\n",*(int *)p);

输出结果如下:

100

100

关于万能指针的赋值,可以在定义阶段就进行赋值,因为p不管是什么类型,都是只保存首地址,而指针类型的大小又是和操作系统有关,所以不管数量类型,&变量 = 指针,在一台操作系统中,这是一个恒等式,&变量也是找出该变量内存的首地址,等式两边内存占用的字节数是相同的,所以完全可以这样赋值。

其中,因为必须要指定指针的数据类型,所以对于万能指针,在其他操作的时候需要强制转换类型:

首先,先强制转换类型:(int *)p,将其转换为int类型,告诉计算机,之后的操作,需要对连续四个字节进行操作。

其次,再访问其内存指向的内容并修改,*(int *)p=100;

                                            

 

3.5.2指针和数组

注意:

1:数组名是常量,不能修改

2:数组名是数组的首地址——是数组第一个元素的地址,可以使用指针来代替数组进行操作(此行为称为指针的降级操作

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int * p = arr;
    int num = sizeof(arr)/sizeof(int);
    for(int i = 0;i<num;i++)
    {
        qDebug()<<"数组arr第"<<i+1<<"个元素"<<p[i];
    }

输出结果: 

其中sizeof关键字的解释,后面会再次强调。

除此之外,还可以利用指针加减来进行输出:

3.5.3关于指针的加减

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int * p = arr;
    int num = sizeof(arr)/sizeof(int);
    for(int i = 0;i<num;i++)
    {
        qDebug()<<"数组arr第"<<i+1<<"个元素"<<*(p+i);
    }

输出如下:

 

注意两次使用指针的方式:

qDebug()<<"数组arr第"<<i+1<<"个元素"<<p[i];

qDebug()<<"数组arr第"<<i+1<<"个元素"<<*(p+i);

p指向的是某段内存的地址,并且其数据类型为int *,对指针的加减,相当于移动这个指针

一个类型为 T 的指针的移动,以 sizeof(T) 为移动单位。 原理是c语言中的指针加减后,会根据指针的类型采用不同的偏移量。

int* a;int* b = a+1;则 b - a = sizeof(int)
 
char* a; char* b = a+1; 则b - a = sizeof(char)  

 下面解释一下通过*(p+1)是如何遍历整个数组的:

首先输出一下地址:

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int * p = arr;
    int num = sizeof(arr)/sizeof(int);
    for(int i = 0;i<num;i++)
    {
        qDebug()<<"数组arr第"<<i+1<<"个元素"<<*(p+i)<<"地址为(1):"<<p+i<<"地址为(2):"<<arr+i;
    }

 

实际的储存方式也是如此,arr为数组名,指向第一个元素的地址,将arr的地址赋值给p后,p和arr指向的都是相同内存段,保存着相同的地址,对arr/p进行移动(加/减)操作,都是根据其数据类型进行移动

因为都是int类型,所以一次地址加4(  sizeof(int)) 

    

3.5.4指针和字符串(字符数组和字符指针)

在C语言中,字符数组存放在全局数据区栈区,可读可写。字符指针存放在常量区,只读不能写。

    char str1[] = "hello world";
    char str2[] = "hello world";
    char str1[] = "hello world";
    char str2[] = "hello world";
    qDebug()<<"str1:"<<str1;
    qDebug()<<"str2:"<<str2;
    str1[0] = 'a';
    qDebug()<<"str1:"<<str1;

输出:

以上两个字符串是占用了不同的内存,而且是可以改变的。

    char * str3 = "hello world";
    char * str4 = "hello world";

 无法修改,为常量。只可以读,不可以写

 程序会直接将hello world保存至常量区,所以str3和str4是相等的,指向同一块内存空间。

常量区中的内容一般无法修改,但是有些编译器是允许程序进行修改的。

关于字符数组的赋值过程:

①编译器现将hello world复制到常量区

②之后再将其内容复制到栈区或者其他分区(这个要看char str[]是在哪儿定义的了)

但是字符串指针不同,如果两个字符串指针赋予相同的字符串,只需要将指针指向相同的常量区地址即可。

    char str1[] = "hello world";
    char str2[] = "hello world";

str1和str2占用的是不同的地址。

    char * str3 = "hello world";
    char * str4 = "hello world";

 str3和str4指向的是相同的地址。

3.5.5指针的步长及取值操作 

3.5.3关于指针的加减中对此部分也有讲解:

一个类型为 T 的指针的移动,以 sizeof(T) 为移动单位。 原理是c语言中的指针加减后,会根据指针的类型采用不同的偏移量。

int* a;int* b = a+1;则 b - a = sizeof(int)
 
char* a; char* b = a+1; 则b - a = sizeof(char)  

指针的类型,不单单决定了指针的步长,还决定解引用的时候从给定地址开始取类型大小的字节数(3.5一开始就有赘述此部分)

 以下有两个例子

(1)

    T student;
    qDebug()<<"student占用空间:"<<sizeof(student);
    student.T_a = 1;
    student.T_d = 10.11111;
    qDebug()<<"通过指针的方式取出T_d的值:"<<* (double *)( (char *)&student + 4 );

输出结果:

&student:取结构体的地址。

(char *):强制转换为char *类型,因为步长为1。

+ 4:因为要去double T_d中的内容,需要跳过int 类型的T_a。

之后强制转换类型为double ,并从中取值。

以上过程中

第一次类型转换是为了步长考虑。

第二次类型转换是为了取值告诉指针取多少个内存中的值 。

(2)

char buf[20] = { 0 };
int a = 100;
memcpy(buf+1,&a,sizeof(int));

char * p = buf;
printf("*(int * )(p3+1) = %d\n",*(int *)(p + 1));

具体说明同上,结果 显然为100,。

int *这个强制转换类型的过程,就是为了之后从p+1这个首地址开始,知道要从首地址开始,要往后偏移多少

参考内容:https://blog.csdn.net/on_1y/article/details/13030439

3.5.6指针和函数

指针做函数参数:

  1. 输入:主调函数分配内存
  2. 输出:被调函数分配内存

函数,数组名作为参数,指针作为参数,指针作为返回值

3.5.7二级指针

    int a = 10;
    int * p = &a;
    int ** pp = &p;
    qDebug()<<"a的值:"<<a<<"a的地址:"<<&a;
    qDebug()<<"p的值:"<<p<<"p的地址:"<<&p;
    qDebug()<<"pp的值:"<<pp<<"pp的地址:"<<&pp;
    qDebug()<<"p的值:"<<*pp;
    qDebug()<<"a的值"<<**pp;

从以上内容即可看出:

一级指针指向普通变量的地址

二级指针指向一级指针的地址。

取值则是一个不断从嵌套的盒子中取出东西的过程。

在函数传参中,又是需要传递一级指针的地址,此时需要二级指针作为参数去进行接收。

void allocataSpace(char **pp)
{
    char * temp = malloc(100);
    memset(temp,0,100);
    strcpy(temp,"hello world");
    *pp = temp;
}
void test()
{
    char * p = NULL;
    allocataSpace(&p);
    printf("p = %s\n",p);
}

3.5.8指针和数组再理解(数组指针)

    int Array_a[4] = {0,1,2,3};
    int Array_Da[4][5] =
    {
        {0,1,2,3,4},
        {10,11,12,13,14},
        {20,21,22,23,24},
        {30,31,32,33,34}
    };
    qDebug()<<endl<<"一维数组大小,sizeof(Array_a):"<<sizeof(Array_a)
           <<endl<<"一维数组大小,sizeof(Array_a)/sizeof(int):"<<sizeof(Array_a)/sizeof(int)
           <<endl<<"一维数组,数组名:"<<Array_a
           <<endl<<"一维数组,数组名取地址:"<<&Array_a
          <<endl<<"一维数组,第一个元素的地址:"<<&Array_a[0]
          <<endl<<"一维数组,数组名步长:"<<(char *)(Array_a+1) - (char *)Array_a
         <<endl<<"一维数组,数组名取地址步长:"<<(char *)(&Array_a+1) - (char *)(&Array_a)
        <<endl<<"通过坐标访问数组元素Array_a[0]:"<<Array_a[0]
       <<endl<<"通过指针访问数组元素*Array_a(解引用):"<<*Array_a
        <<endl;

    qDebug()<<"二维数组大小,sizeof(Array_Da):"<<sizeof(Array_Da)
           <<endl<<"一维数组大小,sizeof(Array_Da)/sizeof(int):"<<sizeof(Array_Da)/sizeof(int)
          <<endl<<"二维数组,数组名:"<<Array_Da
           <<endl<<"二维数组,数组名取地址:"<<&Array_Da
          <<endl<<"二维数组,第一个元素取地址:"<<&Array_Da[0][0]
           <<endl<<"二维数组,第一行第一列元素地址方式1:"<<Array_Da[0]
          <<endl<<"二维数组,第一行第一列元素地址方式2:"<<&Array_Da[0][0]
            <<endl<<"二维数组,数组名步长:"<<(char *)(Array_Da+1) - (char *)Array_Da
           <<endl<<"二维数组,数组名取地址步长:"<<(char *)(&Array_Da+1) - (char *)(&Array_Da)
          <<endl<<"通过坐标访问数组元素Array_Da[0][0]:"<<Array_Da[0][0]
         <<endl<<"通过指针访问数组元素*Array_Da(解引用):"<<*Array_Da
           <<endl<<"通过指针访问数组元素*Array_Da[0](解引用):"<<*Array_Da[0];

输出:

首先看一维数组

    int Array_a[4] = {0,1,2,3};

    qDebug()<<endl<<"一维数组大小,sizeof(Array_a):"<<sizeof(Array_a)
           <<endl<<"一维数组大小,sizeof(Array_a)/sizeof(int):"<<sizeof(Array_a)/sizeof(int)
           <<endl<<"一维数组,数组名:"<<Array_a
           <<endl<<"一维数组,数组名取地址:"<<&Array_a
          <<endl<<"一维数组,第一个元素的地址:"<<&Array_a[0]
          <<endl<<"一维数组,数组名步长:"<<(char *)(Array_a+1) - (char *)Array_a
         <<endl<<"一维数组,数组名取地址步长:"<<(char *)(&Array_a+1) - (char *)(&Array_a)
        <<endl<<"通过坐标访问数组元素Array_a[0]:"<<Array_a[0]
       <<endl<<"通过指针访问数组元素*Array_a(解引用):"<<*Array_a
        <<endl;

数组名就是指向数组第一个元素的指针,从对 Array_a[0] 和*Array_a就可以看出来,二者相同,从&Array_a[0] 和 Array_a也可以看出来。

关键问题在于

1:数组名取地址时:

"一维数组,数组名步长:"<<(char *)(Array_a+1) - (char *)Array_a
"一维数组,数组名取地址步长:"<<(char *)(&Array_a+1) - (char *)(&Array_a)

输出:

 

需要强行转换类型,才能看出来到底步长是几个byte(字节),如果不进行强制类型转换 ,都是int类型指针或者数组指针,输出结果一定是1的。

从上述输出我们可以看出,数组名步长是4个字节,也就是一个int,言外之意,通过数组名,此时也是等价于指向数组首地址的指针,是可以通过步长的变化访问数组这段连续内存中的任意一个元素的。

    qDebug()<<"第一个元素:"<<*Array_a
           <<endl<<"第二个元素:"<<*(Array_a + 1)
          <<endl<<"第三个元素:"<<*(Array_a + 2)
         <<endl<<"第四个元素:"<<*(Array_a + 3)
        <<endl;

 但是,当数组名取地址的时候,步长是整个数组所占用的字节数,即16字节,此时,取地址后的数组名,指向的是整个数组

2:sizeof(数组名)

    qDebug()<<endl<<"一维数组大小,sizeof(Array_a):"<<sizeof(Array_a)
           <<endl<<"一维数组大小,sizeof(Array_a)/sizeof(int):"<<sizeof(Array_a)/sizeof(int)

输出:

 在以上两种情况中,数组名不是指针,所以数组名不完全等价于指向数组首地址的指针,以上情况就是唯二的特例。

再看二维数组

    qDebug()<<"二维数组大小,sizeof(Array_Da):"<<sizeof(Array_Da)
           <<endl<<"一维数组大小,sizeof(Array_Da)/sizeof(int):"<<sizeof(Array_Da)/sizeof(int)
          <<endl<<"二维数组,数组名:"<<Array_Da
           <<endl<<"二维数组,数组名取地址:"<<&Array_Da
          <<endl<<"二维数组,第一个元素取地址:"<<&Array_Da[0][0]
           <<endl<<"二维数组,第一行第一列元素地址方式1:"<<Array_Da[0]
          <<endl<<"二维数组,第一行第一列元素地址方式2:"<<&Array_Da[0][0]
            <<endl<<"二维数组,数组名步长:"<<(char *)(Array_Da+1) - (char *)Array_Da
           <<endl<<"二维数组,数组名取地址步长:"<<(char *)(&Array_Da+1) - (char *)(&Array_Da)
          <<endl<<"通过坐标访问数组元素Array_Da[0][0]:"<<Array_Da[0][0]
         <<endl<<"通过指针访问数组元素*Array_Da(解引用):"<<*Array_Da
           <<endl<<"通过指针访问数组元素*Array_Da[0](解引用):"<<*Array_Da[0];

输出:

 从二维数组中,可以看出,二维数组更像是两个一维数组的嵌套,

第一层Arrary_Da[X]保存的是Arrary_Da[X][Y]首元素的地址,相当于一层指针层,一层线性数据层

如下示例可以验证:

     qDebug()<<"Array_Da[0][0]:"<<&Array_Da[0][0]
            <<endl<<"*Array_Da[0]:"<<Array_Da[0]
           <<endl<<"Array_Da[1][0]:"<<&Array_Da[1][0]
             <<endl<<"*Array_Da[1]:"<<Array_Da[1]
            <<endl<<"Array_Da[2][0]:"<<&Array_Da[2][0]
              <<endl<<"*Array_Da[2]:"<<Array_Da[2]
             <<endl<<"Array_Da[3][0]:"<<&Array_Da[3][0]
               <<endl<<"*Array_Da[3]:"<<Array_Da[3];

输出:

输出效果显而易见

当然从此部分也可以看出,二维数组名和一维数组名有着一样的性质,在唯二的情况下,是不等于指向首元素的指针的

3.5.9数组指针

 一维数组:

    int Array_a[4] = {0,1,2,3};
    int (*PArr)[4] = &Array_a;
    qDebug()<<"数组指针:"<<*PArr
           <<endl<<"数组名"<<Array_a;

输出: 

 

 二维数组:

    int Array_Da[4][5] =
    {
        {0,1,2,3,4},
        {10,11,12,13,14},
        {20,21,22,23,24},
        {30,31,32,33,34}
    };
    int (*DPArr)[4][5] = &Array_Da;
    qDebug()<<"二维数组指针:"<<*DPArr
           <<endl<<"二维数组名"<<Array_Da;

输出:

总结:数组指针的定义方式:

一维数组定义方式:

Data_Type (* NameForpointer)【Size】 =  & NameOfOne-dimensionalArray;

    int Array_a[4] = {0,1,2,3};
    int (*PArr)[4] = &Array_a;

二维数组定义方式:

Data_Type (* NameForpointer)【SizeOfRow】【SizeOfCol】 =  & NameOfTwo-dimensionalArray;

    int Array_Da[4][5] =
    {
        {0,1,2,3,4},
        {10,11,12,13,14},
        {20,21,22,23,24},
        {30,31,32,33,34}
    };
    int (*DPArr)[4][5] = &Array_Da;

其他定义方式:

以一维数组为例:

使用关键字typedef,以下使用typedef和平时稍有区别

typedef int (Array_TYPE) [4];

Array_TYPE myarray;//等于定义int myarray [4];
//数组指针的定义如下:
Array_TYPE * PArray = &myarray;

 数组指针指向整个数组,对齐解引用,就是数组名

发布了85 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41605114/article/details/104497045