目录
指针的概念:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4.指针的运算,(指针-指针)的绝对值得到的是指针和指针之间的元素个数。
一、字符指针
字符指针除了赋值变量外,还可以赋值常量。
int main()
{
char* p = "hello";
printf("%s\n", p);
printf("%c", *p);
return 0;
}
这里的char* p与const char* p意思一样,"hello"这个常量也是在内存中开辟了一块空间,是不可改的。p里面放的也是"hello"的首字符的地址。
做道题来看一下字符指针、数组和常量字符之间的关系
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
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会把常量字符串存储到单独的一个内存区域。当几个指针,指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候,数组就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。
二、数组指针
先来看看指针数组和数组指针的区别
int* p1[10];//指针数组
int (*p2)[10];//数组指针
因为[]的优先级比*高,p1是先和[10]结合说明它是一个数组有10个元素,元素类型是int*;p2是先和*结合说明p2是一个指针,然后指向10个整型的数组(int[10])。所以p1是指针数组,p2是数组指针。
存放一维数组:
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;
p存放的是数组的地址&arr,不能赋值arr,arr是首元素地址类型是int,&arr是数组地址类型是int[10]40个字节。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int(*p)[10] = &arr;
printf("%p\n", p);
printf("%p\n", p + 1);
return 0;
}
一般不会这样写,数组指针通常用来传二维数组:
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
三、数组参数、指针参数
接下来我们来看几段代码,看看是否可以传参,什么原因
1.一维数组
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int* arr)//ok?
{}
void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
都是可也传参的。
test:arr传递的是数组首元素的地址,地址需要一个指针来接收。int arr[]是个数组,但它做参数时就是个指针,只是方便给初学者阅读可以写成数组;int arr[10]也同样它是个指针[]里面的值可以随便写;int* arr是个指针自然可以。
test2:arr2它的元素类型是int*,传递的地址是int*类型。int* arr[20]和arr2的类型相同是可以传参的;int** arr他的类型是int*也可以接受arr2。
2.二维数组
void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
void test(int* arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int(*arr)[5])//ok?
{}
void test(int** arr)//ok?
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
二维数组的传参和创建是一样的,只能省略第一个[]的值,因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。前三个参数都为二维数组只有第二个参数它没有给第二个[]值,所以它不能传参。
二维数组的数组名传递的参数是第一行的地址,arr的类型是int[5]。int* arr的类型是int,不可传参;int* arr[5]是个指针数组,类型是int*,不可传参;int(*arr)[5]是数组指针,类型是int[5],可以传参;int** arr是二级指针,类型是int*,不可传参。
3.一级指针传参
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]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
根据上面我们可以总结,形参只需要和实参类型相同就可以传参。所以一级指针传参用一级指针传参就可以了。
这时我们就可以反推一下,形参是一级指针时可以传什么实参
void test(int* p)
{
}
实参类型也要和形参形同
int main()
{
int a = 10;
int* px = &a;
int arr[10] = { 0 };
test(arr);//整形一维数组的数组名
test(&a);//整形变量的地址
test(px);//整形指针
return 0;
}
4.二级指针传参
void test(char** p)
{ }
int main()
{
char c = 'b';
char* pc = &c;
char** ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);//Ok?
return 0;
}
&pc传的是一级指针的地址,类型是int*,可以传参;ppc本身就是二级指针,可以传参;char* arr[10]是指针数组,元素类型是char*,可以传参。
四、函数指针
函数指针就是指向函数的指针,自然会使用到函数的地址,我们可以先打印出来看一下
void test()
{
printf("hello\n");
}
int main()
{
printf("%p\n", &test);
printf("%p\n", test);
return 0;
}
这里函数名也是函数的地址
那函数的地址又如何保存呢
首先创建个这个指针要和函数的类型是一样的:
void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();
和数组指针一样要看优先级,()函数调用是比*的优先级高的。pfun1先和*结合表示它是个指针void()是它的类型,pfun2先和()结合说明它是函数,类型就是void*()。所以函数指针的写法是pfun1。
再来看一段代码:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int x = 10;
int y = 5;
int (*p)(int, int)=Add;
int ret = p(x, y);
printf("%d", ret);
return 0;
}
可以发现函数指针它的参数只需要写类型就可以,而在使用p时可以不写*,在上面可以看到Add本身就是地址,所以p写不写*都可以。在p前也可以写多个*和p的意思是一样的。
阅读两端有意思的代码:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
代码1:先从内向外看,void (*)()可以在里面个p,void (*p)(),这就可以看出它是个函数指针,而void (*)()是p的类型。这个类型加()放在0前面,是强制类型转换,这时0是函数指针,*0和0在前面说过意思是一样的。最后的()和void (*)()类型对应没有参数。
代码2:可以看出里还有一个函数指针signal,它的类型是int和void(*)(int)两个参数,函数指针的类型也可以作为参数出现,现在就剩下它的返回类型。将signal和它的类型拿出来剩下的是void(*)(int)函数指针类型,它的返回值就是void(*)(int)。
代码2很绕,我们来总结一下:这个代码是一次函数声明,声明的是signal,它的参数有两个,第一个是int,第二个是void(*)(int)该类型的参数是int,返回值是void。signal的返回类型也是void(*)(int)。
这里有人会想能不能这样写void(*)(int) signal(int,void(*)(int),这样c规则是不允许的。我们可以这样来:
typedef void(*pfun_t)(int);//这里pfun_t是必须放在*后的
pfun_t signal(int, pfun_t);