指针是什么?
- 指针是C语言的一个重要概念,也是C语言的一个重要特色。正确而灵活地运用它,可以使程序简洁、紧凑、高效。每一个学习和使用C语言的人,都应当深入的学习和掌握指针。可以说,不掌握指针就是没有掌握C的精华。
- 在计算机科学中,指针(pointer)是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为指针。意思是通过它能找到以它为地址的内存单元。
我们可以这样理解:
指针是个变量,存放内存单元的地址(编号)。
代码演示
#include <stdio.h>
#include <stdlib.h>
int main(){
int a = 10;
int* pa = &a;
system("pause");
return 0;
}
总结:指针就是变量,用来存放地址的变量。(存放在指针中的值都被当做地址处理)。
一个小的单元到底是多大?
1个字节。
如何编址?
- 对于32位机器,假设有32根地址线,那么假设每根地址线在寻址时产生一个电信号(1/0),那么32根地址线就可以产生2^32个地址。
- 每个地址标识一个字节,那我们就可以给(2^32B = 2 ^22K = 2^12M = 2^2G)4G的空间进行编址。
- 同样的方法,64位机器就可以对(2^64B = 2^54K = 2^44M = 234G)234G的空间进行编址。
- 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针的变量的大小就应该是4个字节。
- 如果在64位机器上,如果有64根地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结:
- 指针是用来存放地址的,地址是唯一标识一块地址空间的。
- 指针的大小在32位平台是4个字节,在64位平台是8个字节。
指针和指针类型
我们都知道,变量有不同的类型:整型、浮点型、字符型等。那指针有没有类型呢?准确的说是有的。
char* pc = NULL;
int* pi = NULL;
short* ps = NULL;
long* pl = NULL;
float* pf = NULL;
double* pd = NULL;
这里可以看到,指针的定义方式是:type + \*
。其实:int*类型的指针是为了存放int类型变量的地址。double*类型的指针是为了存放double类型的变量的地址。
指针类型的意义?
指针±整数
代码演示
#include <stdio.h>
int main(){
int a = 1;
int* pi = &a;
char* pc = (char*)&a;
printf("&a: %p\n", &a);
printf("pc: %p\n", pc);
printf("pc + 1: %p\n", pc + 1);
printf("pi: %p\n", pi);
printf("pi + 1: %p\n", pi + 1);
return 0;
}
运行结果
[sss@aliyun pointer]$ gcc ptr.c -o ptr
[sss@aliyun pointer]$ ./ptr
&a: 0x7ffe10e978fc
pc: 0x7ffe10e978fc
pc + 1: 0x7ffe10e978fd
pi: 0x7ffe10e978fc
pi + 1: 0x7ffe10e97900
总结:指针的类型决定了指针向前或者向后走一步有多大。
指针的解引用
代码演示
#include <stdio.h>
int main(){
int a = 257;
int* pi = &a;
char* pc = (char*)&a;
printf("*pi: %d\n", *pi);
printf("*pc: %d\n", *pc);
return 0;
}
运行结果
[sss@aliyun pointer]$ !gcc
gcc ptr_dereference.c -o ptr_dereference
[sss@aliyun pointer]$ ./ptr_dereference
*pi: 257
*pc: 1
总结:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。比如:char*类型的指针解引用只能访问一个字节,而int*类型的指针解引用就能访问四个字节。
指针和数组
指针和数组名
在数组章节我们打印了数组的每个元素的地址,我们知道了数组在内存中是连续存放的。
现在我们研究一下:数组名,数组和指针。
代码演示
#include <stdio.h>
int main(){
int arr[10] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
printf("arr: %p\n", arr);
printf("&arr[0]: %p\n", &arr[0]);
return 0;
}
运行结果
[sss@aliyun pointer]$ gcc arr_and_ptr.c -o arr_and_ptr
[sss@aliyun pointer]$ ./arr_and_ptr
arr: 0x7ffe0757a960
&arr[0]: 0x7ffe0757a960
结论:数组名表示的是数组首元素的地址。
指针和数组
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个数组就成为可能。
代码演示
#include <stdio.h>
int main(){
int arr[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
int* p = arr;
int len = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for(; i < len; ++i){
printf("&arr[%d] = %p <==> p + %d = %p\n",
i, &arr[i], i, p + i);
}
return 0;
}
运行结果
[sss@aliyun pointer]$ !gcc
gcc arr_and_ptr.c -o arr_and_ptr
[sss@aliyun pointer]$ ./arr_and_ptr
&arr[0] = 0x7ffc0fc1efd0 <==> p + 0 = 0x7ffc0fc1efd0
&arr[1] = 0x7ffc0fc1efd4 <==> p + 1 = 0x7ffc0fc1efd4
&arr[2] = 0x7ffc0fc1efd8 <==> p + 2 = 0x7ffc0fc1efd8
&arr[3] = 0x7ffc0fc1efdc <==> p + 3 = 0x7ffc0fc1efdc
&arr[4] = 0x7ffc0fc1efe0 <==> p + 4 = 0x7ffc0fc1efe0
&arr[5] = 0x7ffc0fc1efe4 <==> p + 5 = 0x7ffc0fc1efe4
&arr[6] = 0x7ffc0fc1efe8 <==> p + 6 = 0x7ffc0fc1efe8
&arr[7] = 0x7ffc0fc1efec <==> p + 7 = 0x7ffc0fc1efec
&arr[8] = 0x7ffc0fc1eff0 <==> p + 8 = 0x7ffc0fc1eff0
&arr[9] = 0x7ffc0fc1eff4 <==> p + 9 = 0x7ffc0fc1eff4
由上述结果可知,p + i其实计算的就是数组arr下标为i的地址。
那我们就可以直接通过指针来访问数组。
指针访问数组演示
#include <stdio.h>
int main(){
int arr[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
int* p = arr;
int len = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for(; i < len; ++i){
printf("%d ", *(p + i));
}
printf("\n");
return 0;
}
运行结果
[sss@aliyun pointer]$ !gcc
gcc arr_and_ptr.c -o arr_and_ptr
[sss@aliyun pointer]$ ./arr_and_ptr
0 1 2 3 4 5 6 7 8 9
指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
整型数组,字符数组,指针数组
int arr1[5];
char arr2[5];
int* arr3[5];
二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里呢?这就是二级指针。
代码演示
#include <stdio.h>
int main(){
int a = 1;
int* pa = &a;
int** ppa = &pa;
printf("&a: %p\n", &a);
printf("pa: %p\n", pa);
printf("&pa: %p\n", &pa);
printf("ppa: %p\n", ppa);
return 0;
}
运行结果
[sss@aliyun pointer]$ gcc secondary_ptr.c -o secondary_ptr
[sss@aliyun pointer]$ ./secondary_ptr
&a: 0x7ffe178ff684
pa: 0x7ffe178ff684
&pa: 0x7ffe178ff678
ppa: 0x7ffe178ff678
指针运算
指针加减整数
代码演示
#include <stdio.h>
int main(){
int a = 1;
char* pc = (char*)&a;
int* pi = &a;
double* pd = (double*)&a;
printf("char*: \n");
printf("pc + 1: %p\n", pc + 1);
printf("pc: %p\n", pc);
printf("pc - 1: %p\n", pc - 1);
printf("int*: \n");
printf("pi + 1: %p\n", pi + 1);
printf("pi: %p\n", pi);
printf("pi - 1: %p\n", pi - 1);
printf("double*: \n");
printf("pd + 1: %p\n", pd + 1);
printf("pd: %p\n", pd);
printf("pd - 1: %p\n", pd - 1);
return 0;
}
运行结果
[sss@aliyun pointer]$ gcc ptr_add_sub.c -o ptr_add_sub
[sss@aliyun pointer]$ ./ptr_add_sub
char*:
pc + 1: 0x7fffb3aca5e5
pc: 0x7fffb3aca5e4
pc - 1: 0x7fffb3aca5e3
int*:
pi + 1: 0x7fffb3aca5e8
pi: 0x7fffb3aca5e4
pi - 1: 0x7fffb3aca5e0
double*:
pd + 1: 0x7fffb3aca5ec
pd: 0x7fffb3aca5e4
pd - 1: 0x7fffb3aca5dc
总结:指针加减一个整数,结果与指针类型有关,指针加减1相当于地址加减该类型字节数。
指针加减指针
指针加指针
代码演示
注意:指针加法非法。
指针减指针
代码演示
#include <stdio.h>
int main(){
int arr[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
printf("&arr[3] - &arr[1]: %p\n", &arr[3] - &arr[1]);
printf("&arr[3] - &arr[1]: %d\n", &arr[3] - &arr[1]);
return 0;
}
运行结果
[sss@aliyun pointer]$ !gcc
gcc ptr_sub.c -o ptr_sub
[sss@aliyun pointer]$ ./ptr_sub
&arr[3] - &arr[1]: 0x2
&arr[3] - &arr[1]: 2
总结:指向同一个数组元素的指针相减表示中间有多少个元素。否则没有意义。
指针比较
代码演示
#include <stdio.h>
int main(){
int arr[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
int* p = arr;
for(; p < &arr[9]; ++p){
printf("%d ", *p);
}
printf("\n");
return 0;
}
运行结果
[sss@aliyun pointer]$ gcc ptr_compare.c -o ptr_compare
[sss@aliyun pointer]$ ./ptr_compare
0 1 2 3 4 5 6 7 8
标准规定:允许指向数组元素的指针域指向数组最后一个元素后面的那个内存位置比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。