以下讲解是按照如下这个程序的执行顺序来讲解的

一,程序中的c语言指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int a, b; //这是一个普通的整型变量
int* p;//这是一个整形的指针
a = 3;
b = 4;
6
printf(" a的地址:%d;\r\n", &a);
printf(" b的地址:%d;\r\n", &b);
printf(" p的地址:%d;\r\n", &p);
printf(" p的值:%d,现在p的值是不确定的,目前只是为p申请了地址,还没有为它赋值;\r\n", p);

p = &a;//取址运算/* p现在指向a */
printf(" 利用取址操作p = &a;,把a的地址赋值给p,现在p的值是%d,也就是a的地址;\r\n", p);
printf(" p的地址没有变化,p的地址仍然是%d,在这个地址上存储的是变量a的地址;\r\n", &p);

printf(" 利用*运算符得到指针p指向地址中的数值为%d,在刚才p已经指向变量a的地址了,所以指针p指向地址中的值是3,但是p的值仍然是a的地址;\r\n", *p);

b = *p;/* b现在为a的值 */
printf(" b = *p;,现在b的值就是p指向地址中的值,也就是a的值:%d;\r\n", b);

*p = 5;/* a现在为5 */
printf(" 现在利用*p为p指向地址中存储的值进行赋值:%d,这时a的值也已经改变了:%d;\r\n", *p, a);

1
2
3
4
5
6
7
8
int a,b; //这是一个普通的整型变量
int *p;//这是一个整形的指针
a = 3;
b = 4;
printf(" a的地址:%d;\r\n", &a);
printf(" b的地址:%d;\r\n", &b);
printf(" p的地址:%d;\r\n", &p);
printf(" p的值:%d,现在p的值是不确定的,目前只是为p申请了地址,还没有为它赋值;\r\n", p);

指针p定义的时候没有进行初始化,所以在这里,p的初始值是不确定的。

当然也可以在p定义的时候赋初值,这样p的初始值就是确定的了。

p = 1;

一元运算符&可用于取一个对象的地址,如下,这时p为指向a的指针。地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式、常量或register类型的变量。

这时p的值是3930420,即变量a的地址;

利用&取出p的地址仍然为3930396,没有变;

利用间接寻址p可以得到指针p指向地址中的值为3,即a的值。

p = &a;//取址运算/* p现在指向a */

printf(“ 利用取址操作p = &a;,把a的地址赋值给p,现在p的值是%d,也就是a的地址;\r\n”, p);

printf(“ p的地址没有变化,p的地址仍然是%d,在这个地址上存储的是变量a的地址;\r\n”, &p);

1
2
3
4
5
p = &a;//取址运算/* p现在指向a */
printf(" 利用取址操作p = &a;,把a的地址赋值给p,现在p的值是%d,也就是a的地址;\r\n", p);
printf(" p的地址没有变化,p的地址仍然是%d,在这个地址上存储的是变量a的地址;\r\n", &p);

printf(" 利用*运算符得到指针p指向地址中的数值为%d,在刚才p已经指向变量a的地址了,所以指针p指向地址中的值是3,但是p的值仍然是a的地址;\r\n", *p);

利用*可以得到p地址中的数值,这里b的值就是3,即a的值。

1
2
b = *p;/* b现在为a的值 */
printf(" b = *p;,现在b的值就是p指向地址中的值,也就是a的值:%d;\r\n", b);

对*p进行赋值后,也就是对p指针指向地址中的数值进行赋值,这是a的值也就变为了5

1
2
* p = 5;/* a现在为5 */
printf(" 现在利用*p为p指向地址中存储的值进行赋值:%d,这时a的值也已经改变了:%d;\r\n", *p, a);

二,图解C语言指针

定义两个变量

1
2
int a, b; //这是一个普通的整型变量
int* p;//这是一个整形的指针

定义后,a的地址是0x2000,p的地址是0x3000; 在定义的时候a赋的初始值是3,p没有赋初始值,所以p的值是不确定的。

image-20211215000519144

现在进行运算:

1
p = &a;//取址运算/* p现在指向a */

这时内存图就变成了这样,p的地址没有变化,但是p的值变化了,此时,*p=3;

image-20211215000558962

三,指针与函数

指针作为函数的形参

在程序设计中,指针作为函数形参往往会带来意想不到的效果,下面用一个例程来讲解指针作为函数形参的特性。

例子:用一个函数交换两个变量的值:

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
void Swap(int x, int y)
{
int temp = 0;
temp = x;
x = y;
y = temp;
}

void Swap_pointer(int* x, int* y)
{
int temp = 0;
temp = *x;
*x = *y;
*y = temp;
}

/*
指针与函数
2019-05-09
*/
void Test2()
{
int a = 1, b = 2;
Swap(a, b);
printf("a=%d\r\n", a);
printf("b=%d\r\n", b);
Swap_pointer(&a, &b);
printf("\r\n");
printf("a=%d\r\n", a);
printf("b=%d\r\n", b);
}

执行结果如下图,可以明显的看出指针作为函数形参的特性。

具体讲解详见《C语言程序设计》的5.2章节。

指针,结构体与函数

在C语言中,函数本身不是变量,但是也可以定义指向函数的指针。这种指针的使用方法、特性与变量指针的使用方法、特性大同小异。

下面的介绍都是围绕这段函数来讲解的

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
/*
xutopia
*/
#include "stdio.h"
int max(int x, int y)
{
return x > y ? x : y;
}

int min(int x, int y)
{
return x < y ? x : y;
}

int* maxP(int x, int* y)
{
return x > * y ? x : *y;
}

void CallbackFun(int x, int(*f)(int, int b))
{
int i = 0, a = 0;
for (i = x; i < 10; i++)
{
a = f(i, 0);
printf("%d, ", a);
}
printf("\r\n");
}

typedef struct _dat
{
int(*fun)(int, int);
int* (*funP)(int, int*);
void(*funFun)(int, int(*f)(int, int));
}datTypedef;
datTypedef s;

int main()
{
int maxval = 0, minval = 0;
int a = 10;
int* pval;
pval = &a;
//指针与函数
//int(*p)(int a, int b);//right
int(*p)(int, int);
p = &max;
maxval = p(1, 2);
printf("max=%d\r\n", maxval);
p = &min;
minval = p(1, 2);
printf("min=%d\r\n", minval);
//结构体,指针与函数
s.fun = &max;
maxval = s.fun(3, 4);
printf("\r\nmax=%d\r\n", maxval);
s.funP = &maxP;
pval = s.funP(9, pval);
printf("max=%d\r\n", pval);
//回调函数
s.funFun = &CallbackFun;
s.funFun(4, max);
system("pause");
}

image-20211215000709577

1,指针指向函数。在某些场景下,我们需要用到指针指向函数。

2,在结构体中定义函数指针。利用这样的技巧,可以实现高级语言的特性,例如类的方法,属性等,可以使得代码更加安全规范。

3,函数指针作为函数的参数。

四,指针与数组

使用数组时,需要明白的一些事项:

​ 1,申请的数组存储在相邻的内存区域中;

​ 2,数组名所代表的就是该数组最开始的一个元素的地址;

​ 3,对数组元素a[i]的引用也可以写成*(a+i)这种形式;

​ 4,&a[i]和a+i的含义是相同的,简而言之,一个通过数组和下标实现的表达式可等价地通过指针和偏移量实现。;

​ 5,数组名不是变量,因此,类似于a=pa和a++形式的语句是非法的;

​ 6,当把数组名传递给一个函数时,实际上传递的是该数组第一个元索的地址;

​ 下面通过一些例程来解释指针与数组的运用:

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
/*
统计字符串长度
2019-05-09
*/
//int StrLen(char *s)//数组作为形参有两种写法“char s[]”、“char *s”,效果一样的
int StrLen(char s[])
{
int n;
for (n = 0; *s != '\0'; s++)
n++;
return n;
}

/*
2019-05-09
*/
void Array_pointer()
{
int arr[10];
int* p;
int i = 0;

for (i = 0; i < 10; i++)
{
arr[i] = i;
printf("数组[%2d]的地址:%d\r\n", i, &arr[i]);
}
printf("将指针p指向数组arr的第0个元素\r\n");
//将指针p指向数组arr的第0个元素
p = &arr[0];
p = arr;//也可以写成这种形式,因为数组名所代表的就是该数组最开始的一个元素的地址
for (i = 0; i < 10; i++)
{
printf("p[%d]=%d\r\n", i, *(p++));
}

printf("将指针p指向数组arr的第3个元素\r\n");
//将指针p指向数组arr的第3个元素
//打印出来的结果可以看到,最后面3个地址的数值是不确定的
p = &arr[3];
for (i = 0; i < 10; i++)
printf("p[%d]=%d\r\n", i, *(p++));

printf("安全的做法应该是\r\n");
//安全的做法应该是
p = &arr[3];
for (i = 0; i < 10 - 3; i++)
printf("p[%d]=%d\r\n", i, *(p++));

printf("对数组也可以进行如下方式的使用\r\n");
//对数组也可以进行如下方式的使用
//printf("arr[%d]=%d\r\n", i, *(arr++));//数组名不是变量,因此,类似于arr=p和arr++形式的语句是非法的
for (i = 0; i < 10; i++)
printf("arr[%d]=%d\r\n", i, *(arr + i));

int n = 0;
char* str = "hello,word";
n = StrLen(str);
printf("n=%d\r\n", n);
n = StrLen(&str[2]);//当然,这里可以只传入数组的一部分
printf("n=%d\r\n", n);
}

image-20211215000835686

五,地址算术运算

通过以下这个例子讲解地址算术运算

​ 这是一个不完善的存储分配程序。它由两个函数组成。第一个函数alloc(n)返回一个指向n个连续字符存储单元的指针,alloc函数的调用者可利用该指针存储字符序列。第二个函数afree(p)释放已分配的存储空间,以便以后重用。之所以说这两个函数是“不完善的”,是因为对afree函数的调用次序必须与调用alloc函数的次序相反。换句话说,alloc与afree以栈的方式(即后进先出的列表)进行存储空间的管理。标准库中提供了具有类似功能的函数malloc和free,它们没有上述限制。

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
#define ALLOCSIZE 10000 /* 可用空间大小 */

static char allocbuf[ALLOCSIZE];/* alloc使用的存储区 */
static char* allocp = allocbuf; /* 下一个空闲位置 */

char* alloc(int n) /* 返回指向n个字符的指针 */
{
if (allocbuf + ALLOCSIZE - allocp >= n) { /* 有足够的空闲空间 */
allocp += n;
return allocp - n; /* 分配前的指针 */
}
else /* 空间不够 */
return 0;
}

void afree(char* p) /* 释放p指向的存储区 */
{
if (p >= allocbuf && p < allocbuf + ALLOCSIZE)
allocp = p;
}

/*
指针运算
2019-05-09
*/
void Pointer_cal()
{
char* allAddr;
printf("空闲地址:%d\r\n", allocp);
allAddr = alloc(100);
printf("空闲地址:%d\r\n", allocp);
afree(allAddr);
printf("空闲地址:%d\r\n", allocp);
}

image-20211215000916225

未完,待续