C语言—指针

1 概念

1、指针就是地址,是内存中一个最小单元(字节)的编号
2、口语中的指针其实指的是指针变量,是用来存放内存地址的变量
3、指针变量里面存放的是地址,而通过这个地址,就可以找到一个内存单元
4、指针的大小:在32位平台是4个字节,64位平台是8个字节(x86–>32位环境,x64–>64位环境)

内存中每个字节都有一个编号,这个编号就叫做指针,也叫作地址。
专门用来存储指针(字节编号)的变量叫做指针变量。
只不过我们平时交流时,一般称呼:
地址:地址编号
指针:指针变量

2 指针相关的操作

“ & ”:取地址符,获取变量的地址
对于多字节的变量(每个字节在内存中都有编号),取地址取到的是首地址,也就是编号最小的那个(对于&,按位与为双目运算符,左右都要有; 而当作为取地址符的时候为单侧运算符,右侧有可以)
“ * ” :在定义指针变量时,*只起到一个标识作用,表示定义的是一个指针变量
在其他场景下,表示操作指针指向空间的内容。
int *p 其中给p赋值就相当于改变了指针的指向

3 指针和变量的关系

对于 int a 操作,内存会分配给a四个字节
在这里插入图片描述

4 指针的基本使用

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 //在程序执行到定义变量的语句时 会根据变量的类型
6 //分配对应大小的内存空间给变量使用
7 int a;
8 a = 10; //通过变量名 可以操作对应的内存空间
9
10 //通过 & 取地址符 可以查看变量的地址
11 //使用 %p 输出
12 printf("&a = %p\n", &a);
13
14 //指针变量可以用来保存变量的地址
15 //定义指针的格式 数据类型 *指针变量名;
16 //int *p1 = &a; //初始化的写法 也可以
17 int *p1;
18 p1 = &a;//指针保存了变量a的地址 我们一般称为 指针p1指向变量a
19 printf("p1 = %p\n", p1);
20
21
22 //指针指向变量之后 就可以通过指针操作变量对应的内存空间了
23 *p1 = 1314;
24 printf("*p1 = %d, a = %d\n", *p1, a);//1314 1314
25
26 //不能使用普通的变量来保存地址
27 //long value = &a;
28 //printf("value = %#lx\n", value);//保存是可以的 因为地址本质也是
整数
29 //*value = 520;//但是不能操作 因为普通变量不允许做 *操作
30
31 //指针只能保存已经分配了的地址 不能手动指定
32 //int *p2 = 0x12345678;
33 //printf("p2 = %p\n", p2);
34 //*p2 = 100;//错误是不可预知的
会产生段错误(常常是由于内存的非法访问)
35
36 //关于指针类型的作用 决定了从指针保存的地址开始 一共能操作多少个字节
   //不管什么样的指针,保存编号都只保存一个,也就是首地址
37 //char * 类型的指针只能操作 1个字节;
38 //int * 类型的指针 能操作 4个字节
   //short * 类型的指针 只能操作两个字节
   //所以一般情况下,我们都让指针的类型和指向的变量的类型保持一致
   //目的是为了让两者的操作空间大小一致
39
40 //定义了指针如果不初始化 里面保存的也是随机值
41 //这种指针指向是不确定的 我们称之为 野指针 是有害的
42 //int *p3;
43 //*p3 = 1234;//错误是不可预知的
44 //printf("*p3 = %d\n", *p3);
45
46 //如果定义指针时不知道用谁初始化 可以先用NULL来初始化
47 //这种指针我们称之为 空指针 对空指针取*操作一定是段错误
48 //段错误也比不可预知要好
49 //int *p4 = NULL;//NULL 本质就是 (void *)0
50 //*p4 = 1314;//段错误
51
52 //一行可以定义多个指针变量
53 //int *p5, p6;//这种写法 p5是指针 p6是普通int变量
54 //int *p5, *p6;//这种写法 p5 和 p6 才都是指针
55
56 //常量是没有地址可言的
57 //int *p7 = &100;
58
59 return 0;
60 }

5 指针变量的大小(大小不变)

32位系统中 指针的大小都是4字节
64位系统中 指针的大小都是8字节

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 char *p1;
6 short *p2;
7 int *p3;
8 long *p4;
9 long long *p5;
10 float *p6;
11 double *p7;
12 printf("sizeof(char *) = %ld, sizeof(p1) = %ld\n", sizeof(char
*), sizeof(p1));
13 printf("sizeof(short *) = %ld, sizeof(p2) = %ld\n", sizeof(sho
rt *), sizeof(p2));
14 printf("sizeof(int *) = %ld, sizeof(p3) = %ld\n", sizeof(int
*), sizeof(p3));
15 printf("sizeof(long *) = %ld, sizeof(p4) = %ld\n", sizeof(long
*), sizeof(p4));
16 printf("sizeof(long long *) = %ld, sizeof(p5) = %ld\n",
sizeof(long long *), sizeof(p5));
17 printf("sizeof(float *) = %ld, sizeof(p6) = %ld\n", sizeof(flo
at *), sizeof(p6));
18 printf("sizeof(double *) = %ld, sizeof(p7) = %ld\n", sizeof(do
uble *), sizeof(p7));
19
20 return 0;
21 }

6 指针的运算

指针的运算本质就是指针中保存的地址量作为运算量来参与运算。
既然是地址的运算,那么能做的运算就有限了。(地址编号之间的乘除没有任何意义)
相同类型的指针之间做运算才有意义。
只有连续空间时 指针之间的运算才有意义。
指针能做的运算
算数运算:+ - ++ –
关系运算:> < == !=
赋值运算:=

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 //只有连续空间时 指针之间的运算才有意义
6 int s[5] = {10, 20, 30, 40, 50};
7
8 //一个指针加上一个整数n 表示加上n个指针的类型的大小
9 int *p1 = &s[0];
10 int *p2 = p1+2;   //谁做运算,就按照谁的类型(这里是按p1)
11 printf("p1 = %p, p2 = %p\n", p1, p2);//相差8字节 == 2*sizeof(in
t)
12 printf("*p1 = %d\n", *p1);//10
13 printf("*p2 = %d\n", *p2);//30
14
15 //指针的强转 是安全的 因为指针的大小都是一样的
16 char *p3 = (char *)&s[0];
17 char *p4 = p3+2;
18 printf("p3 = %p, p4 = %p\n", p3, p4);//相差2字节 == 2*sizeof(ch
ar)
19
20 //两个指针做差
21 //得到的结果是相差的指针的类型的个数
22 //int s[5] = {10, 20, 30, 40, 50};
23 int *p5 = &s[0];
24 int *p6 = &s[3];
25 int ret = p6‐p5;
26 printf("ret = %d\n", ret);//3 表示 p6和p5之间相差 3个int
   short *p7  =(short *)&s[0];
   short *p8  =(short *)&s[3];
   ret = p8-p7;
   printf("ret = %d\n",ret); //6 表示相差六个sizeof(short)
27 //自增自减运算
28 //要注意下面的用法
29 int *p7 = &s[0];
30 int v1 = *++p7;
31 printf("v1 = %d, p7 = %p, &s[0] = %p, &s[1] = %p\n", \
32 v1, p7, &s[0], &s[1]); //20 p7保存的是s[1]的地址
33
34 int *p8 = &s[0];
35 int v2 = *p8++;
36 printf("v2 = %d, p8 = %p, &s[0] = %p, &s[1] = %p\n", \
37 v2, p8, &s[0], &s[1]); //10 p8保存的是s[1]的地址
38
39 int *p9 = &s[0];
40 int v3 = (*p9)++; //这种写法等价于 int v3 = s[0]++;
41 printf("v3 = %d, p9 = %p, &s[0] = %p, &s[1] = %p\n", \
42 v3, p9, &s[0], &s[1]); //10 p9保存的是s[0]的地址
43 printf("s[0] = %d\n", s[0]);//11
44
int *p9 = &s[0];
int v1 = *++p9;
printf("v1=%d p9=%p &s[1]=%p\n",v1,p9,&s[1]);//20 后两个地址一样
int q1 = &s[0];
int v2 = *q1++;
printf("v2=%d q1=%p &s[1]=%p\n",v2,q1,&s[1]);//10 后两个地址一样
int q2 = &s[0];
int v3 = (*q2)++;
printf("v3=%d s[0]=%d\n",v3,s[0]); // 10 11
45 //指针的关系运算
46 if(p1 < p2){
47 printf("yes\n");
48 }else{
49 printf("no\n");
50 }
52 //指针的赋值运算
53 //指针变量本质也是变量 可以被赋值 也就是改变指针的指向 也可以 ++
54 int a = 10;
55 int b = 20;
56 int *pp1 = &a;
57 int *pp2 = &b;
58 pp1 = pp2;//指针可以被重新赋值
59
60 return 0;
61 }
//指针的赋值运算 指针变量的本质也是变量 可以被重新赋值 但是指向会变
int *q5 = &s[0];
q5 = &s[2];
int *q6 = &s[3];
q5 = q6;

在这里插入图片描述

7 往期快速回顾

练习:
定义一个变量a,类型为int,赋值888;
定义一个指针p,类型为 int * ,指向变量a,通过p将a的值修改为0x12345678;
再定义一个指针q,类型为 char *,也指向变量a
然后使用 %#x 分别输出 *q *(q+1) *(q+2) *(q+3) 的值(加了#就会在输出带上进制前缀)

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 int a = 888;
6 int *p = &a;
7 *p = 0x12345678;
8 char *q = (char *)p;
9 printf("*(q+0) = %#x\n", *(q+0));//0x78
10 printf("*(q+1) = %#x\n", *(q+1));//0x56
11 printf("*(q+2) = %#x\n", *(q+2));//0x34
12 printf("*(q+3) = %#x\n", *(q+3));//0x12
13
14 return 0;
15 }

8 大小端存储问题

不同类型的CPU和操作系统对多字节数据的存储方式可能不一样
分为大端存储和小端存储(大多数为小端存储)
注意:两个16进制为一个字节
在这里插入图片描述
笔试面试题:
请你写一个简单的程序,判断你所使用的主机是大端存储还是小端存储?

1 #include <stdio.h>
2 int main(){
3 int a = 0x12345678;
4 char *p = (char *)&a;
5 if(0x78 == *p){
6 printf("小端\n");
7 }else if(0x12 == *p){
8 printf("大端\n");
9 }
10 return 0;
11 }

思考:
小端存储的主机上,下面的代码会输出什么?

1 int m = 0x41424344;
2 printf("%s\n", &m);//DCBA+不确定的

涉及:大小端存储问题、16进制数据如何存入内存、16进制如何转换成10进制、常见字符对应的ASCII码(十进制 65对应A)、printf(“%s\n”,&m)对应输出字符串要的是首地址(从首地址到\0结束),取地址&num取的是编号最小的首地址
在这里插入图片描述

9 指针和一维数组

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 int s[5] = {10, 20, 30, 40, 50};
6
7 //数组名就是数组的首地址 是一个地址常量
8 printf("s = %p\n", s);  //记住 这是一个打印地址的方式
9 printf("s+1 = %p\n", s+1);//相差一个 int(四个字节) 也就是说数组名的操作空间是一个元素的大小(和数组的类型是一致的)
10
11 //研究一下一维数组 数组名[下标] 访问元素的本质
12 printf("s[0] = %d\n", s[0]);//10
13 printf("*s = %d\n", *s);//10
14 printf("*(s+1) = %d\n", *(s+1));//20
15 printf("*s+1 = %d\n", *s+1);//11 不加括号 *优先级 比 + 要高
   //也就是说 数组名[下标]方式访问元素的本质就是对指针取*操作
16 //有这样的等价关系 s[i] <==> *(s+i)
17
18 //也可以使用指针保存一维数组的首地址
19 //有下面的写法
20 //int *p = &s[0];
21 int *p = s;//常用的写法 s(数组名)就是数组的首地址
22 //int *p = &s;   //这种写法相当于指针的升维操作,改变了指针的操作空间 不要这么写 
   //记住 不要对数组名做取地址的操作!!!!
23 //三中写法 p 中保存的地址值都是一样的 但是一般不使用第三种写法
24
25 //指针指向数组之后(也就是当指针保存了数组的首地址之后) 就可以通过指针操作数组的元素了
26 printf("*(p+0) = %d\n", *(p+0));//10
27 printf("*(p+1) = %d\n", *(p+1));//20
28 printf("p[0] = %d\n", p[0]);//10
29 printf("p[1] = %d\n", p[1]);//20
30 p[0] = 1314;
31
32 //指针指向数组后 有如下的等价关系
33 //s[i] <==> *(s+i) <==> p[i] <==> *(p+i)
34
35 //指针 p 和 数组名 s 的区别
36 // p是变量 可以被重新赋值 也可以执行 ++ 的操作
37 // s是常量 不可以被赋值 也不能 ++
38
39 //一维数组的遍历
40 int i = 0;
41 for(i = 0; i < 5; i++){
42 //printf("%d ", s[i]);
43 //printf("%d ", *(s+i));
44 //printf("%d ", p[i]);
45 printf("%d ", *(p+i));
46 }
47 printf("\n");
48 //另一种遍历方法
   for(i = 0; i < 5; i++){
   printf("%d",*p);    //printf("%d",*p++);
   p++;}
49 return 0;
50 }

思考:
32位小端存储的主机上,下面的代码会输出什么?

//如果是用64位系统验证 编译时 要加编译选项 -m32
1 int s[5] = {1,2,3,4,5};
2 int *p = (int *)((int)s+1);
3 printf("%x\n", *p); //2000000  %x为16进制 不加#则没有前导符 printf最开始的时候0不输出

32位系统,*p应该是四个字节
在这里插入图片描述
练习:
1.结合指针实现 strlen 函数的功能。

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 char s[32] = "hello world";
6 int count = 0;
7 char *p = s;
8 #if 0
9 while(*p != '\0'){
10 count++;
11 p++;
12 }
13 #endif
14
15 #if 0
16 while(*p){
17 count++;
18 p++;
19 }//当循环结束时 p指向 s 的 '\0'
20 #endif
21
22 #if 1
23 while(*p++){     //相当于while(*p++ != '\0')
24 count++;
25 }//当循环结束时 p是指向 s 的'\0'的后面一位的 是不可预知的
26 #endif
27 printf("len = %d\n", count);
28
29 return 0;
30 }

2.结合指针实现 strcpy 函数的功能。

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 char s1[] = "hello world";
6 char s2[] = "hqyj";
7
8 char *p1 = s1;
9 char *p2 = s2;
10 #if 0
11 while(*p2){
12 *p1 = *p2;
13 p1++;
14 p2++;
15 }
16 *p1 = *p2;  //将\0也复制给s1
17 #endif
18 #if 0
19 int i = 0;
20 while(*(p2+i)){
21 *(p1+i) = *(p2+i);
22 i++;
23 }
24 *(p1+i) = *(p2+i);
25 #endif
26
27 #if 0
28 while(*p2){
29 *p1++ = *p2++;
30 }
31 *p1 = *p2;
32 #endif
33 while(*p1++ = *p2++);
34
35 //printf("s1 = [%s]\n", s1);
36 //printf("s2 = [%s]\n", s2);
37
38 //如果使用p1和p2输出字符串的化注意 需要将p1和p2重新指向首地址
39 //因为在拷贝的过程中 p1 和 p2的指向都发生变化了
40 p1 = s1;
41 p2 = s2;
42 printf("s1 = [%s]\n", p1);
43 printf("s2 = [%s]\n", p2);
44
45 return 0;
46 }

3.结合指针实现 strcat 函数的功能。

//常规写法
#include <stdio.h>
int main(int argc, const char *argv[])
{
	char *p = s1;
	char *p = s1;
	while(*p != '\0'){
		p++;
}
	while(*p != '\0'){
		*p = *q;
		p++;
		q++;
}
	return 0;
}
1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 char s1[32] = "hello";
6 char s2[32] = "beijing";
7 //先找s1的 '\0'
8 char *p1 = s1;
9 while(*p1++);//注意循环结束时 p1指向s1的'\0'的后一位
10 p1‐‐;
11 //开始追加
12 char *p2 = s2;
13 while(*p2){
14 *p1++ = *p2++;
15 }
16 *p1 = *p2;
17 printf("s1 = [%s]\n", s1);
18 printf("s2 = [%s]\n", s2);
19
20 return 0;
21 }

10 指针和二维数组

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 int s[3][4] = {{1,2,3,4},
6 {5,6,7,8},
7 {9,10,11,12}};
8 //二维数组的数组名也是数组的首地址
9 printf("s = %p\n", s);
10 //研究一下二维数组数组名的操作空间
11 printf("s = %p\n", s);
12 printf("s+1 = %p\n", s+1);//相差16字节 4*sizeof(int)
13
14 //也就是说二维数组数组名操作空间是一整行元素 也称为 行指针
15 //对二维数组数组名 取 * 操作 表示降维操作
16 //将操作空间是一整行的指针降维成操作空间是一个元素的指针 ‐‐列指针
17 printf("*s = %p\n", *s);
18 printf("*s+1 = %p\n", *s+1);//相差4字节
19
20 //对列指针再取*操作 表示操作里面的数据
21 printf("**s = %d\n", **s);//1
22 printf("*(*(s+0)+0) = %d\n", *(*(s+0)+0));//1
23 printf("*(*(s+1)+2) = %d\n", *(*(s+1)+2));//7
24 printf("*(*(s+2)+3) = %d\n", *(*(s+2)+3));//12
25
26 //也就是说有如下的等价关系
27 // s[i] <==> *(s+i)
28 // s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j)
29
30 //遍历二维数组
31 int i = 0;
32 int j = 0;
33 for(i = 0; i < 3; i++){
34 for(j = 0; j < 4; j++){
35 //printf("%d ", s[i][j]);
36 //printf("%d ", *(s[i]+j));//不常用
37 printf("%d ", *(*(s+i)+j));
38 }
39 printf("\n");
40 }
41
42 //注意:二维数组的数组名是一个行指针
43 //操作空间是一行元素 已经超过了基本类型的范围了
44 //所以不能使用普通的指针 指向二维数组
45 //即使保存了 也不能按行操作 因为 普通指针不允许取 ** 操作
46 int *p = s;
47 printf("p = %p\n", p);
48 //*(*(p+0)+1) = 10;//错误的
49 //如果需要保存二维数组的首地址 需要用 数组指针
50
51 return 0;
52 }

11 数组指针

本质是一个指针,用来指向二维数组的。也叫作行指针。
多用于将二维数组作为函数的参数传递时。

1 格式:
2 数据类型 (*数组指针名)[列宽];

例:

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 int s[3][4] = {{1,2,3,4},
6 {5,6,7,8},
7 {9,10,11,12}};
8 //定义了一个数组指针p 指向二维数组s
9 int (*p)[4] = s;
10
11 printf("sizeof(p) = %ld\n", sizeof(p));//就是一个指针的大小 4或8
12
13 //数组指针指向二维数组后 就可以操作二维数组的元素了
14 //操作方式和使用数组名操作是一模一样的
15 //也就是 有如下的等价关系
16 // s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j) <==>
17 // p[i][j] <==> *(p[i]+j) <==> *(*(p+i)+j)
18
19 //二维数组的遍历
20 int i = 0;
21 int j = 0;
22 for(i = 0; i < 3; i++){
23 for(j = 0; j < 4; j++){
24 //printf("%d ", s[i][j]);
25 //printf("%d ", *(s[i]+j));
26 //printf("%d ", *(*(s+i)+j));
27 //printf("%d ", p[i][j]);
28 //printf("%d ", *(p[i]+j));
29 printf("%d ", *(*(p+i)+j));
30 }
31 printf("\n");
32 }
33
34 // 数组名s 和 指针p的区别还是
35 // 数组名s 是常量
36 // 指针 p 是变量
37
38 return 0;
39 }

之所以不能对一维数组的数组名取地址的原因:----了解即可

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 int s[5] = {1,2,3,4,5};
6
7 //对数组名s 取地址 相当于升维操作
8 //把原来操作空间是一个元素的指针升维成操作空间是一行元素的指针
9 //但是我们的 p 的操作空间只有一个元素
10 //所以类型不匹配 会报警告
11 //int *p = &s;
12
13 //可以使用数组指针来消除这个警告
14 int (*p)[5] = &s;
15 //但是此时的p已经没有意义了 因为p是行指针了 操作空间是一行元素
16 //而我们的数组 只有一行 也就是说 p+1 就已经越界了
17 //所以我们不要使用 对数组名取地址的这种写法
18
19 return 0;
20 }

12 指针数组

13 指针和字符串

虚拟内存的划分:
在这里插入图片描述

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 //可以使用数组来保存字符串常量
6 //s1是数组 在栈区 "hello world" 是字符串常量 在字符串常量区
7 //这种写法表示 用字符串常量区的 "hello world" 来初始化栈区的数组s1
8 char s1[32] = "hello world";
9 //对数组 s1 进行的操作都是操作的栈区的数据
10 //栈区的数据是允许修改的
11 *s1 = 'H';
12 printf("s1 = [%s]\n", s1);//Hello world
13
14 //栈区定义多个数组 即使保存同一个字符串常量 他们的地址也是不一样的
15 char s2[32] = "hello world";
16 printf("%p %p\n", s1, s2);//不一样的
17
18
19 //也可以使用指针直接指向字符串常量
20 //这种写法 相当于 p 直接保存的是字符串常量区中 "hello world" 的地址
21 char *p1 = "hello world";
22 printf("p1 = [%s]\n", p1);//hello world
23 //*p1 = 'H';//错误的 字符串常量区的内容 是不允许修改的!!!!!!!!
24
25 //多个指针指向同一个字符串常量时 多个指针保存的地址都是一样的
26 char *p2 = "hello world";
27 printf("%p %p\n", p1, p2);//一样的
28
29 return 0;
30 }

14 二级指针

1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 char s1[32] = "hello";
6 char s2[32] = "beijing";
7 //先找s1的 '\0'
8 char *p1 = s1;
9 while(*p1++);//注意循环结束时 p1指向s1的'\0'的后一位
10 p1‐‐;
11 //开始追加
12 char *p2 = s2;
13 while(*p2){
14 *p1++ = *p2++;
15 }
16 *p1 = *p2;
17 printf("s1 = [%s]\n", s1);
18 printf("s2 = [%s]\n", s2);
19
20 return 0;
21 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值