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