小引
在指针的学习阶段,有不少同学都十分畏惧这个物什,甚至“谈指针色变”。其实对指针的不理解,其实本质上是对内存的不理解,本篇博客就从零开始、系统的来讲解指针这个话题。
首先我们应该明确以下的一些基础常识:
4 或 8
个字节。(32
或64
位平台)顾名思义,就是指向字符变量的指针,指针内存放的内容是一个字符。它的一般写法如下:
int main(){
char ch = 'w';
char *ptr = &ch;
*ptr = 'w';
return 0;
}
如果此时对
ptr
进行strlen
,就会内存访问越界,属于未定义行为,结果不可预期。因为指针指向的是一个字符而不是字符串。
指向单个字符的使用方式简单明了,但是相对于下面的这种常见的指向字符串的使用方式,就相形见绌了。
int main(){
char *ptr_1 = "this is a C string";
printf("%s\n",ptr_1);
return 0;
}
这样也可以间接的表现为指针指向了字符串
。
代码中char *ptr_1 = "this is a C string";
语句看似是将字符串放入了指针内,但其本质是将这个字符串的首地址存放到了指针内。
如果此时对
ptr_1
进行strlen
,就会正确的打印出字符串的个数,因为此时指针的指向不再是单个字符,而是字符串。
其实编译器无法识别
指针的指向是字符数组 还是字符串,所以它的指向需要使用者来定义保证。
请看代码:
int main(){
char str1[] = "This is a C string.";
char str2[] = "This is a C string.";
char *str3 = "This is a C string.";
char *str4 = "This is a C string.";
if(str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
str3
和str4
指针指向的是同一个常量字符串。C/C++
语法上会把常量字符串存储到单独的一个内存区域,当几个不同的指针同时指向同一个字符串时,他们实际会指向同一块内存(这个字符串的首地址)。所以这几个指针其实是同一个指针。它的本质是指针,指针的指向是一个 数组
。它的定义如下:
int (*arr)[20];
因为
[]
的优先级高于*
,而()
的优先级高于[]
。所以加上圆括号()
改变优先级顺序。(不加圆括号的情况下面会提及)
先进行括号( )
内的操作,这里变量名arr
与*
结合就明确了他是一个指针变量
,括号操作符完成再与其他元素进行结合,它指向了一个大小为10个整型元素的数组。
【二维数组也是一个数组指针】
arr
和&arr
:arr
是数组名,数组名表示数组首元素的地址&arr
就是数组指针,指针的指向是整个数组。请看如下代码:
int main(){
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
运行结果为:
二者输出内容相同,这就说明数组名
和& 数组名
是等价的吗?其实不然,请看如下代码:
int main(){
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr = %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1 = %p\n", &arr+1);
return 0;
}
运行结果为:
这里对数组名 + 1
和& 数组名 + 1
操作发现二者出现了区别,说明它们有着本质上的差别。
arr
指向数组首元素,&arr
是数组指针,指向数组,二者指向的地址是相同的但类型不同。
arr + 1
是指向数组首元素之后一个元素的指针。
而&arr + 1
是指向下一个数组,跨过了整个数组,步距为数组元素的大小之和。
与数组指针相混淆的有另一个名词:指针数组
。
int *p1[10];
它的本质是数组,数组中的所有元素都是 指针
。
【二级指针也是一个指针数组】
int *arr1[10]; //整型指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5]; //二级字符指针的数组
具体举例:
const char *arr[] = {
"hehe",
"haha",
"xixi"
};
arr
数组中的三个字符串也就是三个字符数组,数组可以隐式转换为指针,所以就可以使得arr
的类型设为char *
的字符指针类型,所以arr
中三个元素的类型都是char *
。const
关键字修饰本数组是为了确保数组内内容不发生改变。同时因为是三个字符串常量,其本身也不会发生改变。对于数组:
int arr[10] = { 0 };
使用以下函数进行传参:
// 情况 1
void test(int arr[])
正确
arr
识别为*arr
的指针,与其数组大小无关,所以数组大小的参数省略书写也是完全可以的。// 情况 2
void test(int arr[10])
正确
// 情况 3
void test(int *arr)
正确
*arr
,数组元素也会转换成数组的移动,所以这种写法也是可以的。所以以上三种方法都是正确的,内涵也是相同的,读者使用时可以根据具体情况选择不同的表现形式,提高代码可读性。
对于数组:
int *arr2[20] = { 0 };
使用以下函数进行传参:
// 情况一
void test2(int *arr[20])
正确
// 情况二
void test2(int **arr)
正确
int*
看作一个整体,将它typedef
成为一个类型t
t arr2[20];
了,调用函数传参就可以看成t *arr
,就与上面所讲的一维整型数组传参如出一辙了。这样传参也是正确的。所以指针数组可以当做二级指针来传参。
对于数组:
int arr[3][5] = {0};
使用以下函数进行传参:
// 情况 1
void test(int arr[3][5])
正确
// 情况 2
void test(int arr[][])
不正确
[]
中第一个数是行数
,第二个数列数
,行数可以目前不知道,但是每一行有多少元素,即列数,必须要知道,否则给定一串元素就不知道如何划分行列数,这样才会方便运算。// 情况 3
void test(int arr[][5])
正确
【小结】:
二维数组传参,函数形参只能省略第一个[]
的数字,第二个[ ]中的数字不可省略。
对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
// 情况一
void test(int (*arr)[5])
正确
// 情况二
void test(int* arr[5])
不正确
// 情况三
void test(int *arr)
不正确
// 情况四
void test(int **arr)
不正确
【小结】:二维数组可以作为 数组指针 传参。
常见的传参方式如下:
void print(int *p, int sz){
int i = 0;
for(i = 0;i < sz;i++){
printf("%d\n",*(p + i));
}
}
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
print(p,sz); //一级指针p,传给函数print
return 0;
}
Q:那么当一个函数的参数部分为一级指针的时候(例如int *
),函数能接收什么参数?
int*
类型的一级整型指针int
的一维数组常见的传参方式如下:
void test(int** ptr){
printf("num = %d\n", **ptr);
}
int main(){
int n = 10;
int *p = &n;
int **pp = &p;
test(pp); //二级指针pp传给函数test
test(&p);
return 0;
}
Q:当函数的参数是二级指针时(例如int **
),可以接收什么参数?