指针基础
指针基础
概念
指针(pointer)是 指向(point to)另外一种类型的复合类型。指针实现了对其他对象的间接访问。从根本上看,指针是一个值为内存地址的变量(或数据对象)。正如char类型变量的值是字符,int类型变量的值是整数,指针变量的值是地址。
创建指针变量:
int *p; // 指向int类型的指针
char *p; // 指向char类型的指针
double *p1, *p2; // 连续声明
为指针变量赋值:
int a = 100;
int *p = &a;
此时指针p指向a, 通过取地址符&将a的地址存入p中。
然后使用间接运算符*(indirection operator)找出储存在a中的值,该运算符有时也称为解引用运算符(dereferencing operator)。后跟一个指针名或地址时,*给出储存在指针指向地址上的值。
// p == &a
// *p == a
printf("%d\n", *p);
根据间接运算符的定义, 我们也可以这样输出a的值:
printf("%d\n", *&a);
printf("%d\n", a);
先取a的地址, 然后根据a的地址来进行间接运算得出储存在该地址上的值。
细节及原理
众所周知, c语言的变量声明都是这样声明:
int a;
double b;
char c;
为啥指针偏要这么奇特, 就不能使用 pointer p
来声明吗?
因为声明指针变量时必须指定指针所指向变量的类型,不同的变量类型占用不同的存储空间,一些指针操作要求知道操作对象的大小。另外,程序必须知道储存在指定地址上的数据类型。long和float可能占用相同的存储空间,但是它们储存数字却大相径庭。故需要用 [类型] *[变量名]
的格式来声明。
pc指向的值(*pc)是char类型。pc本身是什么类型?
我们描述它的类型是“指向char类型的指针”。pc 的值是一个地址,在大部分系统内部,该地址由一个无符号整数表示。但是,不要把指针认为是整数类型。一些处理整数的操作不能用来处理指针,反之亦然。例如,可以把两个整数相乘,但是不能把两个指针相乘。所以,指针实际上是一个新类型,不是整数类型。
既然指针是一个类型, 那么它肯定也有自己的地址和值, 如何输出?
int a = 100;
int *p = &a;
printf("地址:%p\n", &p);
printf("值:%p\n", p);
printf("a的地址:%p\n", &a);
指针运算
对指针进行加减时, 会对地址进行相加减 对应类型字节大小 的倍数:
int a = 100;
int *p = &a;
p + 1 即是 对p的值进行加 4 (int类型字节大小为4)
short a = 100;
short *p = &a;
p - 1 即是 对p的值进行减 2 (short类型字节大小为2)
这一特性为数组的实现提供了基础。
指针应用
指针与数组
在介绍数组的时候, 通常都说是一系列数的集合, 而这个集合的组织方式其实就是利用指针。
#define N 1000
int a[N];
该语句会声明一个长度为1000的int类型数组。在早些年间, C语言并不支持这么写, 必须自己手动创建内存空间, 然后用指针来声明:
#define N 10
int *a = (int *)malloc(N*sizeof(int));
int i;
for(i = 0; i < N; i++)
scanf("%d", a + i);
for(i = 0; i < N; i++)
printf("%d ", *(a + i)); // * 的优先级比+高
上述代码即是创建一个大小为N个int类型的连续内存, 并将头地址赋值给指针a。
假如输入从1到10输入十个数, 他们的地址如下所示:
而在后来为了便利和安全性的考虑, 引入了现在的数组声明方式, 在编译前就确定好数组大小, 然后再创建内存并用a[1]
来代替a+1
的写法, 且方便了初始化操作。
注意, 这里是代替, 也就是说 a[i]
和 *(a + i)
是等价的, 实际上指针写法仍可以使用:
#define N 10
int a[N] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n",a[1]);
printf("%d\n", *(a + 1));
不过这样就有了新的问题, 如果数组长度是根据用户输入来决定, 那么就得在编译前就声明一个很大的数组确保不会溢出, 会浪费很多空间。而利用指针, 可以声明可变长数组, 使用变量来定义数组的大小:
int n, i;
scanf("%d", &n);
int *a = (int *)malloc(n*sizeof(int));
for(i = 0; i < n; i++)
scanf("%d", &a[i]);
for(i = 0; i < n; i++)
printf("%d ", a[i]);
如今C语言也在逐渐发展, 若你现在直接用变量来声明数组大小, 其实也是可以的。
int n;
scanf("%d", &n);
int a[n]; // 并不会报错, 而且可以正常使用
这与很多教科书教的有很大出入, 其实是因为在C99标准下, 被引入了可变长度数组(Variable-length array ,VLA), 与手动利用malloc
来创建是等价的。不过这样的话使用数组的初始化操作会出现问题:
int n;
scanf("%d", &n);
int a[n] = {1,2,3,4,5,9};
在C语言下这么写会报错, 无法定义。而在C++中当n小于6(即初始化的数量)时程序会报错异常, 但大于等于6是可以正常使用的, 多余部分会被填充0。
指针与函数
如何实现在函数内交换主函数的变量呢?
因为默认传参都是传值, 即会创建新的变量然后把传入的值赋值给它。我们可以通过传入原地址, 然后通过指针间接操作来修改:
void swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
怎么传递数组呢?
同理, 只需要传递数组的头指针就行:
void print_arr(int *a, int n)
{
int i;
for(i = 0; i < n; i++)
printf("%d ", a[i]);
}
不过这样感觉区分不出来是数组还是正常变量, C语言提供了另一种写法:
void print_arr(int a[], int n)
{
int i;
for(i = 0; i < n; i++)
printf("%d ", a[i]);
}
作业
-
动态声明一维数组并进行输入输出
-
实现动态声明二维数组, 并实现在函数内输出二维数组, 函数头为:
void print_arr(int n, int m, int a[n][m])
- 实现交换函数