漫谈C语言指针

Posted 易水南风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了漫谈C语言指针相关的知识,希望对你有一定的参考价值。

更多博文,请看音视频系统学习的浪漫马车之总目录

上一篇漫谈C语言指针(一)中主要讲了指针的基本概念相关以及基本使用,今天基于上一篇博文进一步讲下指针和数组的关系、字符串指针、指针变量作为函数参数。

指针和数组

数组是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素。数组中的所有元素在内存中是连续排列的,中间没有间隙的,整个数组占用的是一块内存。
在这里插入图片描述
在C语言中,数组名可以转化为指向这个数组第0个元素的首地址的一个指针

所以遍历数组除了常用的:

int main(){
    int arr[] = { 3, 34, 44, 67, 234 };
    int len = sizeof(arr) / sizeof(int);  //求数组长度
    int i;
    for(i=0; i<len; i++){
        printf("%d  ", arr[i] );
    }
 }

也可以:

int main(){
    int arr[] = { 3, 34, 44, 67, 234 };
    int len = sizeof(arr) / sizeof(int);  //求数组长度
    int i;
    for(i=0; i<len; i++){
        printf("%d  ", *(arr+i) );  //*(arr+i)等价于arr[i]
    }
 }

(*(arr+i)的计算可以看下漫谈C语言指针(一)关于指针的算术运算那一节)

实际上arr[i]是完全等价于(arr+i),它们都表示一个指向数组的起始地址的指针加上偏移量。对数组的引用 a[i] 在编译时总是被编译器改写成(a+i)的形式,C语言标准也要求编译器必须具备这种行为。**

那么,数组名和指针就是完全等价的么?这恐怕是学习指针一个容易混淆的点。

int a[6] = {0, 1, 2, 3, 4, 5};
    int *p = a;
    int len_a = sizeof(a) / sizeof(int);
    int len_p = sizeof(p) / sizeof(int);
    printf("a length = %d, p length = %d\\n", len_a, len_p);
}

a是数组名,所以指向数组a的第0个元素的地址,而定义一个指针p并初始化为a的值,所以p也是指向数组a的第0个元素的地址,都对它们进行sizeof求长度,看看结果:

a length = 6, p length = 2

结果并不相等,sizeof(a)是等于数组长度,sizeof§其实等于当前环境下一个指针变量本身占用空间的大小

所以说数组名并不完全等价于指针,虽然a和p都指向数组a的第0个元素的首地址,但是毕竟a代表的是一个数组,而p只是一个指向int的指针,它们的本质类型不同,所以用sizeof求大小的时候结果就不同。只是在一般情况下,a会转化为指向第0个元素的首地址的指针

值得注意的是a和&a的关系,&a表示什么呢?有人会认为,既然a代表数组a的第0个元素的首地址,那&a就是指针a本身的地址了——中计了,上文已经说过,在一般情况下,a会转化为指向第0个元素的首地址的指针,除了sizeof情况,还有一种情况,就是&a,也是属于“非一般情况”。**在使用&的时候,数组名就仍然便是一个数组,而不是一个指针。**看下这个例子:

int main(){
    int a[6] = {0, 1, 2, 3, 4, 5};
    int *p = a;
    printf("a = %d\\n", a);
     printf("a+1 = %d\\n", a+1);
    printf("*(&a) = %d\\n", *(&a));
    printf("*(&a + 1) = %d\\n", *(&a + 1));
    printf("p = %d\\n",p);
    printf("p+1 = %d\\n",p+1);
}

运行结果:
a = 6487536
*(&a) = 6487536
*(&a + 1) = 6487560
p = 6487536
p+1 = 6487556

看到a和&a和p都是相等的,所以&a不是对指针a取地址,而是对数组a取地址。需要注意的是(&a + 1) = 6487560,而a+1 = 6487556,这里是不是挺奇怪的。还记得上一篇漫谈C语言指针(一)关于指针的算术运算那一节么,指针算术运算增减的大小是要考虑指针指向的类型的,这里a+1打印结果比a大了4,我们知道是sizeof(int)的大小。那么,以此类推,(&a + 1) 比a大了24,即(&a + 1)的1对应的是24字节,数组a刚好是6个int组成的,并且数组在内存中是连续五间隙的可知,&a和a虽然都是指向同个地址,但是它们指向的类型却发生了变化,a指向的是int,而&a指向的是int a[6] (每一种数组也可以看做一个类型),所以指针步长不同,导致算术运算结果不同~

所以总结来说,就是数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。

数组名作为指针还有一个特殊点,就是它是指针常量,即不可指向其他内存地址:

如果这样给str赋值,编译会报错。

char str[] = "abcdefg";
str = 'tddd';

字符串指针

C语言中没有特定的字符串类型,字符串本质就是字符数组,所以一般C语言可以这样定义字符串:

char str[] = "abcdefg";

字符串可以直接打印出来,也可以用普通的数组遍历方式打印出来:

	printf("%s\\n", str);
    //每次输出一个字符
    for(i=0; i<len; i++){
        printf("%c", str[i]);
    }

当然在上文已经讲过,数组名在表达式中可以转化为指针,所以可以使用指针遍历的方式遍历字符串:

 for(i=0; i<len; i++){
        printf("%c", *(str+i));
    }

当然既然字符串本质是数组,那也可以用字符指针的方式去定义字符串:

char *str = "abcdefg";

不知大家还记得否,在漫谈C语言内存管理 一文中有提过字符指针方式定义字符串,强调过这种定义方式的字符串是存储在常量区的,而数组方式定义则存储在堆或者栈,既然是放在常量区,那即使不可修改的。

假如尝试修改:

char *str = "abcdefg";
 *(str + 1) = 't';

运行直接报错,因为尝试去修改不可修改的常量区:
Process finished with exit code -1073741819 (0xC0000005)

不过指针的方式可以让指针指向其他的字符串:

 char* str1 = "dfafdf";
 str1 = "abc";
 printf("%s\\n", str1);```

运行结果:
abc

所以字符指针属于常量指针,即指向常量的指针

指针变量作为函数参数

漫谈C语言指针(一) 中的“通过指针传参改变函数外的变量数值”提到函数传参本质是在函数内拷贝一个参数的副本,那如果数组作为参数,是否也需要拷贝一份呢?

可以验证一下,通过打印出传入函数内的数组首地址和函数外传入的数组首地址比较来验证:

void arrayMethod(int x[],int len) {
    printf("changeValue %#X\\n", x);
}
int main() {
    int x[] = {1, 2, 3, 4, 5};
    arrayMethod(x,5);
    printf("main %#X\\n", &x);
}

运行结果:
changeValue 0X62FE00
main 0X62FE00

结果和漫谈C语言指针(一) 中讲的不一样,在漫谈C语言指针(一) 中,打印出来的地址是不一样的,但是在这里,打印出来是一样的,说明数组传到函数内部并没有拷贝。

实际上,数组作为参数传入函数,本质上也会转化为数组指针传入函数:

void arrayMethod(int *x,int len) {
    
}

验证下,上面的指针和数组一节有说到可以使用sizeof(数组名)/sizeof(数组元素类型)来求数组元素个数,现在我们尝试在函数外和以数组为参数的函数内求同一个数组的个数:

//参数为数组,在函数中通过sizeof(a) / sizeof(int)尝试计算出数组元素个数
void arrayParam(int a[]) {
    int len_a = sizeof(a) / sizeof(int);
    printf("arrayParam len_a %d\\n", len_a);
}

int main() {
    int a[] = {1,2,3,4,5,6};
    int len_a = sizeof(a) / sizeof(int);
    int *p = a;
    printf("main len_a %d\\n", len_a);
    arrayParam(a);
    printf("main len_p %d\\n", sizeof(int *)/ sizeof(int));//当前环境下一个指针占空间的大小(为了更容易和len_a进行对比,这里也除以sizeof(int),当然这里也可以换成其他任意类型的指针)
}

运行结果:
main len_a 6
arrayParam len_a 2
main len_p 2

可以看出函数外和函数内对数组求元素个数的结果是不同的,而且这里不管数组元素个数怎么改,在函数内求出来的长度和当前环境的指针长度相同的,这也进一步证明了数组作为参数传入函数,本质上也会转化为数组指针传入函数。

为什么要这样处理呢?因为数组是一个集合,里面数量未知,有可能会很庞大,所以如果传参也是直接在函数栈内拷贝一份数据的话,那空间和时间都很大,所以通过传地址,这样仅仅需要拷贝指针变量本身,就可以很容易地在函数内处理传入的数组。

下一篇:漫谈C语言指针(三)

以上是关于漫谈C语言指针的主要内容,如果未能解决你的问题,请参考以下文章

漫谈C语言指针

漫谈C语言指针

漫谈C语言内存管理

漫谈C语言指针

漫谈C语言指针

C语言编程漫谈——main函数