1.指针初阶
内存地址
- 字节:字节是内存的容量单位,英文称为 byte,一个字节有8位,即 1byte = 8bits
- 地址:系统为了便于区分每一个字节而对它们逐一进行的编号,称为内存地址,简称地址,系统通过对应的内存地址从而定位内存的位置
基地址
- 单字节数据:对于单字节数据而言,其地址就是其字节编号。
- 多字节数据:对于多字节数据而言,期地址是其所有字节中编号最小的那个,称为基地址。
取址符
- 每个变量都是一块内存,都可以通过取址符 & 获取其地址
- 例如:
int a = 100;
printf("整型变量 a 的地址是: %p\n", &a);
char c = 'x';
printf("字符变量 c 的地址是: %p\n", &c);
double f = 3.14;
printf("浮点变量 f 的地址是: %p\n", &f);
-
注意:
- 虽然不同的变量的尺寸是不同的,但是他们的地址的尺寸确实一样的,在32为系统为4字节,64位系统为8字节
- 不同的地址虽然形式上看起来是一样的,但由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的。
指针基础
-
指针的概念:
- 一个专门用来存放内存地址的变量,指针也就是指针变量
-
地址。比如 &a 是一个地址,也是一个指针,&a 指向变量 a
- 专门用于存储地址的变量,又称指针变量。
-
格式
- 类型 *指针变量名
- 解释:
- “类型” : 指针变量指向的内存空间存放的数据类型
- “指向” : 如果我保存了你的地址,那么就说我指向你
- “*” :定义指针变量的固定格式
// 系统中给a申请了4个字节的内存空间 int a = 10; printf("a addr:%p\n",&a); // 定义一个指针变量用于存放a的地址 int *p = &a; // 第一部分:*p :首先p是一个变量,占用内存8个字节,存放了a的地址 // 第二部分:int 指的是指针变量所指向的内存空间放了什么类型的数据 printf("p的值:%p addr : %p\n",p,&p); printf("a的值:%d addr : %p\n",a,&a);
-
指针的定义:
// 用于存储 int 型数据的地址,p1 被称为 int 型指针,或称整型指针
int *p1;
// 用于存储 char 型数据的地址,p2 被称为 char 型指针,或称字符指针
char *p2;
// 用于存储double型数据的地址,p3 被称为 double 型指针
double *p3;
- 指针的赋值:赋给指针的地址,类型需跟指针的类型相匹配。
int a = 100;
p1 = &a; // 将一个整型地址,赋值给整型指针p1
char c = 'x';
p2 = &c; // 将一个字符地址,赋值给字符指针p2
double f = 3.14;
p3 = &f; // 将一个浮点地址,赋值给浮点指针p3
- 指针的索引:通过指针,取得其指向的目标
*p1 = 200; // 将 p1 指向的目标(即a)修改为200,等价于 a = 200;
*p2 = 'y'; // 将 p2 指向的目标(即c)修改为'y',等价于 c = 'y';
*p3 = 6.6; // 将 p3 指向的目标(即f)修改为6.6,等价于 f = 6.6;
-
指针的尺寸
- 指针尺寸指的是指针所占内存的字节数
- 指针所占内存,取决于地址的长度,而地址的长度则取决于系统寻址范围,即字长
- 结论:指针尺寸只跟系统的字长有关,跟具体的指针的类型无关
- 在32位系统,指针的大小占用4字节
- 在64位系统,指针的大小占用8字节
// 指针大小 int a = 10; char b = 'c'; float c = 85.5; char *p1 = &b; int *p2 = &a; float *p3 = &c; printf("p1 size : %ld\n",sizeof(p1)); printf("p2 size : %ld\n",sizeof(p2)); printf("p3 size : %ld\n",sizeof(p3));
野指针
- 概念:指向一块未知区域的指针,被称为野指针。野指针是危险的。
- 危害:
- 引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault)
- 引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果
- 产生原因:
- 指针定义之后,未初始化
- 指针所指向的内存,被系统回收
- 指针越界
- 如何防止:
- 指针定义时,及时初始化
- 绝不引用已被系统回收的内存
- 确认所申请的内存边界,谨防越界
#include <stdio.h>
int main(int argc, char const *argv[])
{
// 野指针,如果定义定义指针没有指向空间,则称为野指针
// 野指针是危险的,所以定义指针的时候如果不确定指向对应的空间
// 则可以先指向NULL
//int *p;// 野指针
int *p = NULL; // NULL表示(void *)0
return 0;
}
空指针
很多情况下,我们不可避免地会遇到野指针,比如刚定义的指针无法立即为其分配一块恰当的内存,又或者指针所指向的内存被释放了等等。一般的做法就是将这些危险的野指针指向一块确定的内存,比如零地址内存。
- 概念:空指针即保存了零地址的指针,亦即指向零地址的指针。
- NULL地址其实就是 (void *)0,就是0
- 示例:
// 1,刚定义的指针,让其指向零地址以确保安全:
char *p1 = NULL;
int *p2 = NULL;
// 2,被释放了内存的指针,让其指向零地址以确保安全:
char *p3 = malloc(100); // a. 让 p3 指向一块大小为100个字节的内存
free(p3); // b. 释放这块内存,此时 p3 相当于指向了一块非法内存
p3 = NULL; // c. 让 p3 指向零地址
demo:
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a = 10;
printf("%p\n",&a);
// 定义一个指针变量p存放a的地址
int *p = &a;
printf("%p\n",p);
char ch = 'a';
char *p1 = &ch;
float f = 3.14;
float *p2 = &f;
printf("%p,%p\n",p1,p2);
printf("%d,%d\n",*(&a),*p);
printf("%c,%c\n",*(&ch),*p1);
printf("%f,%f\n",*(&f),*p2);
*p = 100;
*p1 = 'b';
*p2 = 5.86;
printf("%d,%d\n",*(&a),*p);
printf("%c,%c\n",*(&ch),*p1);
printf("%f,%f\n",*(&f),*p2);
// 在32位系统里面,所有类型的指针都是4字节
// 在64位系统里面,所有类型的指针都是8字节
printf("%ld,%ld,%ld\n",sizeof(p),sizeof(p1),sizeof(p2));
// 野指针,pstr里面的地址没有申请空间,所以不能直接使用
// 用的过程中有隐患
char *pstr;
*pstr = 'c';
printf("%c\n",*pstr);
// 野指针不用的时候要指向NULL
char *pstr1 = NULL;
*pstr1 = 'c';
printf("%c\n",*pstr1);
return 0;
}
指针运算
- 指针加法意味着地址向上移动若干个目标
- 指针减法意味着地址向下移动若干个目标
- 示例:
int a = 100;
int *p = &a; // 指针 p 指向整型变量 a
int *k1 = p + 2; // 向上移动 2 个目标(2个int型数据)
int *k2 = p - 3; // 向下移动 3 个目标(3个int型数据)
char ch = 'a';
char *p1 = &ch;
p1+1;
p1+2;
- 指针运算
int a = 10;
//int *p = &a;// 初始化
int *p;
// 其它时候用来表示 *单独使用为解引用,所以 *p = &a; 错误
p = &a;
printf("a value:%d\n",a);
printf("*(&a) value:%d\n",*(&a));// * 与 &是互逆运算
printf("%d\n",*p);
*p = 300; //*p 此时相当于变量a
printf("a value:%d\n",a);
-
指针偏移
- 指针的加减称为指针的偏移,偏移的单位由指针的类型大小所决定所谓指针的类型大小指的是指针变量所指向的内存空间的数据类型
-
数组与指针
-
数组名有两个含义
- 第一个含义,表示整个数组
printf("%ld\n",sizeof(a));
- 第二个含义,表示首元素地址
int *p = a;
-
数组下标
int a[3] = {1,2,3}; int b = a[0]; int c1 = *(a+0); // a[0] int c2 = *(a+1); // a[1] int c3 = *(a+2); // a[2] int d1 = *(0+a); // a[0] int d2 = *(1+a); // a[1] int d3 = *(2+a); // a[2] printf("2[a] = %d\n",2[a]); // *(2+a) 仅限面试用 int a[3] = {1,2,3}; printf("%d,%d,%d,%d\n",a[0],*(a+0),*(0+a),0[a]); // *的作用就是[]相当于解引用 printf("%d,%d,%d,%d\n",a[1],*(a+1),*(1+a),1[a]); return 0;
总结:数组最后编译器会自动转为指针操作,数组运算其实就是指针运算
-
-
指针转数组
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a[5] = {1,2,3,4,5};
// 数组名相当于这个数组的首元素地址
int *p = a;
printf("%d,%d,%d,%d\n",p[0],*p,*(p+0),0[p]);
printf("%d,%d,%d,%d\n",p[1],*(p+1),*(1+p),1[p]);
printf("%d,%d,%d,%d\n",p[2],*(p+2),*(2+p),2[p]);
// b和pb是等价的,可以通过pb修改b的值
int b = 10;
int *pb = &b;
// 将b的值赋给c,但是不能通过c修改b的值
int c = b;
return 0;
}
练习1:
int array[5] = {10,20,30,40,50};
要求定义一个指针p存储数组的名字,通过数组名或者指针将所有获取到40数据的方法罗列出来
int array[5] = {10,20,30,40,50};
int *p = array;
printf("%d,%d,%d,%d,%d\n",*(&array[0]+3),*(array+3),*(3+array),array[3],3[array]);
printf("%d,%d,%d,%d,%d\n",*(&p[0]+3),*(p+3),*(3+p),p[3],3[p]);
总结 :
1.指针一定要指向一块合法的空间,否则出现段错误,没有空间自行分配
2.可以将指针转换成数组使用,如上所示
- 指针偏移量
int a[2] = {1,2};
int *p0 = &a[0];
printf("p0 addr:%p:%d,p0+1 addr:%p:%d\n",p0,*p0,p0+1,*(p0+1));
char *p = (char *)&a[0];
printf("p addr:%p:%d,p+1 addr:%p:%d\n",p,*p,p+1,*(p+1));
char b[5] = {'a','b','c','d','e'};
int *q = (int *)&b[0];
printf("%c\n",*(q+1));
练习3:
有这么一个十六进制数据int data = 0x11223344,定义指针指向data
然后通过指针把22打印出来
-
字节序
- 大端模式:高位数据存放在内存的低地址端,低位数据存放在内存的高地址端
- 小端模式:高位数据存放在内存的高地址端,低位数据存放在内存的低地址端
练习4:判断linux系统到底是大端模式存放数据,还是小端模式存放数据
#include <stdio.h> int main(int argc, char const *argv[]) { char ret; int data = 0x11223344; char *p = (char *)&data; printf("%x\n",ret = *(p+2)); printf("%x\n",ret); if(*p == 0x44) { printf("小端模式\n"); } else { printf("大端模式\n"); } return 0; }
作业
输入字符串ch,并作出以下修改 1.输入a或者A,输出* 2.如果输入b或者B,输出# 3.其它数据保持不变,如果字符串第一个字符ch[0]为x,则退出
#include <stdio.h> #include <string.h> int main(int argc, char const *argv[]) { //char buf[100] = {0}; //scanf("%s",buf); // sizeof是计算整个空间尺寸 // strlen是计算有效字符串的大小,就是实际用的空间,不算\0 //printf("%ld,%d\n",sizeof(buf),strlen(buf)); while(1) { // 定义数组初始化为0,防止随机数产生 char buf[100] = {0}; printf("input str : "); // 千万不要给数组添加& //scanf("%s",buf); gets(buf); // 判断第一个元素是否是x if(buf[0] == 'x') { break; } //找位置替换 for(int i = 0; i < strlen(buf); i++) { if(buf[i] == 'a' || buf[i] == 'A') { buf[i] = '*'; } if(buf[i] == 'b' || buf[i] == 'B') { buf[i] = '#'; } } printf("buf : %s\n",buf); } return 0; }
编写一个程序,清除用户输入的字符串空格,并输出,例如输入"a b",输出"ab"; //aaaaaa-->a /* 解题思路: 1.输入字符串存到数组gets或者getchar 2.找到空格,后面的把空格覆盖 */ #include <stdio.h> #include <string.h> int main(int argc, char const *argv[]) { char buf[100] = {0}; int len = 0; // 记录字符长度 while((buf[len++] = getchar()) != '\n'); // 去掉'\n'改为'\0' buf[--len] = '\0'; printf("len:%d\n",len); printf("len : %d\n",strlen(buf)); for(int i = 0; i < len; i++) { // 找空格 if(buf[i] == ' ') { for(int j = i; j < len; j++) { buf[j] = buf[j+1]; } } } printf("%s\n",buf); return 0; }
拓展题 : 华为笔试题:字符串去重
-
数组与指针转换
- 数组指针
数组的指针的本质为指针,此指针保存的是数组的地址,说白了就是,指针指向数组名,此类指针称为数组指针
int arr[3][4] = {{1,2,3,4},{10,20,30,40},{11,22,33,44}};
定义一个指针p存储数组的名字//数组指针 int a = 5;
int (*p)[4] = arr;
通过arr p 将所有获取到1的数据的方法罗列出来
通过arr p 将所有获取到20的数据方法罗列出来
int (*p)[4] = arr;
int (*q)[3][4] = &arr;
int arr[3][4] = {
{1,2,3,4},
{10,20,30,40},
{11,22,33,44}
};
//定义一个指针p存储数组的名字//数组指针
int (*p)[4] = arr;
//通过arr p 将所有获取到1的数据的方法罗列出来
printf("arr[0][0] = %d\n",p[0][0]);
printf("arr[0][0] = %d\n",(*(p+0))[0]);
printf("arr[0][0] = %d\n",*((*(p+0))+0));
printf("arr[0][0] = %d\n",**p);
//通过arr p 将所有获取到20的数据方法罗列出来
printf("arr[1][1] = %d\n",p[1][1]);
printf("arr[1][1] = %d\n",(*(p+1))[1]);
printf("arr[1][1] = %d\n",*((*(p+1))+1));
练习2:
int array[3][5] = {
{10,20,30,40,50},
{11,22,33,44,55}
{110,120,130,140,150}
};
要求定义一个指针p存储数组的名字,通过数组的名字或者指针 将 所有获取到11数据的方法都罗列出来
#include <stdio.h>
int main(int argc, char const *argv[])
{
int array[3][5] = {
{10,20,30,40,50},
{11,22,33,44,55},
{110,120,130,140,150}
};
// 定义数组指针
int (*ptr)[5] = array;
printf("%d\n",ptr[1][0]);
printf("%d\n",(*(ptr+1))[0]);
printf("%d\n",*(*(ptr+1))+0);
return 0;
}
demo:
#include <stdio.h>
int main(int argc, char const *argv[])
{
char buf[100] = "jack";
char *p = buf;
printf("%s\n",p);
char str[3][5] = {"jack","rose","ken"};
// 把数组名去掉,剩下的就是类型
char (*ptr)[5] = str;
printf("%s\n",ptr[2]);
int a[2][2][2] = {{{1,2},{3,4}},{{5,6},{7,8}}};
int (*q)[2][2] = a;
printf("%d\n",a[1][0][1]);
return 0;
}
- 指针数组
概念:
指针的数组,的本质为数组,数组里面存放的内容为指针,而一般指针是指向的地址为字符串居多,我们把此类型称为指针数组
定义:
char *p[5];
char *p = "jack";
char *p[5] = {"jack","rose","ken","tony","tom"};
printf("%s\n",p[2]);
printf("%c\n",p[3][3]);
"hello"是字符串常量,同时"hello"也是一个匿名数组的空间首地址
char *str[3] = {"abc","def","hij"};
指针的数组的本质为数组,数组里面存放的是地址,一般是指向(存放)字符串首元素的地址,
char str1[4] = {"abc"};
char str2[4] = {"abc"};
char str3[4] = {"abc"};
//同类型不同名字的变量可以用数组来同意存放管理
//上方有3个字符串,所以申请一个数组存放3个字符串,字符串的类型是char
char *str_buf[3] = {str1,str2,str3};
总结:
#include <stdio.h>
int main(int argc, char const *argv[])
{
char array[3][5] = {"jack","rose","ken"};
// ()优先级高,所以p的本质是指针,指向一个char [5]类型的数据
// 指针会更加灵活,用的场景比较多
char (*p)[5] = array;
printf("%s\n",p[1]);
// []优先级比较高,所以ptr本质是数组,数组里面存放的是指针,指针指向字符串常量
char *ptr[3] = {"jack","rose","ken"};
printf("%s\n",ptr[1]);
return 0;
}
-
字符串与指针
-
字符串常量在内存中实际就是一个匿名数组
-
匿名数组满足数组的两个条件
1.第一个含义,表示整个数组
char a[10]; sizeof(a);
2.第二个含义,表示首元素地址
char buf[] = "abcd"; printf("%c\n",buf[1]); printf("%c\n","abcd"[1]); char *p = "abcd"; // 将p指向一块匿名数组的一个首地址 printf("%p,%p\n","abcd",&"abcd"[0]);
// 讲"jack"字符串存放在buf的空间里面 char buf[] = "jack"; // 定义指针p指向buf的首地址 char *p = buf; // 定义指针q指向"jack"的首地址 char *q = "jack"; printf("%s,%s,%s,%d\n",buf,p,q,sizeof(q));
#include <stdio.h> int main(int argc, char const *argv[]) { // 定义数组,讲jack赋值 // buf里面存放的是jack char buf[5] = "jack"; // 定义指针p指向buf // buf的空间是变量,所以可以通过p去修改 char *p = buf; *(p+1) = 'b'; printf("%s\n",p); // q存放的是"rose"的地址; // "rose"的空间是常量,不能通过q修改 char *q = "rose"; //*(q+1) = 'a'; // 错误的 return 0; }
-
常见问题
- 问:老师,数组是不是就是地址?
- 答:有时候是,有时候不是。在C语言中非常重要的一点是:同一个符号,在不同场合,有不同的含义。
比如数组int a[3];
当出现在以下三种情形中的时候,它代表的是一块12字节的内存: - 初始化语句时:
int a[3];
- 与
sizeof
结合时:sizeof(a)
- 与取址符
&
结合时:&a
,a
只有在上述三种情形下,数组a
代表一片连续的内存,占据12个字节,而在其他任何时候,数组a
均会被一律视为其首元素的地址。
因此,不能武断地说数组是不是地址,而要看它出现的场合。
- 问:老师,指针不是地址吗?为什么还可以取地址?地址的地址是什么意思?
- 答:你这个疑惑是典型的概念混淆。首先需要明确,指针通常指指针变量,是一块专用于装载地址的内存,因此指针跟别的普通变量没什么本质区别,别的变量可以取地址,那么指针变量当然也可以取地址。
int main(int argc, char const *argv[])
{
int a = 10;
int *p = &a;
int **q = &p;
printf("%d\n",**q);
return 0;
}
笔试题
将一个字符串逆序输出 “abcd” ——》 dcba
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char str[5] = "abcd";
char temp[5] = {0};
int len = strlen(str);
for(int i = 0,j = len-1; j >= 0; i++,j--)
{
temp[i] = str[j];
}
printf("%s\n",temp);
return 0;
}
2.指针进阶
char型指针
char型指针实际上跟别的类型的指针并无本质区别,但是由于C语言中的字符串以字符数组的方式存储,而数组在大多数场合又会表现为指针,因此字符串在绝大多数场合就表现为char型指针。
- 定义:
char *p = "abcd";
多级指针
- 如果一个指针变量p1存储的地址,是另一个普通变量a的地址,那么称p1为一级指针
- 如果一个指针变量p2存储的地址,是指针变量p1的地址,那么称p2为二级指针
- 如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址,那么称 p3 为三级指针
- 以此类推,p2、p3等指针被称为多级指针
- 示例:
int a = 100;
int *p1 = &a; // 一级指针,指向普通变量
int **p2 = &p1; // 二级指针,指向一级指针
int ***p3 = &p2; // 三级指针,指向二级指针
指针万能拆解法
- 任意的指针,不管有多复杂,其定义都是由两部分组成
- 第一部分:指针所指向的数据类型,可以是任意类的类型
- 第二部分:指针的名字
- 示例:
char (*p1); // 第2部分:*p1; 第1部分:char;
char *(*p2); // 第2部分:*p2; 第1部分:char *;
char **(*p3); // 第2部分:*p3; 第1部分:char **;
char (*p4)[3]; // 第2部分:*p4; 第1部分:char [3];
char (*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float);
- 注解:
- 上述示例中,p1、p2、p3、p4、p5本质上并无区别,它们均是指针
- 上述示例中,p1、p2、p3、p4、p5唯一的不同,是它们所指向的数据类型不同
- 第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边
void型指针
- 概念:无法明确指针所指向的数据类型时,可以将指针定义为 void 型指针
- 要点:
- void 型指针无法直接索引目标,必须将其转换为一种具体类型的指针方可索引目标
- void 型指针无法进行加减法运算
- void关键字的三个作用:
- 修饰指针,表示指针指向一个类型未知的数据。
- 修饰函数参数列表,表示函数不接收任何参数。(预习)int main(void)
- 修饰函数返回类型,表示函数不返回任何数据。(预习)void func(int)
- 示例:
// 指针 p 指向一块 4 字节的内存,且这4字节数据类型未确定
void *p = malloc(4);
// 1,将这 4 字节内存用来存储 int 型数据
*(int *)p = 100;
printf("%d\n", *(int *)p);
// 2,将这 4 字节内存用来存储 float 型数据
*(float *)p = 3.14;
printf("%f\n", *(float *)p);
// a 为不确定类型
//void a = 10;
int data = 10;
// 定义通用类型指针p指向data的地址,但是p的类型不确定
void *p = &data;
// 输出的时候要确定p的类型,所以需要强制类型转换,否则报错
printf("%d\n",*(int *)p);
void类型总结:
void 一般运用在指针或者,函数的返回值,或者函数的参数传递,不能用在非指针的定义,比如 void a = 10;错误
const 型指针
- const型指针有两种形式:①常指针 ②常目标指针
- 常指针:const修饰指针本身,表示指针变量本身无法修改。
char* const p;
- 常目标指针:const修饰指针的目标,表示无法通过该指针修改其目标。
const int *p;
int const *p;
- 常指针在实际应用中不常见。
- 常目标指针在实际应用中广泛可见,用来限制指针的读写权限
- 示例:
int a = 100;
int b = 200;
// 第1中形式,const修饰p1本身,导致p1本身无法修改
int * const p1 = &a;
// 第2中形式,const修饰p2的目标,导致无法通过p2修改a
int const *p2 = &a;
const int *p2 = &a;
函数指针(预习:函数)
- 概念:指向函数的指针,称为函数指针。
- 特点:函数指针跟普通指针本质上并无区别,只是在取址和索引时,取址符和星号均可省略
- 示例:
void f (int); // 函数 f 的类型是: void (int)
void (*p)(int); // 指针 p 专门用于指向类型为 void (int) 的函数
p = &f; // p 指向 f(取址符&可以省略)
p = f; // p 指向 f
// 以下三个式子是等价的:
f (666); // 直接调用函数 f
(*p)(666); // 通过索引指针 p 的目标,间接调用函数 f
p (666); // 函数指针在索引其目标时,星号可以省略
-
要点:
- 函数指针是一类专门用来指向某种类型函数的指针。
- 函数的类型不同,所需要的函数指针也不同。
- 函数的类型,与普通变量的类型判定一致,即去除声明语句中的标识符之后所剩的语句。
-
问:老师,一维数组会被视为一级指针,那二维数组是不是等价于二级指针?
-
答:
这种说法是错误的,不能这么简单的类推,实际上,二维数组与二级指针没有任何必然关系。
任何数组a,除了定义语句和sizeof表达式之外,都会被一律视为一个指向其首元素的指针,因此,除非该数组的元素类型本身恰好是指针,即一维指针数组在运算时会被视为二级指针,但题目中提到的二维数组,其元素显然应该是一维数组,因此二维数组在运算时会被视为指向数组的指针,即数组指针。
例如:
int a[3];
// 此处的a等价于 &a[0],而a[0]是一个int,因此此处a的类型是 int (*)
int *p = a+1;
int a[2][3];
// 此处的a等价于 &a[0],而a[0]是一个int[3],因此此处a的类型是 int (*)[3]
int (*p)[3] = a+1;
int *a[3];
// 此处的a等价于 &a[0],而a[0]是一个int *,因此此处a的类型是 int (*)*
int **p = a+1;
*函数指针(预习:函数)**
- 概念:指向函数的指针,称为函数指针。
- 特点:函数指针跟普通指针本质上并无区别,只是在取址和索引时,取址符和星号均可省略
- 示例:
```c
void f (int); // 函数 f 的类型是: void (int)
void (*p)(int); // 指针 p 专门用于指向类型为 void (int) 的函数
p = &f; // p 指向 f(取址符&可以省略)
p = f; // p 指向 f
// 以下三个式子是等价的:
f (666); // 直接调用函数 f
(*p)(666); // 通过索引指针 p 的目标,间接调用函数 f
p (666); // 函数指针在索引其目标时,星号可以省略
-
要点:
- 函数指针是一类专门用来指向某种类型函数的指针。
- 函数的类型不同,所需要的函数指针也不同。
- 函数的类型,与普通变量的类型判定一致,即去除声明语句中的标识符之后所剩的语句。
-
问:老师,一维数组会被视为一级指针,那二维数组是不是等价于二级指针?
-
答:
这种说法是错误的,不能这么简单的类推,实际上,二维数组与二级指针没有任何必然关系。
任何数组a,除了定义语句和sizeof表达式之外,都会被一律视为一个指向其首元素的指针,因此,除非该数组的元素类型本身恰好是指针,即一维指针数组在运算时会被视为二级指针,但题目中提到的二维数组,其元素显然应该是一维数组,因此二维数组在运算时会被视为指向数组的指针,即数组指针。
例如:
int a[3];
// 此处的a等价于 &a[0],而a[0]是一个int,因此此处a的类型是 int (*)
int *p = a+1;
int a[2][3];
// 此处的a等价于 &a[0],而a[0]是一个int[3],因此此处a的类型是 int (*)[3]
int (*p)[3] = a+1;
int *a[3];
// 此处的a等价于 &a[0],而a[0]是一个int *,因此此处a的类型是 int (*)*
int **p = a+1;