指针的定义

用来存放内存单元地址,内存单元地址唯一标识一块内存空间

指针定义方式:数据类型 *变量名;

1
2
3
4
5
6
7
8
int main()
{
int a = 10; //在内存中开辟一块空间
printf("%p\n",&a); //通过&(地址运算符)获取变量a的内存地址值:0x6ffee666d6f
int * pa = &a; //定义指针,并将a的地址赋值给指针
printf("%p\n",pa); //打印指针:0x6ffee666d6f
return 0;
}

结论:

1、指针就是变量在内存中单元中的地址值;
2、地址值是整数值;
3、一般用“%p”的格式,以十六进制显示。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
printf("a:%d\n",a);//打印a的值
printf("&a: %p\n",&a);//打印a的地址编号(%p:十六进制打印地址)、打印a的地址,&是取地址运算符
int * ptr = &a;//*表示声明的是一个指针类型的变量ptr,
//*此时是一个标志符,表示ptr是一个指针变量
//int表示这个指针变量ptr只能保存int类型变量的地址

printf("ptr:%p\n",ptr);//打印ptr的值,是一个地址,
//用%p打印即是用十六进制打印地址编号

printf("&ptr:%p\n",&ptr);//打印ptr的地址编号、打印ptr的地址
int **pptr = &ptr;//pptr是一个二级指针变量,保存的是一级指针变量ptr的地址
printf("sizeof(a):%lu\n",sizeof(a));//4字节
printf("sizeof(ptr):%lu\n",sizeof(ptr));//8字节

printf("*ptr:%d\n",*ptr);//10
//*ptr是根据ptr中保存的地址,
//去访问对应的地址空间<=====>printf("*ptr:%d\n",a);
//*单目运算符,表示指针间接访问运算符。
//*的含义:1、指针标记 2、指针解引用 3、乘号

printf("*&a:%d\n",*&a);
//*&是互逆操作,操作抵消了<=====>*&a <======> a

*ptr = 100;

printf("*ptr:%d\n",*ptr); //100
printf("a=%d\n",a); //100

printf("*ptr*200:%d\n",*ptr * 200);
//两个*运算。一个是指针运算,一个是乘法运算

printf("**pptr:%d\n",**pptr);//100
return 0;
}
//a、&a、ptr、*ptr、&ptr、sizeof(a)、sizeof(ptr)

指针的大小

指针存放地址,所以指针的大小直接取决于地址的大小,即与机器字长有关,与地址空间中存放的数据类型无关。

机器平台 指针大小
32位机器 地址是32位组成二进制序列,用4个字节的空间来存储,所以指针变量的大小就是4个字节
64位机器 地址是64位组成二进制序列,用8个字节的空间来存储,所以指针变量的大小就是8个字节

指针操作

指针解引用*

通过指针访问所指向内存中存储的变量(或数据)

1
2
3
4
5
6
int arr[] ={10,20,30,40};   
printf("%p\n",&arr[0]);
printf("%p\n",arr);
int *ptr = arr;
printf("%p\n",ptr);//数组名 == 数组首元素地址 == 数组第一个字节地址 = 指针
printf("%d\n",*ptr);//10

注意与乘号进行区别使用(乘号是双目运算符,乘号两边必须有两个操作数)

1
2
3
int *ptr; 与 int *ptr;  与 int *ptr; 都是用来定义一个prt指针变量的;功能完全相同。
int *ptr1,n=10; //这是定义了一个ptr1指针变量,和一个n普通变量。
ptr1 = &n;//ptr指针变量里保存了n的地址。也说成:ptr1指向了n。

“+-整数”操作

代表指针地址在内存中加减一定数量的元素单元,也就是指针指向的元素发生了变化

1
2
3
4
5
6
7
8
9
10
11
12
13
int arr[] ={10,20,30,40};
int *ptr=arr;
printf("%p\n",ptr);

printf("%d\n",*ptr);//10

ptr +=2;
printf("%d\n",*ptr);//30
printf("%p\n",ptr);

ptr -=1;
printf("%d\n",*ptr);//20
printf("%p\n",ptr);

指针的类型决定了指针向前(+整数)或者向后(-整数)走一步有多大(距离)

野指针

指未初始化的指针,或者指向不合法地址的指针

1
2
int *ptr;
printf("%p\n", ptr);
指针未初始化 未初始化的指针,当前就是野指针
指针超过作用域 当一个指针超出了它所在的作用域,指针指向的内存是非法的或者已经被释放的
指针被释放后未置空 涉及到动态内存分配和释放

如何规避野指针:

1、指针初始化
2、 小心指针越界
3、指针指向空间释放即置为空NULL

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <stdio.h>
#define N 10
int main()
{
int arr[] = {10,20,30,40};
//数组名就是首元素的地址,是常量,不能修改
//有四个元素:arr[0],arr[1],arr[2],arr[3]

int arr3[4] = {0};
int arr2[4] = {100,200,300,400};

//arr3 = arr2;//错误,不能将一个数组赋值给另一个数组
//N = 20;//错误,不能修改符号常量

*arr = *arr2;//《========》*&arr[0] = *&arr2[0]《====》arr[0] = arr2[0];
printf("arr[0]:%d\n",arr[0]);//100

*arr *= *arr2;
//《========》arr[0] *= arr2[0];
//《========》arr[0] = arr[0] * arr2[0];;
printf("arr[0]:%d\n",arr[0]);//10000
printf("*arr:%d\n",*arr);//10000

int *ptr = arr;//等价于<=======> int *ptr = &arr[0];
printf("%p\n",ptr);//arr[0]的地址
printf("%d\n",*ptr);//arr[0]的值 10000

ptr +=2;
printf("%p\n",ptr);//arr[2]的地址
printf("%d\n",*ptr);//arr[2]的值30


ptr -=1;//ptr--
printf("%p\n",ptr);//arr[1]的地址
printf("%d\n",*ptr);//arr[1]的值20

printf("通过指针访问所有的数组元素:\n");
ptr = arr;//等价于<=======> ptr = &arr[0];
for(int i = 0;i < 4;i++)
{
printf("%d\n",*ptr);
ptr++;
}
printf("%p\n",ptr);
//上面循环的最后一次打印,打印的是指针指向的arr[3],
//再++,就指向了arr[4],越界

printf("%d\n",*ptr);//越界访问,程序可能崩溃。打印出了不确定值
printf("%p\n",&arr[3]);//数组中最后一个元素的地址

ptr = &arr[3];
printf("%p\n",ptr);
printf("%d\n",*ptr);
*ptr += 2;//<======>arr[3] += 2;
printf("%p\n",ptr);
printf("%d\n",*ptr);
printf("arr[3]:%d\n",arr[3]);

ptr = arr;
printf("%p\n",ptr);
*(ptr += 2);//ptr += 2;*ptr;
printf("%p\n",ptr);
printf("%d\n",*ptr);//30
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
28
29
#include <stdio.h>
int sumOfArr(int *a,int n)//形式参数传递数组名
{
//<========>int sumOfArr(int a[],int n)
//a[]===>int *a
printf("sizeof(a):%lu\n",sizeof(a));
int s = 0;
//方法一
//for(int i = 0;i < n;i++)
//{
// s += a[i];//a指针名可以当作数组名使用
// s += *(a+i);//指针加一
//}
//return s;
//方法二
s = 0;
for(int i = 0;i < n;i++)
{
s += *a;
a++;//指针加一
}
return s;
}
int main()
{
int b[] = {10,20,30,40,50};
printf("%d\n",sumOfArr(b,sizeof(b)/sizeof(int)));
return 0;
}

指针运算

指针相减运算

计算两个指针之间相差的偏移量(步数)或元素个数偏移量表示两个指针所指向内存地址之间的距离(即相差多少元素)

1
2
3
4
5
int arr[] ={10,20,30,40};   
int *ptr1=&arr[0];
int *ptr2=&arr[2];
int offset = ptr2 - ptr1;
printf("%d\n",offset);

注意:指针相减运算只有在指针指向同一数组时才有意义

指针关系运算

基于指针所指向的内存地址进行比较

可以使用关系运算符(<、>、<=、>=、==、!=)来比较指针,反映指针所指向地址的前后关系

1
2
3
4
5
6
7
8
9
10
11
12
13
int arr[] ={40,30,20,10};
int *ptr1=&arr[0];
int *ptr2=&arr[2];
if (ptr1 < ptr2)
{
printf("ptr1 在 ptr2之前\n");
} else if (ptr1 > ptr2)
{
printf("ptr1 在 ptr2之后\n");
} else
{
printf("处于相同位置\n");
}

注意:指针关系运算只有在指针指向同一数组元素时才有意义

指针与数组

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
int main()
{
int arr[5] = {10,20,30,40,50};
printf("arr:%p\n",arr);//地址常量。数组首元素的地址
printf("&arr[0]:%p\n",&arr[0]);////arr <==> &arr[0],都是数组的首地址

int *p = arr;//<======>int *p = &arr[0];

printf("p:%p\n",p);//p <==> &arr[0]
printf("*p:%d\n",*p);//*p <==> arr[0]

*p = 1000;//arr[0] = 1000;
for(int i = 0;i < 5;i++){
printf("%d ",arr[i]);
}
printf("\nprintf(\"%%d \",*p);-----------------\n");
for(int i = 0;i < 5;i++){
printf("%d ",*p);
p++;
}

printf("\nprintf(\"%%p \",p+i);-----------------\n");
p = arr;
for(int i = 0;i < 5;i++){
printf("%p ",p+i);
}
printf("\nprintf(\"%%d \",*(p+i));-----------------\n");
for(int i = 0;i < 5;i++){
printf("%d ",*(p+i));
}

printf("\nprintf(\"%%d \",p[i]);-----------------\n");
for(int i = 0;i < 5;i++){
printf("%d ",p[i]);//*(p+i): 数组的首地址指针变量,当做数组名使用
}
printf("\n-----------------\n");

int a = 20000;
int *pa = &a;
printf("*pa:%d\n",*pa);
printf("*pa:%d\n",*(pa+0));
printf("pa[0]:%d\n",pa[0]);//*(pa+0)
printf("pa[1]:%d\n",pa[1]);//没有意义,使用了野指针//*(pa+1)
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <stdio.h>
#include <string.h>
// 定义打印数组的函数
void printArray(int *ptr, int length)
{
for (int i = 0; i < length; i++)
{
printf("%d\n", *(ptr + i));//《====》ptr[i]《======》*(ptr++) 而不能写*(++ptr)
}
}
void swap(int a, int b)
{//在被调用时,x,y的值会被复制一份给函数,函数内部对a, b的操作不会影响到调用者(主调函数)的x和y
int temp = a;
a = b;
b = temp;
printf("swap:a = %d, b = %d\n", a, b);

}
void swap2(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
printf("swap2:*a = %d, *b = %d\n", *a, *b);
}

int main()
{
int arr[] = {10, 20, 30, 40};
int length = sizeof(arr) / sizeof(arr[0]);
printArray(arr, length);
printf("-----------------------\n");

int x = 10, y = 20;
swap(x, y);
printf("main:x = %d, y = %d\n", x, y);//不会改变x, y的值

swap2(&x,&y);
printf("main:x = %d, y = %d\n", x, y);

//char ch[10] = "abc\10156789";//\101会把八进制数101转成10进制数对应的ascii码字符
char ch[10] = "abc\00056789";
//scanf("%s", ch);
printf("sizeof(ch):%lu\n", sizeof(ch));//10
printf("strlen(ch):%lu\n", strlen(ch));//3
printf("%s\n", ch);//abc
printf("%c\n", ch[5]);//6


char cc[10] = "abc\056789";//\056是八进制数56做为ascii码对应的字符
printf("%s\n", cc);//abc.789
printf("strlen(ch):%lu\n", strlen(cc));//7

//字符串比较大小
char str1[] = "acZ";
char str2[] = "abZ";
if (strcmp(str1, str2) == 0)//strcmp相等返回0
{
printf("str1 == str2\n");
}
else if (strcmp(str1, str2) > 0)
{
printf("str1 > str2\n");
}
else
{
printf("str1 < str2\n");
}

char name[][10] = {"abZ", "abc123", "123abc", "abc"};
char n = 4;
//冒泡排序.从小到大
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (strcmp(name[j], name[j + 1]) > 0)
{
char temp[256];
strcpy(temp,name[j]);//strcpy(dest,src)字符串
//复制函数完成字符串赋值,字符串不能使用等号直接赋值
strcpy(name[j],name[j+1]);
strcpy(name[j+1],temp);
}
}
}
for (int i = 0; i < n; i++)
{
printf("%s\n", name[i]);
}
return 0;
}

指针数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int main()
{
int a[3] = {10, 20, 30};
int *p[3] = {&a[0], &a[1], &a[2]};// 指针数组p,p中的每个元素都是地址
for (int i = 0; i < 3; ++i)
{
printf("%p\n", a+i);//数组a中每个元素地址
printf("%p\n", &a[i]);//数组a中每个元素地址
printf("%p\n", p[i]);//数组a中每个元素地址

printf("%d\n", *p[i]);//a[0]、a[1]、a[2]。 10 20 30
printf("%d\n", a[i]);
printf("%d\n", *(a+i));
printf("%d\n", *(*(p+i)));
printf("\n");
}
}

数组指针

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <stdio.h>
void printArr(int (*p)[5], int n)//p是行指针(且是有5个元素的行),n是行数,p也可以写成int p[][5]
{
printf("=====================\n");
for (int i = 0; i < n; ++i)///i++也可以
{
for(int k = 0 ; k < 5 ;k++)
{
printf("%d ", p[i][k]);
}
printf("\n");
}
}


int main()
{
int a[3] = {10, 20, 30};
// 数组指针
int (*p)[3]; // 定义了一个数组指针(行指针),此指针指向一个有3个元素的数组,保存的这3个元素的数组地址
p = &a;//保存了有3个元素的数组的地址
printf("p : %p\n", p);
printf("&a : %p\n", &a); // 数组地址
printf("a : %p\n", a); // 数组名,数组首元素地址 int *p = a;
printf("&a[0]: %p\n", &a[0]); // 数组首地址:首元素地址 int *pa = &a[0];

int *pa = &a[0];//pa++时,pa指向下一个元素
int *pc = a;//pc++时,pc指向下一个元素
pa++;
printf("pa : %p\n", pa);
printf("sizeof(p)=%d\n",sizeof(p));
p++;
printf("p : %p\n", p);//p++时,p指向下一行,对于一维数组a来说是越界了

p = &a;
for (int i = 0; i < 3; ++i)
{
printf("%d\n", p[0][i]);//数组指针可以当做二维数组名来使用
}

int b[3][5] = {{1, 2,10,23,4}, {3, 4,5,6,3}, {5, 6,11,22,33}};
//int (*pb)[5] = &b[0];//定义了一个指针,此指针指向一个有5个元素的数组,保存的这5个元素的数组地址
int (*pb)[5] = b; // 与上面一行代码等价,二维数组名表示第0行的地址、行地址、行指针、&b[0]表示第0行的地址。指向数组的首行地址
for (int i = 0; i < 3; i++)
{
for(int k = 0 ; k < 5 ;k++)
{
printf("%d ", b[i][k]);
}
printf("\n");
}
printf("--------------------\n");
for (int i = 0; i < 3; i++)
{
for(int k = 0 ; k < 5 ;k++)
{
printf("%d ", pb[i][k]);
}
printf("\n");
}

printf("--------------------\n");
printf("0-pb:%p\n",pb);
printf("%d,%d\n", pb[0][0], pb[0][1]);
pb++;//pb指向下一行
printf("1-pb:%p\n",pb);
printf("%d,%d\n", pb[0][0], pb[0][1]);
pb++;
printf("2-pb:%p\n",pb);
printf("%d,%d\n", pb[0][0], pb[0][1]);


printf("+++++++===========\n");
pb = &b[1];
printf("%d,%d\n", pb[0][0], pb[0][1]);
printf("%d,%d\n", pb[1][0], pb[1][1]);

printf("%d,%d\n", *(*(pb + 1) + 0), *(*(pb + 1) + 1)); // 等价于printf("%d,%d\n", pb[1][0],pb[1][1]);

printArr(b, 3);

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
28
29
30
31
32
33
34
35
36
37
38
#include<stdlib.h>
#include<stdio.h>
//int * fun() //指针函数
//{
// int a = 10;//局部变量a:函数执行完毕,变量自动销毁,不可访问 。分配的是栈空间
// return &a;//这样返回的地址,不能使用。因为函数执行完毕,变量销毁,空间释放,不能访问
//}
int *fun2()
{
int *p = (int *)malloc(40*sizeof(int));//malloc动态申请内存,分配的是堆空间,
//申请的内存不会随着函数的调用结束而自动释放,必须手动释放,否则会造成内存泄露
//malloc函数申请的内存,必须free释放,否则会造成内存泄露
return p;//返回的是地址,所以可以访问局部变量
}
int main()
{
// int *p = fun();
// printf("%d\n", *p);//不能直接访问局部变量,只能通过指针间接访问,但是如果局部变量已经销毁,指针访问就会出错
// *p = 100;//不安全,指针访问的局部变量,如果局部变量已经销毁,指针访问就会出错
// printf("%d\n", *p);//野指针,访问了已经释放的指针,是不安全的。

int *q = fun2();
for(int i = 0;i < 40;i++)
{
q[i] = i*10;//*(q+i) = i*10;
}
for(int i = 0;i < 40;i++)
{
printf("%d ", q[i]);
}

//释放内存
free(q);
q = NULL;//释放后,指针置为空
//printf("\n%d\n", q[1]);//error,运行时错误,段错误,访问了空指针,程序崩溃
printf("\n");
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
void pr()
{
printf("**********\n");
}

int add(int a, int b)
{
return a+b;
}
int sub(int a, int b)
{
return a-b;
}

//add_sub是一个通用函数,可以接收任意两个参数的函数指针作为参数,并返回一个结果。
int add_sub(int (*f)(int, int), int a, int b)//f是函数指针,指向一个函数,回调函数
{
return f(a,b);
}
int main()
{
printf("%p\n",pr);//pr函数名表示函数的地址
printf("%p\n",&pr);//pr函数名表示函数的地址

void (*p)();//定义一个函数指针,指向一个函数,指向一个没有形参且没有返回值的函数
p = pr;//将符合要求的函数的地址保存到函数指针中
pr();//直接调用函数
(*p)();//通过指针调用函数
p();//通过指针调用函数
printf("\n");

p = &pr;//将符合要求的函数的地址保存到函数指针中
(*p)();//通过指针调用函数
p();//通过指针调用函数

int (*q)(int,int) = add;//将符合要求的函数的地址保存到函数指针中
int c = (*q)(1,2);//通过指针调用函数
printf("%d\n",c);
c = q(1,2);//通过指针调用函数
printf("%d\n",c);

q = sub;
c = q(1,2);//通过指针调用函数
printf("%d\n",c);


printf("%d\n",add_sub(add,10,20));
printf("%d\n",add_sub(sub,10,20));
return 0;
}

main函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main(int argc,char *argv[])
{
//argument count
//argc是执行当前可执行文件时,命令行中字符串的个数(包含命令本身字符串)。
//argv是命令行中所有的字符串,argv是一个字符串数组,argc值就是数组元素的个数
printf("argc:%d\n",argc);
for(int i = 0;i < argc;i++)
{
puts(argv[i]);//等价于printf("%s\n",argv[i]);
}
return 0;
}

const

修饰的变量不能再被修改

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
28
29
30
31
32
#include <stdio.h>
int main()
{
int a = 10;
a = 20;
const int b = 100;//常变量
//b = 20;//error

int *p1 = &a;
*p1 = 200;
printf("a = %d\n", a);

const int *p2 = &a;//p2 指向的地址里的内容不能修改,不能对*p2赋值
//*p2 = 30;//error
p2 = &b;
printf("*p2 = %d\n", *p2);
//*p2 = 300;//error

int const *p3 = &a;//指向的地址里的内容不能修改,不能对*p3赋值
// *p3 = 40;//error

int * const p4 = &a;//p4中保存的地址不能修改,不能对p4赋值
*p4 = 50;
printf("a = %d\n", a);
//p4 = &b;//error

const int * const p5 = &a;//p5中保存的地址及地址里的内容都不能修改,不能对p4及*p4赋值
//p5 = &b;//error
//*p5 = 60;//error

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
28
29
30
31
32
33
34
35
#include <stdio.h>
int main()
{
int a = 10;
int * pa = &a;//pa指向a,保存了a的地址,pa是一级指针
int ** ppa = &pa;//ppa指向pa,保存了pa的地址,ppa是二级指针

printf("a的地址是%p\n",&a);//a的地址
printf("pa的值是%p\n",pa);//pa的值,也是a的地址
printf("*ppa的值是%p\n",*ppa);//ppa指向的pa的值,也是a的地址

printf("pa的地址是%p\n",&pa);//pa的地址
printf("ppa的值是%p\n",ppa);//ppa的值,也是pa的地址


printf("%d\n",a);//10
printf("%d\n",*pa);//10
printf("%d\n",**ppa);//10

*pa = 20;//修改a的值
printf("%d\n",a);//20
printf("%d\n",*pa);//20
printf("%d\n",**ppa);//20

**ppa = 30;//修改a的值(*ppa的结果是pa的值,也是a的地址 &a)
printf("%d\n",a);//30
printf("%d\n",*pa);//30
printf("%d\n",**ppa);//10

//打印pa和ppa的大小
printf("%lu\n",sizeof(pa));//8
printf("%lu\n",sizeof(ppa));//8

return 0;
}