964
社区成员
这是我参加朝闻道知识分享大赛的第八篇文章
在计算机科学中,指针是编程语言的一个对象,利用地址,它的值直接指向存在于电脑存储器中的另一个地方的值。简单来说,指针就是通过它能找到以它为地址的存储单元。在计算机内存中,每一个存储单元(字节)都有自己的位置编号,即内存地址,就如同地球上的每一片土地都有自己的经纬度。
在C程序中,每一个操作对象(object)也有自己的位置编号。C程序创建、销毁、访问和操作对象。这些对象由声明、分配函数、字符串文字、复合文字以及返回结构或与数组成员联合的非左值表达式创建。在寻常意义上,一般称此为变量,变量名(标识符)起到映射内存地址的作用。
指针式内存中一个最小单元的编号,也就是地址。平时所说的指针,通常指的就是指针变量,用来存放内存地址的变量。指针的大小在32位平台是4个字节,在64位平台是8个字节。
指针变量的大小决定了它能存储多大的地址。对于32位的机器,假设有32根地址线,而每根地址线能产生高电压(1)和低电压(0),那么32根地址线就能产生2^32个0/1二进制序列。每个编号占用32个比特位,即4个字节。指针变量的大小在32位平台是4个字节,在64位平台是8个字节。
指针的类型决定了指针在解引用时访问的权限有多大(一次能操作几个字节)。例如:
char*
的指针解引用访问1个字节。float*
的指针解引用访问4个字节。指针的类型也决定了指针向前向后走一步有多大(+/-整数时跨越几个字节)。例如:
char*
的指针+1,跳过1个字节。int*
的指针+1,跳过4个字节。无具体类型指针(泛型指针),可以接受任意类型的地址。void*
类型的指针就是无具体类型的指针,它不能直接进行解引用和指针+-整数的运算,但一般使用在函数参数的部分,用来接收不同类型数据的地址,这样的设计可实现泛型编程的效果,使用一个函数来处理多种类型的数据。
const
修饰指针:
int *p;
:无const
修饰。const
放在*
的左边,修饰的是指针指向的内容,使指针指向的内容不能通过指针来改变,但是指针变量本身的内容可以变,即指针变量存放的地址是可以改变的。const
放在*
的右边,修饰的是指针变量本身,使指针变量的内容不能改变,即该指针变量只能存放一个固定变量的地址,但是指针指向的内容,可以通过指针来改变。*
的左右两边都有const
,此时指针变量的内容不能改变,指针变量指向的内容也不能改变。野指针就是未初始化的指针,即指向的位置不可知(随机的、不正确、没有明确限制的)。野指针的成因主要有以下几种:
int main() {
int *pa;
*pa = 20; // 在未初始化的指针,其中的值也是随机,程序不会正常执行。
printf("%d", *pa);
return 0;
}
指针指向的空间不合理,比如一个10个元素整型的数组,访问第11个元素的整型值,那么第十一个元素的值就是随机值。
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *pa = arr;
int i = 0;
for (i = 0; i <= 10; i++) {
printf("%d ", *pa);
pa++;
}
return 0;
}
子函数中局部变量的内存在return之后就已经返还给内存了,再次访问的就是野指针。
int* test() {
int a = 10;
return &a;
}
int main() {
int *pa = test();
printf("%d\n", *pa);
return 0;
}
NULL
。NULL
。NULL
指针就不去访问,同时使用指针之前可以判断指针是否为NULL
。指针的运算包括:指针+-整数、指针-指针、指针的关系运算(指针的大小比较)。
float arr[5] = {1.0, 2.0, 3.0, 4.0, 5.0};
float *pa = &arr[0];
for (pa = &arr[0]; pa < &arr[5];) {
*pa++ = 0; // 将数组arr中的元素全部置0。
}
int main() {
int arr[10] = {0};
printf("%d", &arr[9] - &arr[0]);
return 0;
}
得出的结果是9,反过来是-9。
数组名表示的是数组首元素的地址。p+i
计算的是数组arr[i]
的地址。因此可以通过指针来访问数组。
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p = arr; // 指针p存放数组arr首元素的地址
通过指针+-整数就可以实现对数组每个元素的访问。
凡是变量就有地址,而指针变量也是变量,因此也有地址,这个地址可以用二级指针来存放,即存放指针变量的地址的二级指针变量。
int a = 0;
int *pa = &a;
int **ppa = &pa;
*ppa
通过对ppa
中存放的地址(即pa
的地址)解引用,访问pa
。**ppa
通过对*ppa
中存放的地址(对ppa
解引用得到pa
的内容,而pa
的内容是a
的地址)解引用,访问a
。即先通过*ppa
找到pa
,然后对pa
解引用*pa
,找到a
。
字符指针有两种使用方式:
char ch = 'a';
const char *pc = "hello world";
// 常量字符串直接赋值给字符指针变量,常量字符串的值不能被改变,因此加一个const
更好。直接把一个常量字符串放到字符指针中,实质上是将字符串首字符的地址存放到字符指针pc
中。
八、指针数组
指针数组是存放指针的数组。
int arr1[5]; // 存放了5个整型变量的数组
char arr2[5]; // 存放了5个字符型变量的数组
int *arr3[5]; // 存放了5个指针变量的数组
数组指针存放数组的地址的指针,指向的是数组。
int arr[5];
int (*p)[5] = &arr;
数组指针和指针数组的区别:
int *arr[5];
// arr[5]
是一个数组,数组的类型为int*[5]
,数组的元素是指针,而且是指向整型的指针变量。int (*parr2)[10] = &arr;
// parr2
是数组指针,该指针指向的是一个数组,该数组有10个元素,每个元素是int
类型的。函数指针是指向函数的指针,可以用来存储函数的地址。通过函数指针,可以间接地调用函数。
函数指针的定义:
返回类型 (*指针变量名)(参数类型);
例如:
int (*func_ptr)(int, int); // 定义一个指向返回类型为int,接受两个int参数的函数的指针
函数指针的赋值:
int add(int a, int b) {
return a + b;
}
int main() {
int (*func_ptr)(int, int);
func_ptr = add; // 将add函数的地址赋值给