当前位置:网站首页>C语言学习-Day_06
C语言学习-Day_06
2022-06-27 09:49:00 【坏坏-5】
- 学习参考B站郝斌老师的视频,文章内的源码如有需要可以私信联系。
指针
- 指针就是地址,地址就是指针
- 地址是内存单元的编号(从0开始的非负整数)
- 指针变量是存放地址的变量
- 指针可以直接对主函数中的变量进行修改,但是需要把主函数变量的地址传给定义函数
- 指针只能进行减运算,不能进行加乘除运算

- 控制线:负责控制数据是调入CPU还是调出CPU
- 数据线:负责传输数据
- 地址线:负责内存条中哪个内存单元编号中的数据的读取或写入
指针概述
int *表示指针变量的类型*p表示以指针变量为地址的值&p表示指针变量存放的地址
/*指针概述*/
# include <stdio.h>
int main(void)
{
int * p; //int *表示p变量存放的是int类型变量的地址,p是变量名
int i = 3;
p = i; //error,p存放的是整形地址,i是整形数据
p = 5; //error,原因同上
p = &i; //正确,&i表示去变量i的地址
return 0;
}
p = &i变量 p 保存了变量 i 的地址,因此 p 指向 i- p 不是 i ,i 也不是 p,修改值不影响彼此的值
- 如果指针变量指向普通变量,则
*指针变量就等同于普通变量
- 所有出现
*指针变量和普通变量的位置都可以互换
例:
/*指针变量&普通变量*/
# include <stdio.h>
int main(void)
{
int * p; //指针变量,int *表示的是变量p的数据类型,p是变量名
int i = 5;
p = &i; //指针变量指向普通变量
printf("变量i的值是:%d\n", i);
printf("变量i存放的地址是:%d\n", &i); //输出的是变量i存放的地址
printf("变量p的值是:%d\n", p); //输出的是变量p的值
printf("变量p的地址是:%d\n", &p); //输出变量p存放的地址
printf("*p的值是:%d\n", *p); //*p可以替换成i
i = 3;
printf("变量i的值是:%d\n", i);
printf("变量p的值是:%d\n", p); //修改变量i的值,不影响变量p的值
*p = 3; //表示指针变量p指向的值是3
printf("*p的值是:%d\n", *p);
return 0;
}
/*运行结果*/
变量i的值是:5
变量i存放的地址是:1703720
变量p的值是:1703720
变量p的地址是:1703724
*p的值是:5
变量i的值是:3
变量p的值是:1703720
*p的值是:3
Press any key to continue

- 定义指针变量 p,定义整形变量 i
- 变量 p 的地址是:1703724,变量 p 的值是变量 i 的地址,即1703720
- 变量 i 的地址是:1703720,变量 i 的值是 5
*p表示 p 指向的地址存放的值,即1703720存放的值(i的值)5- 修改变量 i 的值,实际上 i 的存放地址没有变化,所以变量 p 也不会被影响
- 修改变量 p 的值,p 存放的是 i 的地址,所以也不会影响 i 的值
指针的重要性
- 指针可以表示一些复杂的数据结构
- 可以快速传递数据
- 使函数返回一个以上的值
return只能返回一个值,返回一个值,就会结束函数
- 能直接访问硬件
- 可以方便的处理字符串
指针常见错误
例:
/*指针常见问题*/
# include <stdio.h>
int main(void)
{
int * p;
int i = 5;
printf("%d\n", p); //变量p没有初始化,使用垃圾数据(-858993460)填充
*p = i;
printf("%d\n", *p);
return 0;
}
- 变量 p 没有被初始化,会使用垃圾数据填充
- 系统只分配变量 p、i的地址空间,而
*p = i;则是将垃圾数据对应地址空间的值进行修改,则会存在问题
例:
/*指针常见问题*/
# include <stdio.h>
int main(void)
{
int i = 5;
int * m;
int * n;
m = &i; //m的值为存放i变量的地址
*n = m; //error,变量n没有初始化,为垃圾数据,且*n(int)和m(int *)的数据类型不同
*n = *m; //error,数据类型相同,但变量n没有初始化,无法修改垃圾数据的值
m = n; //n是垃圾数据,n赋值给m,m也是垃圾数据
printf("%d\n", *n);
/* 变量n的内存单元被分配给本程序,所以可以读写n内存单元中的数据 但变量n没有初始化,使用垃圾数据填充,所以本程序不能读写*n的数据 因为*n所代表的内存单元,即垃圾数据使用的内存单元,本程序没有权限读写其中的值 */
return 0;
}

- 变量 p 没有初始化,所以系统使用垃圾数据填充
- 所以变量 p 的地址和值都是使用垃圾数据
- 因为垃圾数据的内存单元没有分配给本程序,所以本程序不能读取垃圾数据的内存单元编号和数值
- 即:没有权限读写
*p的内容
经典指针程序
- 互换两个数字
例:指针程序,互换两个数字
/*使用指针,互换两个数*/
# include <stdio.h>
void Exchange_1(int , int); //函数声明,可以不用写形参
void Exchange_2(int *, int *);
void Exchange_3(int *, int *);
void Exchange(int * p, int * q) //正确,修改的*p,*q的值,即修改了主函数内a,b的值
{
int t; //修改*p,*q的值,则t需要定义为int类型
t = *p;
*p = *q; //*p是变量p存储地址的变量值,即主函数变量a的值
*q = t;
}
int main(void)
{
int a = 3;
int b = 5;
Exchange(&a, &b); //定义函数中是int *类型,所以要取a,b的地址
printf("a = %d, b = %d\n", a, b);
return 0;
}
//不能实现互换
void Exchange_1(int a, int b) //改变的是定义函数内的a,b的值,并没有改变主函数内的值
{
int t;
t = a;
a = b;
b = t;
return;
}
//不能实现互换
void Exchange_2(int * p, int * q) //改变的是定义函数内的p,q的值,并没有改变主函数内的a,b的值
{
int * t; //互换p,q的值,则t需要定义为int *类型
t = p;
p = q;
q = t;
return;
}
/*运行结果*/
a = 5, b = 3
Press any key to continue
- 星号代表的意义
- 乘法运算
- 定义指针变量
- 取指针变量所代表的地址的值
指针和数组
- 指针和一维数组
- 数组名是指针常量,存放的是一维数组的第一个元素的地址
- p是指针变量,则p[i]等价于*(p + i)
- 数组名是指针常量,不能改变
- 指针变量指向同一块连续空间的不同存储单元,则两个指针变量可以相减
- 指针和二维数组
例:一维数组名与数组的第一个元素的关系
/*一维数组名与数组的第一个元素*/
# include <stdio.h>
int main(void)
{
int a[5];
printf("%#X\n", &a[0]); //%#X表示十六进制输出
printf("%#X\n", a);
return 0;
}
- 数组名对应的就是数组中第一个元素对应的地址
%d对int类型数据输出%ld对long int类型数据输出%f对float类型数据输出%lf对double类型数据输出%c对char类型数据输出
/*运行结果*/
0X19FF1C
0X19FF1C
Press any key to continue
例:确定一个一维数组需要几个参数
/*确定一个一维数组需要几个参数*/
# include <stdio.h>
void f(int * p, int len) //主函数中的a表示的是数组中第一个元素对应的地址,所以定义变量要使用int *类型
{
int i;
for (i = 0; i < len; ++i)
printf("%d ", *(p + i)); //数组中的值存放的地址是连续的
printf("\n");
}
int main(void)
{
int a[5] = {
-1, -2, 0, 4, 5};
int b[6] = {
1, 2, 3, 4, 5, 6};
int c[10] = {
1, 2, 3, 4};
f(a, 5);
f(b, 6);
f(c, 10);
return 0;
}
- 数组名表示的是第一个元素的地址,对于int类型数据,没有特殊的值标识着数组的结束,所以需要再读取数组的长度来结束数组
- 对于char类型的数据,
\0标识着字符的结束,所以对于char类型的数组,可以不用读取数组的长度
/*运行结果*/
-1 -2 0 4 5
1 2 3 4 5 6
1 2 3 4 0 0 0 0 0 0
Press any key to continue
例:
/*确定一维数组需要的参数*/
# include <stdio.h>
void f(int * p, int len)
{
p[3] = 5; //p[3]等价于*(p + 3),改变的是p + 3这个地址对应的值
}
int main(void)
{
int a[5] = {
1, 2, 3, 4, 5};
printf("%d\n", a[3]); //输出4
f(a, 5);
printf("%d\n", a[3]); //a[3]对应的是*(a + 3),输出的是a + 3这个地址对应的值
return 0;
}

- p和a表示的都是数组a中第一个元素的存放地址
&a[0]- 执行函数f后,
p[3]等价于*(p + 3),
- 对
p[3]重新赋值,即对p + 3这个地址(即a[3]的地址)存放的数据重新赋值- 所以执行函数后,输出
a[3]的值即为函数中重新定义的值- 即
p[3]和a[3]代表的是同一个变量
/*运行结果*/
4
5
Press any key to continue
例:指针的运算
/*指针的运算*/
# include <stdio.h>
int main(void)
{
int i = 1;
int j = 2;
int * p = &i;
int * q = &j;
int a[5];
p = &a[1];
q = &a[4];
printf("q与p相隔%d个存储单元!\n", q-p); //指针变量在同一块连续单元的不同存储空间才可以相减
return 0;
}
/*运行结果*/
q与p相隔3个存储单元!
Press any key to continue
- 指针变量无论指向的数据类型是什么,都只占4字节
- char类型,占用1字节
- int类型,占用4字节
- double类型,占用8字节
sizeof(数据类型)返回值即为数据类型所占的字节数
例:
/*指针变量所占字节数*/
# include <stdio.h>
int main(void)
{
char ch = 'A'; //字符类型数据不能省略''
int i = 5;
double x = 3.3;
char * p = &ch;
int * q = &i;
double * r = &x;
printf("%d %d %d\n", sizeof(p), sizeof(q), sizeof(r));
return 0;
}
- 指针变量都是只存贮数据的第一个字节的地址,所以只占用4字节
- 根据指针变量定义的类型,再从第一个字节向后读取相应数据类型所占用的字节数
/*运行结果*/
4 4 4
Press any key to continue
动态内存分配
- 传统数组的缺点
- 数组长度必须事先指定,且只能事长整数,不能是变量
- 定义的数组分配的空间不能手动释放,只有在本函数运行完毕时,才会由系统自动释放
- 数组的长度不能在函数的运行过程中动态扩充或缩小
- 函数运行期间,函数内定义的数组可以被其他函数调用,函数终止,则该数组不能被其他函数调用
- 为什么需要动态内存的分配
- 动态数组解决了传统数组的缺陷
- 静态内存和动态内存的比较
- 静态内存由系统自动分配,由系统自动释放
- 静态内存是在栈分配的
- 动态内存是由程序员手动分配的,手动释放
- 动态内存是在堆分配的
- 跨函数使用内存的问题
例:动态数组的构造
/*malloc函数*/
# include <stdio.h>
# include <malloc.h> //使用malloc函数需要使用此头文件
int main(void)
{
int i = 5; //静态分配,int类型分配了4字节
int * p = (int *)malloc(4); //请求系统分配4字节
/* int * p中p被分配的空间是静态分配的,p所指向的空间是动态分配的 */
*p = 5; //*p代表的即为一个int类型变量,地址空间是动态分配的
free(p); //把p所指向的内存释放
return 0;
}
- 要使用malloc函数,必须添加malloc.h的头文件
- malloc函数只有一个形参,形参必须时整形,代表请求系统分配的字节数
- malloc函数只能返回第一个字节的地址
- 所以malloc函数前必须强制指定数据类型,方便从第一个字节向后读取多少字节
int * p = (int *)malloc(4);中,变量p的地址空间是静态分配的,变量p指向的地址空间是动态分配的
例:malloc函数的用法
/*malloc函数的用法*/
# include <stdio.h>
# include <malloc.h>
void f(int * q)
{
*p = 200; //error,p是主函数中的变量,不能跨函数调用
q = 200; //error,q是int *类型,只能存放地址,不能存放整形数据
*q = 200; //正确,给q指向的地址重新赋值
free(q); //把q指向的内存释放
}
int main(void)
{
int * p = (int *)malloc(sizeof(int)); //sizeof(int)返回值是int所占的字节数
*p = 10;
printf("%d\n", *p); //输出变量p指向的值
f(p); //p是int *类型,所以定义函数调用时,形参也必须是int *类型
printf("%d\n", *p); //在调用函数中,q指向的空间为动态分配的地址空间,被释放,所以不能再使用*p读取p指向的空间中的内容
return 0;
}

- 主函数定义指针变量p,p的地址空间是静态分配的
- p的值为动态分配的4字节int类型的存储空间
*p = 10表示给这个动态分配的地址空间存放一个值,为10- 定义函数,将p发送给指针变量q,即q也指向动态分配的地址空间
- 在f函数中,使用
* q = 200重新向这个动态分配的地址空间存放一个值,为200- 使用
free(q);将q指向的动态分配的地址空间释放,即这个动态分配的地址空间不再存放任何值,且函数没有权限再对这个释放的空间进行读写- 所以不能再使用
*p读取之前动态分配的地址空间的值
例:静态一维数组示例
/*静态一维数组举例*/
# include <stdio.h>
int main(void)
{
int a[5];
/* int类型的数据占用4字节,总共5个元素,所以数组a[5]占用20字节 a用于存放第一个元素的地址,a[0]表示第一个元素 第二个元素的地址为a + 1,值为从a存放的地址向后读取4字节 */
return 0;
}
例:动态一维数组示例
/*动态一维数组举例*/
# include <stdio.h>
# include <malloc.h>
int main(void)
{
int len;
int * p;
int i;
//动态构造一维数组
printf("请输入您想存放的元素个数:");
scanf("%d", &len);
p = (int *)malloc(4 * len); //一个int类型元素占用4字节
//对动态一维数组进行赋值
for (i = 0; i < len; ++i)
{
printf("请输入第%d个元素的值:", i+1);
scanf("%d", &p[i]);
}
//对一维数组进行输出
printf("一维数组的内容是:");
for (i = 0; i < len; ++i)
printf("%d, ", p[i]);
printf("\n");
free(p); //释放动态分配的数组
realloc(p,100); //重新动态分配给变量p的地址空间是100字节
return 0;
}
/*运行结果*/
请输入您想存放的元素个数:3
请输入第1个元素的值:1
请输入第2个元素的值:2
请输入第3个元素的值:3
一维数组的内容是:1, 2, 3,
Press any key to continue
- p存放的是数组的第一个元素的地址,后一个元素的地址为
p + 1,后一个元素的值为*(p + 1)
多级指针
例:多级指针
/*多级指针*/
# include <stdio.h>
int main(void)
{
int i = 5;
int * p = &i; //p用于保存变量i的地址
int ** q = &p; //q用于保存指针变量p的地址
int *** r = &q; //r用于保存变量q的地址
r = &p; //error,因为变量r的类型是int ***,不能存放变量p的地址
printf("i = %d", ***r); //***r表示的即为变量i的值
return 0;
}
指针和函数
- 跨函数使用内存
- 静态变量不能跨函数使用
- 动态变量可以跨函数使用
例:静态变量不能跨函数使用
/*静态变量不能跨函数使用*/
# include <stdio.h>
void f(int ** q) //q只占用4字节,存放指针变量p的地址
{
int i = 5;
*q = &i; //
}
int main(void)
{
int * p;
f(&p);
printf("%d\n", *p); //语法正确,逻辑存在错误,权限越界
return 0;
}

- 主函数中定义指针变量p,未初始化,将指针变量p的地址发送给函数f
- 函数f的形参类型必须是
int **,因为指针变量q存放的是指针变量的地址- 函数f内定义变量i,赋值为5,地址为静态分配
*q表示指针变量q存放的地址对应的值,即指针变量p的值,p没有指向变量,未初始化- 将变量i的地址赋值给指针变量q存放的地址指向的值,即
p = &i- f函数执行完后,变量i的地址被释放,则再执行
*p时,不能读写被释放的i的地址空间
例:动态内存可以跨函数使用
/*动态内存可以跨函数使用*/
# include <stdio.h>
# include <malloc.h>
void f(int ** q)
{
*q = (int *)malloc(sizeof(int)); //sizeof返回该类型所占字节数
q = 5; //error,因为q存放的是指针变量p的地址
*q = 5; //error,因为*q表示指针变量p存放的垃圾数据的地址
**q = 5; //正确,表示指针变量p存放的地址对应的值
}
int main(void)
{
int * p;
f(&p);
printf("%d\n", *p); //输出*p的值,即5
return 0;
}
- 使用
sizeof(int)可移植性更强,不同的机器和软件对于int类型的数据所占字节数可能不同- 动态分配的空间是由堆分配的,函数执行结束,地址空间不会被释放,需要手动释放
free(q)
- 静态分配的地址空间是由栈分配的,函数执行结束,该地址空间就会被释放
以上内容均属原创,如有不详或错误,敬请指出。
边栏推荐
- ucore lab3
- 使用Aspose.cells将Excel转成PDF
- 如何获取GC(垃圾回收器)的STW(暂停)时间?
- R语言plotly可视化:plotly可视化基础小提琴图(basic violin plot in R with plotly)
- torch.utils.data.RandomSampler和torch.utils.data.SequentialSampler的区别
- R langage plotly visualisation: visualisation de plusieurs histogrammes normalisés d'ensembles de données et ajout d'une courbe de densité KDE à l'histogramme, réglage de différents histogrammes en ut
- Google browser chropath plug-in
- Scientists develop two new methods to provide stronger security protection for intelligent devices
- Quartz (timer)
- 12个网络工程师必备工具
猜你喜欢

产品力对标海豹/Model 3,长安深蓝SL03预售17.98万起

How do I get the STW (pause) time of a GC (garbage collector)?

Take you to play with the camera module

Quelques exercices sur les arbres binaires

2-4Kali下安装nessus

Understand neural network structure and optimization methods

隐私计算FATE-离线预测

通俗易懂理解樸素貝葉斯分類的拉普拉斯平滑

前馈-反馈控制系统设计(过程控制课程设计matlab/simulink)

C# Any()和AII()方法
随机推荐
Five page Jump methods for wechat applet learning
Freemarker
【STM32】HAL库 STM32CubeMX教程十二—IIC(读取AT24C02 )
R语言plotly可视化:可视化多个数据集归一化直方图(historgram)并在直方图中添加密度曲线kde、设置不同的直方图使用不同的分箱大小(bin size)、在直方图的底部边缘添加边缘轴须图
prometheus告警流程及相关时间参数说明
E+h secondary meter repair pH transmitter secondary display repair cpm253-mr0005
CPU design (single cycle and pipeline)
谷歌浏览器 chropath插件
.NET 中的引用程序集
Decompile the jar package and recompile it into a jar package after modification
【OpenCV 例程200篇】212. 绘制倾斜的矩形
Use CAS to complete concurrent operations with atomic variables
感应电机直接转矩控制系统的设计与仿真(运动控制matlab/simulink)
JS array splicing "suggested collection"
Understand neural network structure and optimization methods
Only one ConfirmCallback is supported by each RabbitTemplate 解决办法
片刻喘息,美国电子烟巨头禁令推迟,可暂时继续在美销售产品
Installation and use of SVN version controller
10 common website security attack means and defense methods
Easy to understand Laplace smoothing of naive Bayesian classification