指针
指针作为C语言中极具代表性的特征之一,也是C语言学习中的一大难点。
简单来说,指针我们需了解的最基础的即:
- 指针是一个用来存放地址的变量,地址唯一标识一块内存空间。
- 指针的大小是固定的4/8个字节(32位平台/64位平台)。
- 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
指针与数组传参
了解指针与数组的传参,首先了解传值调用和传址调用的问题
传值调用:除了数组,其他数据实参均以直接拷贝,以传值形式调用
传址调用:数组传参时不可直接拷贝,需降维为指针;但数据也可以进行传址调用,只要在它前面加上取地址操作符即可
在讲述这个问题之前,我们先看以下这段代码:
int main()
{
char str1[] = "hello.";
char str2[] = "hello.";
char *str3 = "hello.";
char *str4 = "hello.";
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;
}
以上一段代码的输出结果为"str1 and str2 are not same
" ,"str3 and str4 are same
"
因为str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当多个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
指针和数组作为参数传入函数时,传入的是地址,即指向变量的地址和数组的首地址,可以在函数中改变指针或数组的值,但本质上还是值的传递(区别于变量的值传递的是:变量值传递不会改变实参原来的值。),我们无法对指针和数组的地址进行操作(如:地址赋值,分配内存等),要进行地址操作需要使用指针引用或二级指针。
一维数组传参
数组名作为函数参数传递时,传递数组首元素的地址,函数接收的实际为原参数的一份拷贝,使得函数操纵时不影响实际值
对于一维数组传参,有以下几种表示方法:
void test(int arr[])
void test1(int arr[10])
void test2(int *arr)
void test3(int *arr[20])
void test4(int **arr)
以上五种写法都是正确的
用数组接收传的是首元素地址,可以指定数组大小,也可以不指定,因为传到函数内的是地址,故可以用指针接收,第四种情况中,将一个指针数组的数组名传入,第五种则是传入指针数组数组名,代表首元素地址,首元素是一个指向数组的指针,再取地址,表示二级指针
调用函数时实际传递的是一个指针,即函数形参为指针,但数组名依然可被表示为数组首元素的地址被调用。两种调用明显用指针较为准确,因为实参实际是指针,而函数中一维数组可以不标明数组长度也是这个原因:函数没有给数组参数分配内存空间,形参指向已经开辟好的空间。指定数组长度,则数组作为一个显式参数传给数组
二维数组传参
二维数组的表示方法如下:
void test(int arr[3][5])
//void test(int arr[][])
void test(int arr[][5])
//二维数组传参,函数形参的设计只能省略第一个[]的数字
以上表示方法第二种有错误
从实参传递来的是数组的起始地址,在内存中按数组排列规则存放(按行存放),而并不区分行和列,如果在形参中不说明列数,则系统无法决定应为多少行多少列,不能只指定一维而不指定第二维,即多维数组传参要指定第二维或者更高维的大小,可以省略第一维的大小
一级、二级指针传参
一级指针传参和二级指针传参较为简单,传参后,对指针参数执行间接访问操作使函数修改原本变量的值,值得一提的是:
函数参数部分为一级指针时,函数可接收的参数有以下几种
- 一个整形指针
- 整型变量地址
- 一维整型数组数组名
函数参数部分为二级指针时,函数可接收的参数有以下几种
- 二级指针变量
- 一级指针变量地址
- 一维指针数组的数组名
- 二维数组的数组名
数组指针与指针数组
数组指针
定义
int (*p)[10];
//p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向
一个数组,叫数组指针。
数组指针的运用
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
int j;
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;
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;
}
如上述代码所示,两个函数作用都是遍历二维数组的各个元素,区别不大
指针数组
int *p[10];
//[]的优先级要高于*,说明p是一个数组,结合*,即存储指针的数组,叫指针数组
函数指针数组
函数指针
void *pfun();
pfun先和*结合,说明pfun是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
函数指针数组
把函数的地址存到一个数组中,这个数组叫函数指针数组
int (*parr[10])();
//[]的优先级要高于*,说明paar是一个数组,数组的内容为int (*)()
类型的函数指针
函数指针数组一般用于:转移表
举个例子:实现计算器操作的代码
普通写法
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
while (input)
{
scanf("%d", &input);
switch (input)
{
case 1:
scanf("%d %d", &x, &y);
ret = add(x, y);
break;
case 2:
scanf("%d %d", &x, &y);
ret = sub(x, y);
break;
case 3:
scanf("%d %d", &x, &y);
ret = mul(x, y);
break;
case 4:
scanf("%d %d", &x, &y);
ret = div(x, y);
break;
default:
printf("选择错误\n");
break;
}
printf("ret = %d\n", ret);
}
return 0;
}
利用函数指针数组
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入有误\n");
printf("ret = %d\n", ret);
}
return 0;
}
相较之下,简便了很多
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。举个例子:
void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
看到两道很有意思的题和大家分享一下,可以验证是否掌握了指针的大部分知识:
int main()
{
char *str[] = { "welcome", "to", "fortemedia", "nanjing" };
char **p = str + 1;
str[0] = (*p++) + 2;
str[1] = *(p+1);
str[2] =p[1] + 3;
str[3] = p[0]+(str[2]-str[1]);
printf("%s\n", str[0]); //
printf("%s\n", str[1]); //nanjing
printf("%s\n", str[2]); //jing
printf("%s\n", str[3]); //g
return 0;
}
进化版…
int main()
{
char *c[] = { "ENTER", "NEW", "POINT", "FIRST" };
char**cp[] = { c + 3, c + 2, c + 1, c };
char***cpp = cp;
printf("%s\n", **++cpp); //POINT
printf("%s\n", *--*++cpp + 3); //ER
printf("%s\n", *cpp[-2] + 3); //ST
printf("%s\n", cpp[-1][-1] + 1); //EW
return 0;
}
还有这两段代码的名称,可以尝试读一下(出自《C陷阱与缺陷》)
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);