指针(part1)
CHXQM
2024年10月22日 23:29
收录于文集
共10篇

0、引入

int a; // 定义一个 int 类型的变量,变量名为 a,没有初始值;实质上向内存申请 4 字节(连续)空间

a = 100; // 把整数常量 100 存放到 a 对应的 4字节空间

int b = a; // 定义一个 int 类型的变量,变量名为 b,4 字节(连续)空间;有初始值,是从 a 中取出存放的值放到 b 对应的 4字节空间

C 语言中任何一个变量,有两个含义:

(1)左值 变量的内存单元

(2)右值 变量的数值

而且对于变量的访问也只有两种情况:

(1)把数据存放到变量的存储空间中 write 写入

(2)把数据从变量的存储空间中取出 read 读取

系统将变量名和变量对应存储空间关联起来,我们就能通过变量名访问那个存储空间

实际上通过 存储空间的内存编号进行访问。

能不能直接通过 存储空间的编号 来访问存储空间???

---> 指针

1、什么指针

为了方便管理内存,在内存中以字节为单位对内存单元进行依次编号(编号是一个整数常量)

地址:

系统分给每个对象(变量、函数...)的占有内存都有一个编号(起始编号),这个内存编号就是所说的地址。也称之为 对象的首地址,它标志着 该对象的内容从哪里开始。

在 C语言中 地址 <==> 指针 概念差不多,指针是有类型的 地址

变量的地址/指针 是可以直接打印出来的,用 %p

%p <--> 地址(& 取地址符)

&对象名 取该对象的地址(存储空间的起始编号)

2、指针变量

用来保存 地址(内存空间编号) 的变量,一般用于保存其它 对象的地址

指针变量的定义语法:

数据类型 *指针变量名;

数据类型 *指针变量名 = 其指向的那个对象的地址;

数据类型、指针变量名 合法即可

指向的那个对象?

指针变量保存了谁的地址,就称之为 指向的那个对象

eg:

int a = 10;

int *p = &a; // p 指向了 a,p 保存了 a 的地址

// 定义一个指针变量 p

// * 指针变量说明符(定义时),声明了 p 是一个指针变量,指向了一个 int 类型数据 的指针变量

// 等价于 int *p; p = &a

int *p,q; // p 是指针变量,q int变量

※※※※注意

int *p; // p 未初始化,p 指向谁??? 没有明确指向的对象,称之为 野指针

int *q = NULL; // q 称之为空指针

参考示例代码 CS2415/一阶段/1C语言/07.12_指针/demo1/test1.c

3、两个与指针相关运算符

& 取地址运算符 单目运算符

* 解引用运算符 单目运算符

&对象名 取对象名的地址(存储空间的起始地址)

*地址 地址对应某个对象

eg:

int a = 100;

int *p = &a; // int *p;p = &a;

*p ???

*p <==> *(&a) <==> a // a 的地址对应的对象就是 a

分析:

double d = 3.1415;

double *p = &d; // 定义一个指针变量 p,保存了 d 地址;p 指向了 d

printf("d = %f\n",d); // 3.1415

printf("*p = %f\n",*p); // 3.1415

*p = 5.21; // *p ---> d

printf("*p = %f\n",*p); // 5.21

printf("d = %f\n",d); // 5.21

double *q; // 野指针,不知道对象是哪个

*q = 2.333; // 对象不知道是哪个,可能导致不可预测的结果

// 内存非法访问就会出错

printf("*q = %f\n",*q); // 不可预测的结果

int *r = NULL; // 空指针

printf("1111\n"); // 输出语句找哪里有错误

*r = 666; // 空指针不能 * ,一定是错误的 "段错误"

printf("wwww\n");

printf("*r = %d\n",*r); // 不可预测的结果

※※※※注意

解引用 空指针 一定错误,"段错误"

解引用 野指针 有问题,不可预测的结果 如果是出错 "段错误"

建议定义 指针变量时,尽量初始化;暂时不知道,赋值为 NULL

参考示例代码 CS2415/一阶段/1C语言/07.12_指针/demo1/test2.c

4、指针类型空间大小

int a = 10;

long b = 20;

double c = 30;

int *p = &a; // p 指向了 a,保存 a 的地址

long *q = &b; // q 指向了 b,保存 b 的地址

double *r = &c; // r 指向了 c,保存 c 的地址

printf("p = %p\n",p);

printf("&p = %p\n",&p);

printf("q = %p\n",q);

printf("r = %p\n",r);

p q r 这三个指针变量占多少个字节???

都是 8 字节

指针类型所占字节大小 由 机器寻址方式有关,现在 基本是 8 字节

不要认为是 和指向的对象有关 

一般来说 X86(32bits) 占 4 字节; X64 (64bits) 占 8 字节

参考示例代码 CS2415/一阶段/1C语言/07.12_指针/demo1/test3.c

5、函数与指针

(1)参数为指针

分析:

void swap1(int a,int b) // 形不改实 形参a 保存实参a 的值 3,形参b 保存实参b 的值 4

{

int t = a;

a = b;

b = t; // 形参a 保存 4,形参b 保存 3

printf("swap1:a = %d,b = %d\n",a,b); // 4 3

}

void swap2(int *a,int *b) // 形参a 指向了实参a 的地址 0x1234(3),形参b 指向了实参b 的地址 0x1238(4)

{

int *t = a; // t 指向了实参a t = 0x1234(3)

a = b; // 形参a 指向了实参b a = 0x1238(4)

b = t; // 形参b 指向了实参a b = 0x1234(3)

printf("swap2:a = %d,b = %d\n",*a,*b);

}

void swap3(int *a,int *b) // 形参a 指向了实参a 的地址 0x1234(3),形参b 指向了实参b 的地址 0x1238(4)

{

int t = *a; // t 保存了实参a的值 t = 3

*a = *b; // 实参a = 实参b = 4

*b = t; // 实参b = t = 3

printf("swap2:a = %d,b = %d\n",*a,*b);

}

int main()

{

int a = 3,b = 4;

swap1(a,b);

printf("main:a = %d,b = %d\n",a,b); // 3 4 

a = 3,b = 4;

swap2(&a,&b);

printf("main:a = %d,b = %d\n",a,b); // 3 4 

a = 3,b = 4;

swap3(&a,&b);

printf("main:a = %d,b = %d\n",a,b); // 4 3

return 0;

}

参考示例代码 CS2415/一阶段/1C语言/07.12_指针/demo1/test4.c

分析以下代码的结果:

void test(int *p)

{

//printf("test:data = %d\n",data); // 未定义使用,在 data 作用域外不能只能使用变量名进行访问

*p = 300;

printf("test:*p = %d\n",*p);

}

int main()

{

int data = 1234;

printf("main:data = %d\n",data);

test(&data);

printf("main:data = %d\n",data);

return 0;

}

参考示例代码 CS2415/一阶段/1C语言/07.12_指针/demo1/test5.c

(2)返回值为指针

如果函数需要返回一个 地址,函数头 

数据类型 *函数名(形参列表)

分析以下代码的结果:

int * test1()

{

int data = 100;

return &data; // warning: function returns address of local variable 

}

int * test2()

{

static int data = 100; // 局部变量被 static 修饰

return &data;

}

int * test3()

{

int data = 100;

int *p = &data;

printf("&data = %p,p = %p\n",&data,p);

return p; // 局部变量 p 的值

}

int main()

{

int *p1 = test1();

printf("p1 = %p\n",p1);

int *p2 = test2();

printf("p2 = %p\n",p2);

int *p3 = test3();

printf("p3 = %p\n",p3);

return 0;

}

参考示例代码 CS2415/一阶段/1C语言/07.12_指针/demo1/test6.c

※※※※ 注意

函数中未定义的局部变量,在作用域是不能直接通过 对象名 访问,可以通过 指针进行访问

不能返回 普通局部变量的地址;返回 普通局部的值(如果值是地址也能返回)

6、数组与指针

数组中各个元素和普通变量,也有自己的地址,且数组相邻的元素的地址也相邻

eg:

int arr[5];

// 定义了一个数组,数组名为 arr,有 5 个元素,每个元素都是 int 一共占了 20 字节

想要定义一个指针变量保存 arr[0] 的地址,怎么搞???

int *p = &arr[0];

数组名有些情况下可以看做是数组的首地址(数组起始的地址,也就是第一个元素的地址)

&数组名[0]

假设:

&arr[0] ---> 0x7ffc1d3b2810

&arr[1] ---> 0x7ffc1d3b2814

&arr[2] ---> 0x7ffc1d3b2818

&arr[3] ---> 0x7ffc1d3b281c

&arr[4] ---> 0x7ffc1d3b2820

那么:

0x7ffc1d3b2810 + 4 == 0x7ffc1d3b2814

&arr[0] + 4 == &arr[1]

if(&arr[0] + 4 == &arr[1])

{

printf("菜就多练!!!\n");

}

else

{

printf("小邓错了!!!\n");

}

参考示例代码 CS2415/一阶段/1C语言/07.12_指针/demo1/test7.c

※※※※ 指针运算(加减)

p +/- i p 是一个指针,i 是一个整数

不是简单的数值运算,而是运算 i 个指针的步长

指针的步长:

是 指针指向的对象的类型所占字节数目

eg:

char *p = NULL; // p 的步长 1 

int *q = NULL; // q 的步长 4

练习:

int brr[10] = {1,3,5,7,9,11,13,15,17,19};

int *p = brr; // p = &brr[0]

printf("*p = %d\n",*p); // brr[0] --> 1

printf("*p+1 = %d\n",*p+1); // brr[0] + 1 --> 2

printf("*(p+1) = %d\n",*(p+1)); // *(&brr[0] + 1) --> *(&brr[1]) --> brr[1] --> 3

printf("*p++ = %d\n",*p++); // *p --> 1 p = p+1 = &brr[1]

printf("*p+5 = %d\n",*p+5); // brr[1] + 5 --> 8

printf("*++p = %d\n",*++p); // p = p+1 = &brr[2] brr[2] ---> 5

int *q = &brr[3];

printf("*q = %d\n",*q); // brr[3] --> 7

printf("*(q+4) = %d\n",*(q+4)); // *(&brr[3]+4) --> *(&brr[7]) --> brr[7]-->15

printf("*brr+6 = %d\n",*brr+6); // *(&brr[0]) --> brr[0] + 6 --> 7

printf("*(brr+6) = %d\n",*(brr+6)); //*(&brr[0] + 6) --> brr[6] --> 13

// printf("*brr++ = %d\n",*brr++); // brr = brr + 1 error

数组名作为地址时,是一个 常量

参考示例代码 CS2415/一阶段/1C语言/07.12_指针/demo1/test7.c

分析:

以下代码输入 hello 输出结果

int main()

{

char s[10]={0};

scanf("%s",s+2);

printf("s = %s\n",s); // s = 

printf("s = %s\n",s+3); // s = ello

}

参考示例代码 CS2415/一阶段/1C语言/07.12_指针/demo1/test7.c

char ch[] = "howareyou";

char *p = ch; // p = &ch[0]

(int *)p++;

printf("*p = %c\n",*p);

7、数组作为函数参数

eg:

写一个函数,求一个一维数组的所有元素和

一般需要 两个参数:

数组名

数组元素的个数

int array_sum(int arr[],int num) // int arr[] <==> int *arr

{

int i,sum = 0; // 元素的下标,元素之和

for(i = 0;i < num;i++)

{

sum += arr[i];

}

return sum;

}

int main()

{

int a[3] = {1,2,3};

printf("a 数组所有元素之和为: %d\n",array_sum(a,3));

int b[4] = {1,2,3};

printf("b 数组所有元素之和为: %d\n",array_sum(b,4));

int c[5] = {5,5,5,5,5};

printf("c 数组所有元素之和为: %d\n",array_sum(c,5));

}

注意:

一维数组作为参数 形参为 int a[],不是一个数组,而是一维数组 退化 成了 一个指针

作业:

1、写一个函数,求一个 字符串 的长度(可以用 strlen 验证)

要求 字符串 使用字符数组 进行保存!!!

对字符数组保存的 字符串 每个字符都进行判断是否为 '\0'

不是 '\0' 计数++,是 '\0' 结束计数,返回 长度!!!

2、高数巨占座位问题

见 高数巨占座位.bmp

输入 教室座位行列数,及需要连续的多少个位置

再输入 教室已经占座情况 1 代表已经占了 0 代表没人

输出 一共有多少种占座方式

3、计算水洼的个数

1 1 1 1 1

0 0 0 1 0

1 0 1 0 1

0 1 0 0 1

1 0 1 1 1

上面有 5 个水洼

找到一个 0 计数 + 1

并且将这个 0 置为 1,0 上下左右是 0 也要置为 1;上下左右为 0 上下左右置为 1