C语言:指针

指针的概念
系统给虚拟内存的每个存储单元分配了一个编号,从0x00000000 ~ 0xffffffff ,这个编号称之为地址
指针变量:用来存放一个地址的变量
作用 :可以通过指针来间接访问内存
每定义一个变量,程序会向计算机申请一个内存空间,空间的大小取决于数据类型
1 |
|
运行结果
64位int类型指针占用内存:8
float类型指针占用内存:8
double类型指针占用内存:8
char类型指针占用内存:8
32位int类型指针占用内存:4
float类型指针占用内存:4
double类型指针占用内存:4
char类型指针占用内存:4
扩展在32位操作系统下,指针占用4byte内存,在64位操作系统下,指针占用8byte内存
指针变量的定义
语句:数据类型 * 变量名
eg.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(){
// 定义一个指针
int num1 = 10;
// 指针的定义语法: 数据类型 *变量名
// 指针的数据类型需要和变量的数据类型相同
int *p;
// &取地址符
p = &num1;
printf("num1的地址是:%p\n",&num1);
printf("指针p是:%p\n",p);
return 0;
}运行结果(不同设备运行结果不同,但两个地址结果一定相同)
num1的地址是:000000000061FE14
指针p是:000000000061FE14请按任意键继续. . .
指针的解引用
语法:*指针名称
作用:通过指针找到对应的内存地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(){
// 定义一个指针
int num1 = 10;
// 指针的定义语法: 数据类型 *变量名
// 指针的数据类型需要和变量的数据类型相同
int *p;
// &取地址符
p = &num1;
// 使用指针
// 可以通过解引用的方式来找到指针指向的内存
// 指针前面加一个*表示解引用
printf("指针p指向的内存的值为:%d\n",*p);
*p = 100; // 可以通过修改指针p指向内存的值来修改变量的值
printf("现在num1的值:%d\n",num1);
printf("现在指针p指向的内存的值是:%d\n",*p);
return 0;
}
运行结果
指针p指向的内存的值为:10
现在num1的值:100
现在指针p指向的内存的值是:100请按任意键继续. . .
指针与数组元素
我们定义的数组,就是多个相同类型变量的集合,,每个元素都有对应的内存空间,都有对应的地址,因此,指针也可以存放数组元素的地址。
eg.
1
2
3
4
5
6
7
8
int main(){
int a[5];
// 定义指针变量p,保存a[0]的地址
int *p = &a[0];
return 0;
}
应用(数组元素的引用)
数组名[下标]
1
2int a[5];
a[2] = 100;指针加下标
- c语言规定:数组的名字就是数组的首地址,即第0个元素的地址,就是&a[0] 。
1
2
3
4int a[5];
int *p;
p = a;
p[2] = 100; // 等同于a[2] = 100;! ! ! p和a的不同,p是指针变量,而a是个常量。所以可以用等号给p赋值,但不能给a赋值
1
2p = &a[0]; // 正确
a = &a[0]; // 错误通过指针变量运算加取值的方法来引用数组的元素
- p是数组的第0个元素,相当于a[0],那么p + 2就相当于a[2]
1
2
3
4int a[5];
int *p;
p = a;
*(p + 2) = 100; // 等同于a[2] = 100;通过数组名+取值的方法引用数组的元素
- 由于C语言中数组的名字也是一个指针,因此我们可以通过如下方式引用数组中的元素
1
2int a[5];
*(a+2)=100; // 等同于a[2]=100;
指针的运算
指针加整数,往下指变量(仅指针指向数组时有意义)
1
2
3
4int a[5];
int *p;
p=a;
p+2; // p是&a[0],p+2是&a[2]两个相同类型指针比较大小(仅两个相同类型指针变量指向同一数组时有意义)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(){
int a[10];
int *p,*q;
p = &a[0];
q = &a[6];
printf("%d\n%d\n",p,q);
/* 6421984
6422008
请按任意键继续. . .
两个结果前面的指针小于后面的指针,由于整型为4byte,因此两者间相差24 */
printf("%d\n",q - p); // 6 即两个指针指向的之间有多少个元素
return 0;
}两个相同类型的指针做减法(仅两个相同类型指针变量指向同一数组时有意义)
1
2
3
4
5
6
7
8
9
10
int main(){
int a[10];
int *p,*q;
p = &a[0];
q = &a[6];
printf("%d\n",q - p); // 6 即两个指针指向的之间有多少个元素
return 0;
}两个相同类型的指针可以相互赋值(void*类型除外)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(){
int a[10];
int *p,*q;
p = &a[0];
q = &a[6];
// 两个相同类型的指针可以互相复制,例如下面的例子中,q保存了p的地址也指向a
p = a;
q = p;
// 编译运行的结果是p和q指向的同一个地址
printf("%p\n%p\n",p,q);
return 0;
}
指针数组
概念:指针数组是若干个相同类型的指针变量构成的集合(数组)
语法:数据类型 * 数组名 [元素个数];
eg.
1
2
3
4
5
6
7
8
9
int main(){
char *chr[6] = {"China","America","Russia","French","Btitain","Welcome"};
for(int i = 0;i < 6;i++){
printf("%s\n",chr[i]);
}
return 0;
}运行结果
China
America
Russia
French
Britain
Welcome
指针的指针
概念:指针变量的地址
eg.
1
2
3
4
5
6
7
8
9
10
11
int main(){
// 指针的指针
int a = 0;
int *p = &a;
int **q = &p; // q保存了p地址
return 0;
}理论上,我们可以创建指针的指针的指针乃至更多,无限套娃
指针与字符串
- 字符串的概念:字符串就是以’\0’结尾的若干的字符的集合(数组)
- 字符串的地址:即字符串中第一个字符的地址,例如字符串“helloworld”中字符串的地址即为字符“h”的地址
1 | char *s = "helloworld"; |
数组指针
概念:指向数组的地址
语法:指向数组的数据类型(*指针变量名)[指向数组的元素个数]
eg.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(){
int b[2][3] = {1,2,3,4,5,6};
// 这里二维数组需要两次解引用才能的到b[0][0] 具体原因见下方补充说明
printf("b = %d\n",**b);
printf("b + 1 = %d\n",b[0][0]);
int (*t)[3];
printf("b = %p\n",b);
printf("b + 1 = %p\n",b + 1);
t = b;
printf("t = %p\n",t);
printf("t + 1 = %p\n",t + 1);
return 0;
}运行结果
b = 1
b + 1 = 1
b = 000000000061FDD0
b + 1 = 000000000061FDDC
t = 000000000061FDD0
t + 1 = 000000000061FDDC补充说明
这里二维数组b的数组名实际上指向的是该二维数组中的第一个一维数组的地址,b[2] [3]可以具象化为一个2*3的表格,*b得到的是第0行的地址,**b得到的就是第0行第0列的元素的地址了
各种数组指针的定义
一维数组指针,加1即为下一个一维数组
如 int (*p)[5]; ,配合每行有5个int型元素的二维数组如 int a[3] [5] 、int b[4] [5]、 int c[5] [5]、int d[6] [5]
二维数组指针,加1即为下一个二维数组
如 int (*p)[4] [5]; 配合三维数组来用。三维数组中由若干个4行5列二维数组构成,如int a[3] [4] [5];、 int b[4] [4] [5];、int c[5] [4] [5];、 int d[6] [4] [5]; 这些三维数组,有个共同的特点,都是有若干个4行5的二维数组构成。
eg.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
int a[3][4][5];
printf("a=%p\n", a);
printf("a+1=%p\n", a + 1);// a 和 a+1 地址编号相差 80 个字节
// 验证了a+1 跳一个4行5列的一个二维数组
int(*p)[4][5];
p = a;
printf("p=%p\n", p);
printf("p+1=%p\n", p + 1);//p 和 p+1 地址编号相差也 80 个字节
return 0;
}
数组名字与指针变量的区别
相同点:a为数组的名字,是a[0]的地址,p = a则p保存了a[0]的地址,两者都指向a[0],所以在引用数组时两者等价
1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
int a[3] = {0,1,2};
int* p;
p = a;
printf("a[2] = %d\n", a[2]); // 2
printf("*(a + 2) = % d\n", *(a + 2)); // 2
printf("p[2] = %d\n", p[2]); // 2
printf("*(p + 2) = % d\n", *(p + 2)); // 2
return 0;
}不同点:a作为数组的名字,它是一个常量,值永远是a[0]的地址;而p则为指针变量,可以为其赋值。对a取地址相当于数组指针,而对p取地址则为指针的指针。
指针与函数
指针传参
- eg.
1
2int num;
scanf("%d",&num);- 可以将变量的地址传入函数,在被调函数中来修改主调函数中的变量(局部变量)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void swap(int *p,int *q){
int temp;
temp = *p;
*p = *q;
*q = temp;
}
int main(){
int a = 10,b = 20;
swap(&a,&b);
printf("a = %d,b = %d",a,b); // a = 20,b = 10
return 0;
}指针传数组
- 传一维数组
1
2
3
4
5
6
7
8
9
10
11
12
void printArray(int *p){
printf("%d\n",p[2]); // 3
printf("%d\n",*(p + 3)); // 4
}
int main(){
int a[5] = {1,2,3,4,5};
printArray(a);
return 0;
}- 传二维数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// int(*p)[3] 将二维数组的三个一维数组依次传入
void printArray(int(*p)[3]){
// 通过数组调用
printf("%d\n",p[1][1]); // 5
// 通过指针调用
printf("%d\n",*(*(p + 1) + 2)); // 6
}
int main(){
int a[2][3] = {1,2,3,4,5,6};
printArray(a);
return 0;
}- 传指针数组
1
2
3
4
5
6
7
8
9
10
11
12
13
void printChar(char **p){
for(int i = 0;i < 3;i++){
printf("%s\n",p[i]);
}
}
int main(){
char *p[3] = {"Hello","World","Programe"};
printChar(p);
return 0;
}指针作返回值
一个函数可以返回整型数据、字符数据、浮点型的数据,也可以返回一个指针。
这里返回的是fun函数里面局部变量str的地址,函数运行结束,内存就被释放了,返回这个地址,也没有意义了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char* fun() {
char str[100] = "hello world";
return str;
}
int main()
{
char* p;
p = fun();
printf("%s\n", p); // 乱码
return 0;
}- 在函数里面给局部变量加static修饰,因为静态数组的内容,在函数结束后,仍然存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char* fun() {
static char str[100] = "hello world";
return str;
}
int main()
{
char* p;
p = fun();
printf("%s\n", p); // hello world
return 0;
}函数指针
- 概念:定义一个指针变量,来存放函数的地址
- 语法:返回值类型(*函数指针变量名)(形参列表);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int add(int a,int b){
return a + b;
}
int main(){
int (*p)(int,int);
p = add;
// 通过函数名调用
printf("%d\n",add(1,2));
// 通过函数指针调用
printf("%d\n",(*p)(1,2));
return 0;
}函数指针数组
- 由若干个相同类型的函数指针变量构成的集合,在内存中连续的顺序存储。函数指针数组是个数组,它的每个元素都是一个函数指针变量
- 语法:类型名(*数组名[元素个数])(形参列表)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int max(int a,int b){
if(a > b){
return a;
}else{
return b;
}
}
int min(int a,int b){
if(a < b){
return a;
}else{
return b;
}
}
int add(int a,int b){
return a + b;
}
int main(){
int (*p[3])(int,int) = {max,min,add};
printf("%d\n",(*p[2])(4,8)); // 12
return 0;
}
特殊指针
空类型指针void *
- void * 并不是指向void类型的变量,而是一种通用指针,任何数据类型的地址都可以为其赋值
1
2
3int *p;
void *q;
q=p // 是可以的,不用强制类型转换空指针NULL
- p哪里都不指向,用于指针的初始化
1
int *p = NULL;
main 函数传参
- int argc:argc 表示命令行参数个数(argument count),包括程序本身,即 argc 最小为1
- char *argv[]:使用argument value来记忆,参数值的意思,argv[] 是一个指向字符串数组的指针,其中每个元素是一个指向传递给程序的参数的指针(argument vector),这些字符串是命令行参数。
1 |
|
无参数运行结果
argc=1
argv[0]=test.exe
1 | test.exe abc 123 |
有参数运行结果
argc=3
argv[0]=test.exe
argv[1]=abc
argv[2]=123












