C语言中的指针


指针 即 指向某一个 变量 的内存地址

指针变量的定义

定义指针时,建议变量名为 p 开头

1
2
int a = 10, b = 20, *pa, *pb = &b;
pa = &a; // 注意不是 *pa = &a

此处 pa 为先声明后赋值,pb 为声明时赋值
声明后但未赋值的为野指针,禁止对其存取,因为其被随机指向了某一个内存空间

间接访问符

* 用来声明指针的同时,也是间接访问符
对于指针 int *pint a = 6
p = &a
此刻 p 为 a 的内存地址,*p 则等同于 a,均为 6

指针与数组

指针变量+1 表示 跳过该指针变量对应的基类型所占字节数大小的空间
当指向数组时,其基类型为数组元素类型,指针变量+1表示跳过一个数组元素空间,指向下一个数组元素
数组名 a 相当于数组首元素 a[0] 的地址,即 a 等价于 &a[0]
例一:依次将输入的数据填入数组,并依次打印输出

1
2
3
4
5
6
7
8
9
10
11
12
int a[5], *p, i;
p = a;
cout << "请依次输入数组元素:" << endl;
for (i = 0; i < 5; i++)
{
cin >> *p++;
}
cout << "数组的每个元素为:" << endl;
for (p = a; p < a + 5; p++)
{
cout << *p << endl;
}

指针与二维数组

二维数组的存储结构为顺序形式,即二维数组中的数据元素在内存中的存储地址是连

访问数组元素的方法:

  • 直接访问:a[i][j]
  • 间接访问:*(*(a+i)+j)

数组指针和指针数组

数组指针

数组指针,即指向一维数组的指针,是指向含 N 个元素的一维数组的指针

1
int (*p)[5];

一维数组指针 p,该指针 p 只能指向含 5 个元素的整型数组
注:不要漏掉括号,下标运算符[] 比 *运算符 优先级高,p 首先与下标运算符 [] 相结合,说明 p 为数组,该数组中有 5 个元素,每个为 int * 型。即 p 为指针数组。

指向二维数组

1
2
int a[M][N];
int (*p)[N]=a;

两者变换关系

1
2
3
4
*(i 行首地址)=i 行首元素地址

0 行首地址:p + 0 <--> a + 0
1 行首地址:p + 1 <--> a + 1

当定义一个指向一维数组的指针 p,并初始化为二维数组名 a 时,即 p=a,用该指针访问元素 a[i][j] 的两种形式 ((p + i) + j) 与 ((a + i) + j) 非常相似,仅把 a 替换成了 p 而已。
数组指针指向的是一整行,故数组指针每加 1 表示跳过一行

指针数组

即存储指针的数组,数组中的每个元素均是同类型的指针

1
int *a[5]

指针数组最主要的用途是处理字符串,c语言中没有 字符串类型,定义字符串时常需要这样

1
2
3
char a[] = "sorry";

char a[] = {'s', 'o', 'r', 'r', 'y', '\0'}; // '\0'为字符串终止符,不使用会在sorry后打印乱码

在 C 语言中,一个字符串常量代表返回该字符串首字符的地址(字符串其实就是字符数组,联想理解数组),即指向该字符串首字符的指针常量,因此根据上式子得。

1
2
3
4
char a[] = "sorry";
char *p = a; // 此处 a 是指针,指向第一个字符 s 的地址,参考数组
cout << a << endl;
cout << p << endl;

打印结果均为 sorry

由于指针数组的每个元素都是指针变量,因此,可以通过指针数组定义字符串数组:

1
char * c[4]={"if","else","for","while"};

通过c[i]来获取每个字符串

指针与字符串

字符串指针常量

上节已知,字符串常量 “wdnmd” 为一个指针常量,指向第一个字符 w 的地址,打印字符串指针为**从指针地址开始直到遇到’/0’**,同理char a[] = "wdnmd",a 也是指针常量
类比数组的指针特性,可得:

1
2
3
4
char a[] = "abcd" 
cout << "abcd" + 1 << endl; // 打印bcd
cout << a + 1 << endl; // 打印bcd
cout << a + 2 << endl; // 打印cd

注:’/0’为字符串终止符,遇到即为字符串结束,详细见最后一节

既然字符串是个指针,那么可以通过间接访问的方式获取具体某一个字符

1
2
3
4
char a[] = "abcd"
cout << *(a + 1) << endl; // 打印b
cout << *(a + 4) << endl; // 打印空字符,为'/0'
*(a + 5) // 已越界,禁止使用,其值不确定

##¥¥ 字符串指针变量

通过字符指针变量可访问所指向的字符串常量,但仅限于读取操作

1
2
3
char *pc="abcd";
pc="hello"; // 正确,修改pc指向,让其指向串"hello"
*(pc+4) = 'p'; // 运行时错误。试图把'o'字符改变为'p'

字符串变量

字符数组可以理解为若干个字符变量的集合,如果一个字符串存放在字符数组中,那么字符串中的每个字符都相当于变量,故可把存放在字符数组中的字符串称为变量字符串

1
char str[10] = "wdnmd";

定义字符数组并显示指定其大小(10),前六个空间分别为有效字符’w’ ‘d’ ‘n’ ‘m’ ‘d’ ‘\0’,第七个往后为’\0’填充

也可以不定义长度

1
char str[] = "wdnmd";

自动为其分配6个空间,5个有效字符和结束符’/0’

允许使用数组下标方式改变数组中的元素,如

1
str[1] = 'r'; // 改为 "wrnmd"

字符串结束符

上文已多次提到,不再赘述,注意几点:

  1. 定义确定长度的变量字符串时,长度一定要考虑到’/0’
  2. 逐个字符定义字符数组时,如char a[] = {'a', 'b'},一定要手动在最后加一个’/0’,否则输出a为乱码甚至程序崩溃
  3. 定义char a[3] = ""时,默认填充3个’/0’,此时手动给第2、3个元素赋值依然为空字符串,因为第1个元素为’/0’,自动终止

函数与指针

函数的两种调用方式

  1. 传值调用:传入的实参为固定值,函数内部将此值拷贝一份副本到形参变量,对副本进行操错,不会影响传入的数据,较大的数据时创建副本开销较大
  2. 传址调用:传入的实参为地址,形参为能接受地址的地址箱即指针变量,通过该地址间接访问要处理的数据,能在函数内改变函数外的数据,不涉及创建副本,开销较小

指针函数

即为返回类型为指针的函数,常用于字符串处理函数,格式

1
2
3
4
类型*函数名(形参列表)
{
... /*函数体*/
}

函教指针

即指向函教的指针,C语言中,整型变量在内存中占一块内存空间,该空间的起始地址称为整型指针,可把整型指针保存到整型指针变量中。函数像其他变量一样,在内存中也占用一块连续的空间,把该空间的起始地址称为函数指针。而**
函数名就是该空间的首地址**,故函数名是常量指针。可把函数指针保存到函数指针变量中。

函数指针的定义

注意不要省略括号,省略为声明函数原型

1
2
3
4
返回类型(*指针变量名)(函数参数表);

int *pf (int, int);//声明了一个函数原型,函数名为pf,含两个int型参数,且该函数返回类型为整型指针类型,即int*。
int (*pf) (int, int); //定义了一个函数指针变量pf,可以指向任意含有两个整型参数、且返回值为整型的函数。

下面定义了一个函数:

1
2
3
4
int wdnmd(int a, int b) 
{
...
}

该函数符合 函数指针pf的条件,可以让pf指向此函数

1
2
pf=&func; //正确
pf=func; //正确。也可省略&
通过函数指针调用函数

如下,声明一个函数原型,定义函数指针并指向原型

1
2
int f(int a)
int (*pf) (int) = &f

当函数指针被初始化指向函数后,有三种调用函数方式

1
2
3
4
int result;
result=f(2); //正确。编译器会把函数名转换成对应指针
result=pf(2); //正确。直接使用函数指针
result=(*pf)(2); //正确。先把函数指针转换成对应函数名

函数调用时,编译器把函数名转换为对应指针形式,故前两种调用方式含义一样.而第三种调用方式,*pf 转换成对应的函数名 f(),编译时,编译器还会把函数名转换成对应指针形式,从这个角度来理解,第三种调用方式走了些弯路。

函数指针通常主要用于作为函数参数的情形。

参考