接下来就让小奔来带着大家简单的了解一下C语言,不过看起来挺多的,哈哈哈,写了大概八十个小时吧,不过小奔是在20几天前才开始学的哦,这里只是简单带着大家对C语言有一个轮廓的认识,小奔会持续更新的
入门第一个语言——C语言
- 0. 看前须知
- 1. 学C语言前期的准备
- 1.1 建议
- 1.2 安装相关的软件
- 2. 什么是C语言
- 3. 第一个c语言程序
- 4.数据类型
- ✨5. 变量和常量
- ✨5.1变量
- ✨5.1.1定义变量
- ✨5.1.2变量的分类
- ✨5.1.3变量的使用
- ✨5.1.4 变量的作用域和生命周期
- ✨5.1.4.1 作用域
- ✨5.1.4.2 生命周期
- ✨5.1.5 变量的命名:
- ✨5.2常量
- ✨5.2.1 常量的定义
- ✨5.2.2 常量的分类
- ✨5.2.2.1 字面常量
- ✨5.2.2.2 const修饰的常变量
- ✨5.2.2.3 #define 定义的标识符常量
- ✨5.2.2.4 枚举常量
- 6. 字符串
- 7. 转义字符
- 8.注释
- 9. 顺序语句
- 10. 选择语句
- 11. 循环语句
- 12. 函数
- 13. 数组
- 13.1数组的定义
- 13.2数组的小标
- 13.3数组的使用
- 14. sizeof和strlen头文件的的区别
- 15. 操作符
- 16. 关键字
- 17. 关键字typedef
- 18. 关键字static
- 18.1 static修饰的局部变量
- 18.2 简单介绍一下void
- 18.3 static修饰的全局变量
- 18.4 static修饰的函数
- 19. 关键字register
- 20. #define定义的宏
- 21. 指针
- 21.1 内存
- 21.2 指针变量的大小
- ⛺22. 结构体
- ⛺22.1 结构体的初始化
- ⛲下载链接
目录
0. 看前须知
对于我们这些才步入编程界的小生来说,有一个方向尤为重要,但是无论选择哪个方向,有一门计算机语言是我们都要去学习的,这门计算机语言就是C语言
为什么说C语言是我们都要学习的呢?
我问过鹏哥,如果你想入手编程的话,而且也是一门比较经典的语言,以这门语言作为编程入门的语言是比较合适的
C语言在编程领域的用途的话,一个C++开发工程师他必然要懂C语言对吧,因为C语言和C++本质上是不分家的,在学完C之后,再延伸往后学习C++,这个时候就可以做C++开发工程师对吧,如果你学了C之后,要走Linux方向,那这个时候呢可以做Linux C的一个开发,甚至可以做底层的驱动开发,都是没有任何问题的,或者做一些嵌入式开发,驱动开发,包括系统开发都是需要C ,Linux这一套的
接下来,学完C语言之后,对于其他编程语言的学习也更加容易一些,因为C语言简单对吧,容易上手,而且它是一门偏底层的计算机语言,它让你更加了解内存底层的一些原理之类的,学完之后,你再学习其他计算机语言都会变得非常容易,得心应手
1. 学C语言前期的准备
1.1 建议
跳转到目录末尾
1.喜欢分享
要习惯去分享,可以向我一样写博客,就是要去把自己的观点分享出来,不能闭门造车,善于分享的人对以后的工作有非常的优势。(找工作的时候,哎,把自己的博客链接一贴,面试官看了就能知道你是一个喜欢分享的人,会有很好的印象。)
2.要去实践,去刷题
学习编程必须去实践,去刷题,我推荐一些网站,可能还有不知道的其他很不错的刷题网站,我了解后会补充在这里。
链接:
牛客网
3.建立一个自己的仓库
学编程要建立一个自己的仓库,我听过程序猿敲过的代码,它能运行就行,你稍微一改,可能就改了一个数,就运行不了,但是你可能就会忘记改的哪里,或者电脑死机,你写的代码就在里面,取不出来,这时候如果有一个仓库储存着这些代码,需要的时候重新把这个代码从仓库里拿出来,就不会怕上面这种情况。
我使用的是gitee,这个跟博客有一个相同的作用,就是面试的好工具,大公司都喜欢这个,里面还有一个贡献度,
你每上传一次代码,上面都会有记录,你这天上传的次数越多,绿点就会越深,而且也可以点击这个绿点来查看这一天所上传的代码,在面试的时候非常有帮助。所需要的软件都存在百度网盘的,就在最下面链接中。
1.2 安装相关的软件
跳转到目录末尾
1.如何安装编译器呢?
快看鹏哥的视频
(吹爆他,太强了)
2.如何使用
gitee
呢?
还是看鹏哥的视频
(鹏哥是真的强)
2. 什么是C语言
跳转到目录末尾
c语言是一门计算机语言(比如c++/java/python…),常用于底层开发,跟人类交流的语言类似,它是人与计算机交流的语言。
计算机语言的发展:低级–>高级
发展到C语言时,已经是高级语言了
语言形式 例:
二进制的指令 1001010 汇编语言 ADD
C语言的国际标准:ANSI C(常用的是C89,C90还包括C99,C11…)
编译C语言的编译器主要有
MSVC
(作者目前使用),GCC,Clang,WIN-TC,Turbo C等
C/C++是编译型的语言,要把源代码(
test.c
), 编译为程序(test.exe
), 需要4个过程(下面四个过程)
初步了解编译器运行过程:
我们都知道计算机使用的是
二进制
语言,我们生活在十进制的世界,
二进制就例如10010101
,逢二进一;
八进制就例如157643235
,逢八进一;
十进制就例如95124921
,逢十进一;
十六进制就例如A5B46
,逢十六进一。(A指10,B指11,依次类推)
二进制八进制十进制十六进制 1 1 1 1 102 2 2 111 7 7 7 1000108 8 1001119 9 10101210A 11011515E 1110161610 10110010111313715 2CB
那么计算机为什么要使用
二进制
语言而不去使用八进制
,十进制
,甚至十六进制
的语言呢,这是物理方面的原因,只有二进制
比较好实现,
灯亮则是1
灯灭则是0
从物理方面好实现,其他的进制都不太好实现,这里就先简单了解一下
3. 第一个c语言程序
跳转到目录末尾
我们在敲出第一个C语言程序前先思考一下,为什么要写代码?
当然是为了解决生活中的问题(比如购物、点餐、看电影等)
1.打开VS
跳转到目录末尾
2.创建新建项目
跳转到目录末尾
3.选择空项目
跳转到目录末尾
4.输入项目名称并创建
跳转到目录末尾
我这里推荐把项目名称改为当天的日期,存放在一个统一的位置,方便自己查找,也方便自己把代码上传仓库
5.创建源文件
跳转到目录末尾
在解决方案资源管理器那里的源文件右击,就可以弹出来那些选项(如果一不小心把解决方案资源管理器关了,不要关掉整个软件,你可以点最上面的视图,再点击解决方案资源管理器就可以了)
注意:记得把名称的后缀.cpp
改为.c
C的语法和C++的语法是有所差别的
.cpp后缀 编译器会按照C++的语法来编译代码 .c后缀 编译器会按照C的语法来编译代码
我们目前使用的是C语言,在后面的C++学习篇中再来使用
.cpp
后缀
6.写代码
跳转到目录末尾
//
这个是写注释的,即把这一横行的代码无效化
vs2019环境如何运行代码:
运行按键ctrl+f5
或fn+ctrl+f5
#include//c语言代码中一定要有main函数//也就是主函数//printf是一个库函数,是专门用于打印数据的//标准的main(主)函数写法int main(){printf("Hello world!");return 0;}//古老的写法-不推荐//void main()//{////}
运行的结果:
打印了一个
Hello world!
解释:
跳转到目录末尾
1.
#include
是在程序编译之前要处理的内容,称为编译预处理命令(还记得上一个博客里面编译器运行的四个过程吗,第一个过程就是预处理)
std—指标准(standard)
i—指输入(input)
o—指输出(output)
2.
int main() { return 0; }
是C语言main函数(也叫主函数)的一种声明方式(我目前写的简单的代码都要套用这个模板,还不清楚的看之后发的博客所示代码就能明白),这里的int
是数据类型(下一个博客会讲)里面的整型(比如6,48,99
等的整数),int
对应后面的是return 0
;
return 0
即返回值为0
,因为int
是整型,所以要求返回值也符合int
,这个0
也可以是其他数,但我们俗称规定的就是0
,就不要搞特殊写4
,5
什么其他的数字了。
3.
printf(" ");
库函数,专门用来打印数据的,c语言提供这个库函数,是不是你的?当然不是,它是别人给你的,所以你要用的时候需要打个招呼,这个头文件#include
就是用来打招呼的,说我要用这个printf
这个库函数
4.数据类型
跳转到目录末尾
之前说过写代码是为了解决生活中的问题,比如购物。
假设我们要买一本书,它的价格是
10.5
,是个小数,也可以是一个整数10
,它的书名是钢铁是怎样炼成的,是一组字符串,就像svli@gikvw
这些,里面每个称为字符。
我们要去用c语言来去存储这些信息呢,在c语言里面为了能够描述这些类型。
就把小数抽象出来一个数据类型,叫浮点型
把整数抽象出来一个数据类型,叫整型(就是上面的
int
)
把字符抽象出来一个数据类型,就叫字符
10.5 -小数-浮点型 10 -整数-整型 A-字符-字符
那么c语言里面抽象出来哪些数据类型呢?
char//字符数据类型short //短整型int //整型long//长整型long long //更长的整型float //单精度浮点数double//双精度浮点数//浮点型10.5//整型10//字符A
看了上面的数据类型,很容易看出来下面的关系:
为什么叫浮点数?
就像66.6
和6.66*10
,小数点是可以浮动的,所以叫浮点数
那么,为什么会有上面这么多的类型呢?我们就需要先回答一下下面这个问题:
每一种类型的大小是多少?
我们等一下用代码问一下他们各自的大小
先了解一下新的东西
#includeint main(){printf("%d\n",100)//return 0;}
这个
%d
指的是打印(输出)的格式,即整型,后面的100
是返回值,即打印的是100
,\n
的意思就像enter
,空行的意思,如图:
运行结果:
100
现在来解释一下\n的作用,这里用两组代码来对比一下:
第一组
#includeint main(){printf("%d\n",100)//printf("%d\n",100)printf("%d\n",100)return 0;}
运行的结果:
100
100
100
第二组
#includeint main(){printf("%d",100)//printf("%d",100)printf("%d",100)return 0;}
运行的结果:
100100100
可以看出,\n的作用就是空一行,就是平时enter的作用
接下来就是
#includeint main(){printf("%d ", sizeof(char));printf("%d ", sizeof(short));printf("%d ", sizeof(int));printf("%d ", sizeof(long));printf("%d ", sizeof(long long));printf("%d ", sizeof(float));printf("%d ", sizeof(double));return 0;}
运行结果:
1 2 4 4 8 4 8
(那么这些数字的含义是什么?它们又是怎么来看出所占内存的大小呢?我们等一会儿就来解释。)
这里的sizeof()
的作用就是返回数据类型中的char
在内存中所占的大小,通俗点就是用它来告诉你char
这个数据类型占多少内存
不过这里用%d
不太合适,在一些的编译器里面会报警告,我们用%zu
最合适,sizeof
的返回值要用%zu
来打印。
int main(){printf("%zu\n", sizeof(char));printf("%zu\n", sizeof(short));printf("%zu\n", sizeof(int));printf("%zu\n", sizeof(long));printf("%zu\n", sizeof(long long));printf("%zu\n", sizeof(float));printf("%zu\n", sizeof(double));return 0;}
结果跟上面那个图是一样的,运行结果是:
1
2
4
4
8
4
8
(我里面加了\n
,所以是一排一排的)
接着我们来讲上面那些数字的含义,
你只看到了它们这些数字,但没有单位,其实它们都是有单位的,这里我们就来了解一下计算机的单位:
计算机的单位从大到小就是PB,TB,GB,MB,KB,byte
。就像我们平时生活中用的流量,一般开通的套餐只到GB
,所以GB
,MB
,KB
这些我们都已经很熟悉了,下面就是各单位间的换算
1Pb=1024Tb1Tb=1024Gb 1Gb=1024Mb 1Mb=1024kb 1kb=1024byte(字节) 1byte=8bit(比特位)
看到这些,你可能对这些没有什么概念,我们再来进一步了解一下
我们都知道计算机使用的是
二进制
语言,而我们生活在十进制
的世界,
来给一个空间| |
,里面可以填1
或0
就是|1|
或|0|
,这就是一个bit
,对应的就是灯亮和灯灭
那么一个字节
byte
呢?
|1| |0| |1| |0| |0| |1| |0| |1|
占了8个空间,也就是8个bit
10100101
对应的十进制就是 165
一个字节能储存多大的数字呢?
11111111
对应的十进制
就是255
,即最多可以储存2^8-1
这么大的数
我们可以感受到越到后面的单位大小越庞大
解释完计算机的单位,我们就来回到上面的
1 2 4 4 8 4 8
,它们的单位都是字节
char
对应1字节,可以开辟一个能储存2^8-1
的空间
short
对应2字节,可以开辟一个能储存2^16-1
的空间
int
对应4字节,可以开辟一个能储存2^32-1
的空间
long long
对应8字节,可以开辟一个能储存2^64-1
的空间
里面的
float
和double
是来存储小数的,双精度浮点数比单精度浮点数精度要高一点,这个在以后的博客会更加深入的去讲解
但是我们看到
int
和long
为什么都是4呢?
C语言规定,sizeof(long)
>=sizeof(int)
就行,但不会超过sizeof(long long)
那么我们回到前面提出的问题,为什么要规定这样的数据类型呢,答案是节省空间:
给你一个数2
,你要把它存起来,你用了long long
这个数据类型,开辟了能储存2^64-1
的空间来放这个2
,是不是有点大材小用了,用一个char
就行了,提高空间的利用率
接下来我们来解释一下下面的代码,来引入下一个博客讲的变量和常量
#includeint main(){int age=18;double weight=16.5;return 0;}
int age=18;
向内存申请一个int
大小的空间,来把18
这个数据放在age
这个变量里面,通俗一点就是,int
要了一个房间,起名为age
(随便起名),把18
放到了里面。
double weight=16.5;
这个像上面一样,double
是数据类型,weight
是个变量,16.5
是个小数
这些数据类型就是来定义变量的
✨5. 变量和常量
跳转到目录末尾
生活中有一些是可变的量(比如你的年龄,身高,体重,对象)
还有一些不变的量(比如圆周率,性别,身份证号码)[哎,这里你说性别可变啊,我们只能笑一下,哈哈]
✨5.1变量
✨5.1.1定义变量
跳转到目录末尾
#includeint main(){int a=10;printf("a=%d",a);return 0;}
运行的结果:
a=10【a
就是一个变量(int
定义一个变量a
,向内存申请一个空间给a
,并赋予a
一个值),因为a
是可变的,所以可以赋予a
任意一个值】
下面再来多写几个变量来加深一下了解
#includeint main(){short age=20;//年龄int high=180;//身高float weight=88.5;//体重return 0;}
运行的结果:
(因为没有printf
来输出,所以什么也没有)
可以看出来我们定义的变量
age,high,weight
分别都有自己的含义,这可以让我们在写代码时具有很好的可读性,当然变量是可以随便命名的,比如也可以把age
改为a
,无所谓,只是为了可读性。
✨5.1.2变量的分类
跳转到目录末尾
变量分为局部变量和全局变量,下面我们来用代码来演示一下
#includeint global=2022;//全局变量int main(){int local=2018;//局部变量//下面的global和上面的global会不会冲突呢?int global=2021;//局部变量printf("global=%d",global);return 0;}
运行的结果:
global=2021(运行的结果为下面的global
,所以当局部变量和全局变量同名时,局部变量优先使用,所以不会冲突)
在int main() { }
外的global
就是全局变量,整个项目都可以使用的变量,在{}里面的变量就是局部变量,只能在{}
范围内使用,假如{}
中还有一个{}
可以类推全局变量和局部变量的关系,都是一样的
✨5.1.3变量的使用
跳转到目录末尾
#include int main(){int num1 = 0;int num2 = 0;int sum = 0;printf("输入两个操作数:>"); //输入两个数scanf("%d %d", &num1, &num2);//求和sum = num1 + num2;//输出sum的值printf("sum = %d\n", sum);return 0;}//这里介绍一下输入语句//scanf可以输入数值赋予后面&所带的的变量num1和num2
在你运行的时候会出现下面的情况:
不要害怕,出现的错误在错误列表上会显示出来(但不要太依赖这个,仅供参考,很多时候出错的莫名奇妙):
错误 C4996 ‘scanf’: This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS.
我们来慢慢解析一下,它说,这个
scanf
它不安全,让你使用它的替代品scanf_s
,如果你执意要使用scanf
,需要在前面加一个_CRT_SECURE_NO_WARNINGS
我们这里来说一下scanf_s
,它是vs编译器自带的只能在这个编译器里使用,只有这个编译器认识它,所以我们应该使用的通用的scanf
,但是使用scanf
它会报错啊,来,我们往前面加一个#define _CRT_SECURE_NO_WARNINGS
试一试
#define _CRT_SECURE_NO_WARNINGS#include int main(){int num1 = 0;int num2 = 0;int sum = 0;printf("输入两个操作数:>");//输入两个数scanf("%d %d", &num1, &num2);//求和sum = num1 + num2;//输出sum的值printf("sum = %d\n", sum);return 0;}
运行结果:
输入两个数,中间用空格隔开
按enter
即可输出两数之和
可以看出来,这串代码可以运行了,那么我们每次使用scanf
就要每次都加一个#define _CRT_SECURE_NO_WARNINGS
是不是太麻烦了,告诉你们一个办法:
在vs的安装路径下有一个文件
newc++file.cpp
,
在vs工程中创建新的.c
或.cpp
文件的时候,都是拷贝上面这个文件的,所以我们只需要把这个文件的开头加上#define _CRT_SECURE_NO_WARNINGS
,之后所创建的.c
或.cpp
都会带有#define _CRT_SECURE_NO_WARNINGS
✨操作:
✨下载everything
(下载链接在下面的百度云)
✨打开everything
✨查找newc++file.cpp
✨用记事本打开
✨把#define _CRT_SECURE_NO_WARNINGS复制在里面
保存即可,之后我们创建的新项目里面都会包含这条代码,也就不需要一直输入#define _CRT_SECURE_NO_WARNINGS
✨5.1.4 变量的作用域和生命周期
✨5.1.4.1 作用域
跳转到目录末尾
作用域(scope)是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的
而限定这个名字的可用性的代码范围就是这个名字的作用域。
- 局部变量的作用域是变量所在的局部范围(就是局部变量所在的{})
- 全局变量的作用域是整个工程。
我们举一个全局变量作用于整个工程的例子:
文件test4.5(2),里面的int a=100就是一个全局变量
另一个项目test4.5里面
运行的时候出现了错误,原因是没有申明这个变量a是来自其他项目里面的
所以我们加了一个
extern int a;//去申明一下它来自外面
这时去运行一下,结果:
a=100,没有问题
✨5.1.4.2 生命周期
生命周期就类似于作用域,类比过来就行,生命周期≈作用域
变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段
- 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
- 全局变量的生命周期是:整个程序的生命周期。
✨5.1.5 变量的命名:
1.有意义
int age;
//年龄
float salary;
//薪资
2.名字在必须是字母、数字、下划线组成
不能有特殊符号,同时不能以数字为开头
int 2b;
//err
int _2b;
//ok
3.**变量名不能是关键字**
✨5.2常量
跳转到目录末尾
生活中有一些是可变的量(比如你的年龄,身高,体重,对象)
还有一些不变的量(比如圆周率,性别,身份证号码)[哎,这里你说性别可变啊,我们只能笑一下,哈哈]
✨5.2.1 常量的定义
跳转到目录末尾
常量就是一个不变的量(比如圆周率,性别,身份证号码)
C语言中的常量和变量的定义的形式有所差异。
✨5.2.2 常量的分类
跳转到目录末尾
C语言中的常量分为以下以下几种:
1.字面常量
2.const 修饰的常变量
3.#define 定义的标识符常量
4.枚举常量
我们来演示一下
✨5.2.2.1 字面常量
跳转到目录末尾
就是字面上就可以看出来的常量
int main(){//字面常量演示3.14;//小数1000;//整数'w';//字符"abc";字符串 return 0;}
✨5.2.2.2 const修饰的常变量
跳转到目录末尾
#define _CRT_SECURE_NO_WARNINGS#include//使用printf记得加这条代码int main(){//const修饰的常变量演示 int a=100;//我们等一会儿会改变它 a=10; printf("%d",a); return 0;}
运行结果:
10
当我们在int a=100前加一个const来修饰
#define _CRT_SECURE_NO_WARNINGS#includeint main(){//const修饰的常变量演示 const int a=100;//在这儿 a=10; printf("%d",a); return 0;}
运行的结果:
出错了
我们可以知道,const如果修饰一个变量,再给变量换一个值是不被允许的,相当于一个常量,但它还有变量的属性,所以我们就叫它是const 修饰的常变量
为什么说它具有变量属性呢?
我们来证明一下:
再新了解一个东西,数组,当我们来写代码的时候,有时候需要写很多的变量,很麻烦,这时候我们使用数组arr[ ],(a[ ],hin[ ]都随便,自己起一个名),在数组的[ ]中只能放常量,比如arr[5],相当于你直接定义了5个变量,里面是不能放变量的,根据这个原理我们来证明一下const 修饰的常变量是否具有变量属性
#define _CRT_SECURE_NO_WARNINGS#includeint main(){const int a = 100;int arr[10] = {0};return 0;}
可正常运行
#define _CRT_SECURE_NO_WARNINGS#includeint main(){const int a = 100;int arr[a] = {0};return 0;}
运行结果:
出现了错误,说明const 修饰的常变量具有变量属性
我有一个大佬(我认为的大佬,哈哈哈)是这样理解的:假设变量是一个空间,常量是一个实际值,变量用来容纳常量,const只是锁定了值,但是并不是把空间变成数值
✨5.2.2.3 #define 定义的标识符常量
跳转到目录末尾
#define _CRT_SECURE_NO_WARNINGS#include#define MAX 100//hereint main(){printf("%d\n",MAX);int a=MAX;printf("%d\n",a);return 0;}
运行的结果:
100
100
可以看出来,MAX被定义为了100,无论是直接打印还是给变量一个值都可以,想用的时候就可以拿出来使用,这个MAX就是标识符常量,也就是#define 定义的标识符常量。(#define也是可以定义一个字符串的,不局限于数字)
✨5.2.2.4 枚举常量
跳转到目录末尾
我生活中一些值是可以一一列举出来的,比如性别,我们初步来了解一下,毕竟只是初始c语言,
#include enum Sex{MALE,//男FEMALE,//女SECRET//不确定};//enum Sex未来的可能取值就是MALE,FEMALE,SECRET,它们三个就是枚举常量int main(){//枚举常量演示printf("%d\n", MALE);printf("%d\n", FEMALE);printf("%d\n", SECRET);//注:枚举常量的默认是从0开始,依次向下递增1的
注意,这里enum Sex里面三个枚举常量并不开辟空间,而且也是不能改变的,改变就会报错
6. 字符串
跳转到目录末尾
这一篇博客我们来了解一下字符串,看下面这个我们熟知的也是最先学习的代码
"Hello world!"
这一堆的字母就是字符串字面值,简称字符串,每一个字母都是一个字符,字符串需要用” “双引号来引起,字符需要用’ ‘单引号来引起,就像下面
"Hello world!"//字符串'a'//字符'!'//字符
字符串也算常量,上面三条都算字面常量,之前的讲常量的时候就举过这种例子
那么字符串有什么用呢?
假设我们要把下面的字符串存起来,那么我们需要怎么存呢?
"abcdef"
我们可以把它放在字符串数组里,有两种形式如下:
#includeint main(){char arr1[10]="abcdef"; //形式一char arr2[10]={'a','b','c','d','e','f'};//形式二//我们打印一下这些字符串printf("%s\n",arr1);printf("%s\n",arr2);return 0;}
运行的结果:
abcdef
abcdef
把”abcdef”都存在arr[ ]这个数组里面,[ ]里面数是多少,就能存多少字符
arr[10]可以存10个字符 ,但一定要不小于你要存的字符
你也可以不写[ ]里面的数字,它会自己根据你要存的字符串来调整,就像这样
#includeint main(){char arr[]="abcdef";//char-字符类型,没有字符串类型哦return 0;}
那这两种形式有什么区别呢?
我们不写arr[ ]里面的数字,我们来看一下
#includeint main(){char arr1[]="abcdef";char arr2[]={'a','b','c','d','e','f'};//我们打印一下这些字符串printf("%s\n",arr1);printf("%s\n",arr2);return 0;}
运行的结果:
abcdef
abcdef烫烫烫烫烫abcdef
哎,发现没有,它们不一样了,那为什么会出现这种情况呢?
先想一个问题,它是根据什么来停止打印的?
(啊,你会问:它们后面啥都没有,我怎么会知道它们根据什么来停下来的)
那里确实看不见有什么标志,但是那个标志就是看不见的,字符串的结束标志是\0,它是一个我们马上就会学到的转义字符
char arr1[]="abcdef";
这个形式一,字符串后面就默认带了一个\0
char arr2[]={'a','b','c','d','e','f'};
这个形式二,后面没有\0
懂了吧,printf去打印的时候要识别到\0才会结束
第一个形式最后一个字符结束就识别到了\0,然后结束
第二个形式识别到最后的f,一直都没有识别到\0,就一直打印下去,最后就出现刚才的结果
那么如果我们给第二个形式加一个\0作为结束标志怎么样,不要光想,我们来做一下试试
#includeint main(){char arr1[]="abcdef\0"; //我们也试一下给它也加一个\0char arr2[]={'a','b','c','d','e','f','\0'};//我们打印一下这些字符串printf("%s\n",arr1);//%s是打印字符串的printf("%s\n",arr2);//%s是打印字符串的 return 0;}
运行的结果:
abcdef
abcdef
结果运行正常了
那么\0算不算里面的字符串长度的一部分呢?
又来介绍一个好东西,strlen,它可以计算一个字符串长度的函数,但是使用它还是跟以前一样,需要给另一个头文件string.h打一下招呼才能使用它,我们来实操一下:
#include#includeint main(){char a[] = "abc";char b[] = "abc\0";printf("%d", strlen(a));printf("%d", strlen(b));return 0;}
运行结果:
3
3
"abc"
这个字符串的长度为3,所以我们可以知道看不见的\0并不算在长度里面的,就算在后面加上\0也是一样
大家可以试一试计算一下第二种形式不加\0情况下的字符串长度,保持好奇心,去自己尝试一下。
就这样,我们把”abcdef”这个数组储存起来了,那我们要使用里面的字符怎么办?我们还需要再了解一个知识:
调用arr里面字符,用arr[2]就可以调用c,用arr[0]就可以调用a,它们的对应关系也就是:
abcdef 012345
我们来实验一下
#includeint main(){char arr[]="abcdef";printf("%c",arr[2]);return 0;}
运行结果:
a
#includeint main(){char arr[]="abcdef";printf("%c",arr[0]);return 0;}
运行结果:
a
7. 转义字符
跳转到目录末尾
今天我们来了解一下转义字符,转义字符顾名思义就是转变意思。
现在了解一下这些字符:
转义字符释义\?在书写连续多个问号时使用,防止他们被解析成三字母词\'用于表示字符常量'\“用于表示一个字符串内部的双引号\\用于表示一个反斜杠,防止它被解释为一个转义序列符。\a警告字符,蜂鸣\b退格符\f进纸符\n换行\r回车\t水平制表符\v垂直制表符\dddddd表示1~3个八进制的数字。 如: \130 X\xdddd表示2个十六进制数字。 如: \x30 0
为什么要使用这些转义字符呢?
我们来说一下三字母词(过去有,现在使用的编译器不使用它了,所以就演示不了了),如果我们要打印一些字符,比如
??)
,在三字母词中它对应的符号是]
??) --> ] ??( --> [
表示的是你本来想要打印的是
??)
这三个符号,但是在编译器看来,它们是]
,所以它会给你打印出],而不是??)
那么你就想要打印出
??)
怎么办呢?
我们只需要把
??)
前加一个\
即
\?\?)//转义字符\?
它可以让
?
变成不是三字母词中的?
,让它老老实实的就是一个?
,所以编译器就不会打印出]
三字母词实在是太古老了,但是这个语法不能消失,所以我们一直都在使用转义字符
这里总结一下我们打印所用到的格式
//%d - 打印整型//%c - 打印字符//%s - 打印字符串//%f - 打印float类型的数据//%lf - 打印double类型的数据//%zu - 打印sizeof的返回值
如果我们要打印一个字符,如下:
#includeint main(){printf("%c",'''); //我们来打印'return 0;}
运行结果:
出现了错误
那么出错的原因是什么?
我们来看这'''
,printf识别的时候,识别第一个'
,然后再识别一个'
,哎,到这里就结束了,就像识别这个字符一样'w'
,只要识别到一对儿'
,打印就会结束,但是后面还有第三个'
是多余的,就出错了
那么,我们使用转义字符
\'
,让'
不再是一对儿'w'
里面的'
,就像上面的??)
一样
验证一下:
#includeint main(){printf("%c",'\''); //我们来打印'return 0;}//转义字符\'
运行的结果:
’
再来继续了解转义字符
我们打印字符串有两个形式:
#includeint main(){printf("%s\n","abcdef"); //形式一printf("abcdef");//形式二return 0;}
第二种形式不能直接像下面这样打印:
printf(2123456)//错误的
如果我们在
abcdef
中间加一个\0
,但因为它代表的是换行,所以我们需要让它失效,让\0
就是\0
,没有其他含义,只需要这样就可以了:
#includeint main(){printf("%s\n","abc\\0def"); //形式一printf("abc\\0def");//形式二return 0;}//转义字符\\
上面那个表里的转义字符都是这样子来使用的
来练习一个题
#include #include int main(){printf("%d\n", strlen("c:\test\121"))return 0;}
它的输出结果是什么?
答案解析:
strlen:获取字符串的有效长度,不包括’\0’
“c:\test\121”: 在该字符串中,\t是转移字符,水平制表,跳到下一个tab的位置;而\121表示一个字符,是讲121看做8进制数组,转换为10进制后的81,作业为ASCII码值的字符,即:字符’Q’ ,故上述字符串实际为:“c: esty”,只有7个有效字符
如果我们真的要打印
c:\test\121
,需要变成c:\\test\\121
,这样就可以把c:\test\121
给打印出来
关于转义字符,我们只需要记住特殊的就行,没有必要全部记忆,我们以后是会用到一些的
8.注释
跳转到目录末尾
在之前小奔写代码的时候,你会经常看到
//
//
这个是注释,注解解释的意思,它可以让一段代码无效化,常用作解释一段代码的作用
就像这样:
int main(){//创建指针变量p,并赋值为NULLint* p = NULL;return 0;}
//
这个是c++的注释方式,还有一种注释的方式/* */
如下:
int main(){//int a = 10;int a = 10;int b = 20;//创建指针变量p,并赋值为NULLint* p = NULL;return 0;}/*int main(){//int a = 10;int a = 10;int b = 20;//创建指针变量p,并赋值为NULLint* p = NULL;return 0;}*/
可以看到下面那个已经被注释掉了,这个注释方式是c语言的注释方式
那么你认为哪一种会比较好呢?
我的老师说都好,各有千秋,不过我比较喜欢//这一种
因为/* */
这种注释不能嵌套
就像这样:
/*int main(){//int a = 10;/*int a = 10;int b = 20;*///创建指针变量p,并赋值为NULLint* p = NULL;return 0;}*/
你可以观察到,我现在最外面加了
/* */
,又想在里面的
int a = 10;int b = 20;
加一下注释
/* */
,但是没有实现,它不能嵌套(套娃)
梳理一下注释的作用:
- 注释可以梳理梳理
- 对复杂的代码进行解释
- 写代码的时候写注释,是帮助自己,帮助别人的
鹏哥建议:
写代码的时候记得养成写注释的习惯,这对以后面试很有好处,也在工作中有便利,方便别人去理解,也可以给面试官一个好印象
9. 顺序语句
跳转到目录末尾
就像这样直着下来,就是顺序语句,我们之前演示的集合都是顺序语句:
10. 选择语句
跳转到目录末尾
如果你好好学习,校招的时候拿一个好offer,不好好学习,就会回家买红薯,这就是选择
我们C语言也可以实现这种选择,会用到
if(条件)//满足条件{选择一}//不满足条件else{选择二}
我们来使用一下
int main(){int input = 0;printf("跟着我一起打卡学习\n");printf("要好好学习吗(1/0)?");//1为真,0为假scanf("%d", &input);if (input == 1)//input是否为1//为1{printf("好offer\n");}//不为1else{printf("卖红薯\n");}return 0;
运行结果:
如果你输入1
如果你输入0
这就是选择语句,对你的种种方向进行选择
11. 循环语句
跳转到目录末尾
有些事必须一直做,比如我日复一日的写博客,比如大家,日复一日的学习
比如:
我每天都写博客,每写一篇会涨五个粉丝,目标是500粉获得一个作者认证,我需要写100篇,如果里面有一篇上了热搜,哗的一下,粉丝都冲了进来,一下子涨了1000粉,走上了人生巅峰,认证了作者,还额外获得了好多的粉,我们需要用到这三种循环语句:
while语句-讲解for语句(后期讲)do ... while语句(后期讲)
我们来使用一下while循环语句:
int main(){int 博客 = 0;printf("和我一起打卡学习\n");while (博客 < 100)//循环100次,只打印到博客=99,但是其实最后博客=100,//因为最后的博客+1时候还没打印就跳出循环了{printf("写代码:%d\n", 博客);博客++;//相当于博客+1}if (博客 >= 100){printf("获得作者认证\n");}else{printf("继续加油\n");}return 0;}
运行结果:
//如果你在写第55篇博客的时候找到了一个女朋友,//女朋友要求你陪她,没时间写博客,你没写够55篇,//那就继续加油,我们来看一下结果:int main(){int 博客 = 0;printf("和我一起打卡学习\n");while (博客 < 55)//循环100次,只打印到博客=99,但是其实最后博客=100,//因为最后的博客+1时候还没打印就跳出循环了{printf("写代码:%d\n", 博客);博客++;//相当于博客+1}if (博客 >= 100){printf("获得作者认证\n");}else{printf("继续加油\n");}return 0;}
运行结果:
12. 函数
跳转到目录末尾
接下来讲一讲函数
类似于这些:
f(x)=2*x-1这个就是一个函数,你输入一个值,最后返回你一个处理过的值,来举一个例子:
#include int main(){int num1 = 0; int num2 = 0;int sum = 0;printf("输入两个操作数:>");scanf("%d %d", &num1, &num2);sum = num1 + num2;printf("sum = %d\n", sum);return 0;}
上述代码,写成函数如下:
#include int Add(int x, int y){ int z = x+y; return z;}int main(){int num1 = 0; int num2 = 0;int sum = 0;printf("输入两个操作数:>");scanf("%d %d", &num1, &num2);sum = Add(num1, num2); //在这里printf("sum = %d\n", sum);return 0;}
运行的结果都一样:
随便输入两个值
回车
num1就是上面Add函数里面的x
num2就是上面Add函数里面的y
x和y在函数里面进行处理,最后返回(return)z的值,所以就把z的值赋予了sum
就像下面的流程:
最后来了解c语言中函数的结构:(如图)
13. 数组
跳转到目录末尾
之前有提到过,但是不太一样,那个是char类型的
要存储1-10的数字,怎么存储?
你会
int a=1;
int b=2;
······
这样吗?
当然不会,我们会用数组来解决
C语言中给了数组的定义:一组相同类型元素的集合
13.1数组的定义
跳转到目录末尾
我们想把1到10用int类型储存起来,就像下面这样
int arr[10] = {1,2,3,4,5,6,7,8,9,10};//定义一个整形数组,最多放10个元素
13.2数组的小标
跳转到目录末尾
C语言规定:数组的每个元素都有一个下标,下标是==
从0开始==的。
数组可以通过下标来访问的。
比如:
#includeint main(){int arr[10] = {1,2,3,4,5,6,7,8,9,10};//我们要怎么去使用它们呢?//就像住房子,每个房子都有一个编号,你想住哪个就选一个编号//就像这样int arr[5]=6;int arr[0]=1;return 0;}
13.3数组的使用
跳转到目录末尾
我们可以把数组和循环语句结合起来:
while循环
#include int main(){ int i = 0; int arr[10] = {1,2,3,4,5,6,7,8,9,10};while(i<10)//while循环语句 { printf("%d ", arr[i]); i++; } printf("\n");return 0;}
运行的结果:
for循环
#include int main(){ int i = 0; int arr[10] = {1,2,3,4,5,6,7,8,9,10}; for(i=0; i<10; i++)//for循环语句 { printf("%d ", arr[i]); } printf("\n");return 0;}
运行的结果:
14. sizeof和strlen头文件的的区别
跳转到目录末尾
之前的博客对sizeof和strlen没有怎么区分,看了一些其他,现在就来补充一下
strlen 是一个函数,它用来计算指定字符串的长度,但不包括结束字符,遇见\0就会结束
sizeof是一个单目运算符,而不是一个函数,它会把0也计算在内,直到最后
小小的演示一下:
int main(){char arr[] = "abcd";printf("%d", sizeof(arr));return 0;}
运行的结果:
a b c d 0(五个)【char类型的数组“ ”形式后面还隐藏了一个\0】
int main(){int arr[] = {1,5,3,4,5};printf("%d", sizeof(arr)/sizeof(arr[1]));//sizeof 的单位是字节,总的元素所占的字节/其中每一个元素所占的字节=数组的长度return 0;}
运行的结果:
1 5 3 4 5(五个)【int类型的数组后面没有隐藏的\0】
int main(){char arr[] = {'s','f','w'};printf("%d", strlen(arr));return 0;}
运行的结果:
s f w ······(停不下来,就给一个随机值)【char类型的数组{}形式后面没有\0,strlen会一直识别下去,但是sizeof只识别到w就会停下】
int main(){char arr[] = {'s','f','w'};printf("%d", sizeof(arr)/sizeof(arr[0]));return 0;}
运行的结果:
s f w(三个)【char类型的数组{}形式后面没有\0,strlen会一直识别下去,但是sizeof只识别到w就会停下】
15. 操作符
跳转到目录末尾
C语言是非常灵活的
C语言提供了非常丰富的操作符,使得使用起来就比较灵活,我们这篇来简单了解一下操作符
下面的有些操作符会不介绍,以后再说
算术操作符 |
⭐+⭐-⭐*⭐/⭐%加减乘 除取模下面会重点讲一下⭐/和⭐%
移位操作符(涉及二进制的运算,学到这里再讲) |
⭐>>⭐<<右移操作符 左移操作符
位操作符(涉及二进制的运算,学到这里再讲) |
⭐&⭐^ ⭐|按位与按位异或 按位或
赋值操作符 |
⭐=⭐+=⭐-=⭐*=⭐/= ⭐&= ⭐^=⭐|=⭐>>= ⭐<<=(后五个涉及二进制的运算,学到这儿再讲)
单目操作符 |
⭐!⭐- ⭐+ ⭐&逻辑反操作负值 正值 取地址⭐sizeof 操作数的类型长度(以字节为单位)⭐~对一个数的二进制按位取反⭐--⭐++前置、后置-- 前置、后置++⭐* 间接访问操作符(解引用操作符)⭐(类型) 强制类型转换
关系操作符 |
⭐>⭐>=⭐<⭐<=⭐!= 用于测试“不相等”⭐== 用于测试“相等”
逻辑操作符 |
⭐&&逻辑与⭐||逻辑或
条件操作符 |
⭐exp1 ? exp2 : exp3
逗号表达式 |
⭐exp1, exp2, exp3, …expN
下标引用、函数调用和结构成员 |
⭐[] ⭐() ⭐. ⭐->
算术操作符
⭐+ ⭐- ⭐*就不介绍了,比较简单,就是你们理解的那样
我们来说一下⭐/和⭐%
int main(){int a = 7 / 2;//除int b = 7 % 2;//取模printf("%d\n", a);printf("%d\n", b);return 0;}
运行的结果:
3(应为变量a是整型,不会四舍五入,只会取小数点前面的整数)
1(取模就是取余数)
我们定义
a
是一个浮点型变量,/
左右的操作数必须要有一个是浮点型,或者都是浮点型,我们来看一下不是浮点型的情况
浮点型变量,整型操作数
int main(){float a = 7 / 2;//除int b = 7 % 2;//取模printf("%f\n", a);printf("%d\n", b);return 0;}
运行的结果:
3.000000(操作数没有一个是浮点型,就会导致3.500000变成3.000000)
1
浮点型变量,浮点型操作数
int main(){float a = 7 / 3.0;//除int b = 7 % 2;//取模printf("%f\n", a);printf("%d\n", b);return 0;}
运行的结果:
2.333333
1
如果要保留浮点型前几位,那就在
%f
中间加上.保留位数
例如%.2f
,保留两位
int main(){float a = 7 / 3.0;//除int b = 7 % 2;//取模printf("%.2f\n", a);printf("%d\n", b);return 0;}
运行的结果:
2.33
1
取模的操作数一个都不可以是浮点数,否则就会报错
int main(){float a = 7 / 2.0;//除float b = 7 % 2.0;//取模printf("%.2f\n", a);printf("%f\n", b);return 0;}
运行的结果:
赋值操作符
⭐=
int main(){int a = 0;//像这个创建一个变量的时候给它一个值叫做初始化a = 20;//赋值=return 0;}
⭐+=
⭐-=
int main(){int a = 0;int b = 5;//像这个创建一个变量的时候给它一个值叫做初始化a=a+3;a+=3;//就相当于a=a+3;a=a-3;a-=3;//就相当于a=a-3;return 0;}
单目操作符
a+b;+ //操作符有两个操作数的就是双目操作符 + a; +//操作符只有一个操作数的就是单目操作符
在c语言中,我们用
0
表示假,非0
表示真
!
就是把假变成真,把真变成假,就像下面这样
int main(){int flag = 0;//flag假if (!flag)//!flag真printf("真");elseprintf("假");return 0;}
运行的结果:
真
⭐-
负号
把符号颠倒,把正的变成负的,把负的变成正的
int main(){int a = -10; //只有一个操作数的就是单目操作符int b = -a; //只有一个操作数的就是单目操作符printf("%d", b);return 0;}
运行的结果:
10
⭐+
加号
+a与a是等同的,不会令负数变成一个正数
⭐sizeof
sizeof是操作符,是单目操作符,可以计算变量和类型所占内存的大小,单位是字节
int main(){int a = 10;printf("%zu\n", sizeof(a));printf("%zu\n", sizeof(int));return 0;}
运行的结果:
4
4
如果用来计算数组的大小,我们来看一下:
int main(){int arr[] = { 1,2,3,4,5 };printf("%zu\n", sizeof(arr));return 0;}
运行的结果:
很明显是4×5=20(在c语言中,是没有这些×÷符号的,要用c语言可以识别出来的符号*/)
也可以用这个
sizeof(arr)/sizeof(arr[0])
来计算数组中有几个元素
int main(){int a = 10;int arr[] = { 1,2,3,4,5 };printf("%zu\n", sizeof(arr) / sizeof(arr[0]));return 0;}
运行的结果:
20(数组占20个字节大小)
5 (数组里面有五个元素)
sizeof不是函数,所以也可以把
sizeof(a)
变成sizeof a
括号是可以省去的,这一点可以证明sizeof不是函数
但对于类型来说,括号是不可以省略掉的
⭐- -和⭐++
分前置和后置
i++;//后置++,先使用,后++++i;//后置++,先++,后使用i--;//后置++,先使用,后++--i;//后置++,先--,后使用
看一下下面的代码,小奔只举一个
++
的例子,或者自己动手操作一下
int main(){int a = 0;int b = a++;printf("一%d %d\n", a, b);//b先使用了a=0这个值,然后a再++变成1int c = ++a;printf("二%d %d\n", a, c);//a先++变成2,c再使用a=2这个值return 0;}
运行的结果:
一1 0
二2 2
⭐(类型)
int main(){//int a = 3.14;int a = (int)3.14;//3.14字面浮点数,编译器默认理解为double,我们用(int)来把它强制认为是int类型printf("%d", a);return 0;}
运行的结果:
3
关系操作符
关系操作符是用来判断关系的,举一个例子
a < 10;a <= 10;a == 10;
判断
a
与10
的关系,成立就是真,不成立就是假
在这里我们来说一下=
和==
两个的区别,非常容易错
int main(){int a = 10;if (a = 3)printf("%d", a);return 0;}
你认为它输出的结果是什么?
运行一下
结果是3
哎,你看到,你明明输入的a
是10
,为啥打印的是3
呢?
在这里小奔告诉你,=
是赋值操作符,是给变量一个值的作用,而==
是关系操作符,它才是判断是否相等的操作符
应该这样
int main(){int a = 10;//if (a = 3)if (a == 3)printf("%d", a);return 0;}
运行的结果:
可以看出来,a
不等于3
,所以为假,不执行if
下面的操作,也就是不打印a
的值
逻辑操作符
再来讲一下
&&
和||
的关系
哎,你有两只手,有一个桌子&&
,和一个椅子||
,桌子&&
要两只手才能搬动,椅子||
一只手就可以搬动
同理,
真&&
真,就是真
真&&
假,就是假
假&&
假,就是假真
||
真,就是真
真||
假,就是真
假||
假,就是假
写个代码试一下:
int main(){int a = 10;int b = 20;if (a <= 20 && b >= 10)printf("真");elseprintf("假");return 0;}
运行的结果:
真
其他的自己试一试,在这里就不写了
条件操作符
⭐exp1 ? exp2 : exp3
它有三个操作数,所以是三目操作符
它的意思是判断exp1
是否为真,是真的话输出exp2
的值,是假的话输出exp3
的值
举例来使用一下
int main(){int a = 10;int b = 20;//求最大值int r = a > b ? a : b;//a>b是假,输出b的值,所以r=b=20printf("%d", r);return 0;}
运行的结果:
20
int r = a > b ? a : b;
上面的代码就类似于下面的代码
if (a > b)r = a;elser = b;
逗号表达式
⭐exp1,exp2,exp3…expN
特点:从左到右依次计算,整个表达式的结果是最后一个表达式的结果
用法直接用代码来演示一下
int main(){int a = 10;int c = 0;int b = (a = a + 2, c = a - 3, c - 3);//b最后等于的是最后面的表达式//a = a + 2 = 12//c = a - 3 = 9//c - 3 = 6//b = 6printf("%d", b);return 0;}
运行的结果:
下标引用、函数调用和结构成员
⭐[ ]
在代码里面解释
int main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//定义数组大小的时候是不可以在[]里用变量的arr[5];//[]就是下标引用操作符//arr和5都是操作数int n = 5;arr[n];//这里是可以用变量的//这里的n是来访问下标是n的元素,在定义数组大小的时候是不可以用变量的return 0;}
⭐()
函数调用操作符
int Add(int x){return x + 5;}int main(){int a = 10;a = Add(a);//()函数调用操作符,Add,a都是()的操作数//sizeof的()是可以省掉的,所以它不是函数//函数是不可以省去()printf("%d", a);return 0;}
16. 关键字
跳转到目录末尾
autobreak casecharconst continuedefault do doubleelseenumextern float for gotoifint longregisterreturnshort signedsizeofstatic structswitchtypedef unionunsignedvoidvolatilewhile
这些关键字都是c语言内置的,我们是不能自己创建的
auto
自动的意思,不常用
在局部范围内,它的局部变量进作用域会自动创建,出作用域会自动销毁,所以它是一个自动变量,在每一个变量前面都有一个auto
关键字,只是我们把它省略了,毫无存在感
就像这样
int main()[int a = 10;//auto int a = 10;return 0;]
break
跳出循环的
它和for
循环,while
循环,do…while
循环一起用,来跳出循环的
case
它和
switch
一起用,用于选择语句
char
short
int
long
long long
float
double
这些数据类型都是内置的,是关键字,之前讲过,这里就不讲了
const
常属性
让一个变量具有常属性,叫常变量,不能用来创建数组的
continue
也是用于循环的
default
用于
switch
语句
else
用于
if else
语句,也就是分支语句
enum
是一种和类型有关的关键字,是自定义的,之前简单介绍过
enum
-枚举
struct
-结构体
union
-联合体(共用体)
这三个都是一伙的
extern
是用来申明外部符号的,就是在两个.c文件中,全局变量在其中一个.c文件被创建,另一个.c文件要用这个全局变量是要申明一下的,申明这个变量不在这个,是借用外面.c文件里面的全局变量
for
用于
for
循环对吧
goto
它是实现跳转的语句,和分支语句是比较相似的
if
用于判断语句(分支语句)
register
它是寄存器的意思
return
函数返回值要用到它
sighed
有符号的
unsighed
无符号的
sizeof
计算大小
static
静态的,它限制变量存在静态区里面,
register
限制变量存在寄存器里面
typedef
叫类型重命名
void
无的意思(常用于函数的返回类型,也会用于函数的参数)
volatile
在操作系统那里再讲
while
while循环
在这里我们只是来对这些关键字增加一下印象,不能盲人摸象,也是提醒我们定义变量名的时候不能用这些关键字
就比如这个int if = 10 ;
,这个是错误的
17. 关键字typedef
跳转到目录末尾
typedef 顾名思义是类型定义,这里应该理解为类型重命名。
它在很多关于类型的地方都会用到
typedef unsigned int uint;int main(){unsigned int num = 0;uint num = 0;return 0;}
unsigned int
这个叫无符号整型,一般我们写这个比较麻烦,我们来把它重定义一下,改个名字,改成uint
,typedef unsigned int uint;
它的意思就是把unsigned int
重命名为uint
,是不是方便了很多,名字改了,但是作用都是一样的
再比如:
struct Node{int data;struct Node* next;}
它是一个结构体类型,非常抽象,不好理解,我们要创建一个节点
n
,就像这样
struct Node n;
我们不想写这么长只想写一个
Node
,我们就重命名一下它,
像这样:
typedef struct Node{int data;struct Node* next;}Node;
我们再要创建一个节点
n2
就可以像这样
Node n2;
这样是很方便的
再强调一遍typedef
只能对类型重命名,只能针对类型
18. 关键字static
跳转到目录末尾
在C语言中:
static是用来修饰变量和函数的
- 修饰局部变量-称为静态局部变量
- 修饰全局变量-称为静态全局变量
- 修饰函数-称为静态函数
我们来一一来说一下
18.1 static修饰的局部变量
跳转到目录末尾
写一个代码来了解一下:
你猜一下这个代码运行的结果是多少?
void test(){int a = 1;a++;printf("%d\n", a);}int main(){int i = 0;while (i < 5)//循环5次{test();i++;}return 0;}
运行的结果:
2
2
2
2
2
看到这里的可以一步一步根据代码推一下,可以看到a在每一次的循环中的int a = 1;都会被重新赋予1的值,然后再a++;所以输出的都是2
(进作用域,a被定义,出作用域,a被销毁)
那我们来用static来修饰一下int a = 0;来看一下
void test(){static int a = 1;//static来修饰一下a++;printf("%d\n", a);}int main(){int i = 0;while (i < 5)//循环5次{test();i++;}return 0;}
运行的结果:
2
3
4
5
6
而static修饰的局部变量使得局部变量没有被销毁,为什么会这样?
本质上,static修饰的局部变量的时候,改变了变量的存储位置
我们来画图来看一下
这个整个橙色的方块是一个大的内存,我们会划分区域来进行不同的使用,我们就把这个大的区域分成栈区,堆区,静态区
这三个区域(以后都会介绍)
我们会把局部变量存放在栈区,局部变量本来放在栈区(栈区的数据特点是进入它的作用域就创建,出它的作用域就会销毁)
但是用static
修饰了局部变量后就把局部变量变成了静态变量,而静态变量存储在静态区,所以出作用域它并不会销毁
还是这个代码,再运行的时候,这个
static int a = 1;
不是每循环一次就运行一次,而是在运行到这里的时候就会直接跳过去,不会执行这句话,因为static
修饰后在编译期间就提前把这个a
变量创建好了
void test(){static int a = 1;//static来修饰一下a++;printf("%d\n", a);}int main(){int i = 0;while (i < 5)//循环5次{test();i++;}return 0;}
所以
static
影响了变量的生命周期,生命周期变长,和程序的生命周期一样
18.2 简单介绍一下void
跳转到目录末尾
void
就是不需要返回的意思
void test(){static int a = 1;//static来修饰一下a++;printf("%d\n", a);}
这是一个
test
函数,void
不用返回一个值,如果你用int
,你就必须要返回一个值
就像这样
int test(){int x = 5;return x;//这里我就随便返回一个值x}int main(){int a = 0;a = test();//这里的a就会接受到返回的值//所以a的值变成了5return 0;}
但是你用
void
就不用返回值了,你想返回就返回,不返回就不返回
18.3 static修饰的全局变量
跳转到目录末尾
在这里,我们不是已经有一个
.c
文件了嘛
我们再创建一个.c
文件,就像这样
我们拖住上面的标题,把它拖动成两个界面,可以同时看两个,就像这样
我们都知道,全局变量在整个工程里面都可以使用,不过一个
.c
文件要使用在另一个.c
文件里的全局变量要使用关键字extern
,来声明外部符号,说一下要使用那个.c
文件里面的全局变量
就像这样
1.c1extern a;int main(){printf("%d", a);//打印a的值return 0;}
2.cint a = 5;//全局变量
那么我们用
static
修饰一下全局变量会怎么样?
1.c源文件extern a;int main(){printf("%d", a);return 0;}
2.c源文件static int a = 5;//static修饰一下全局变量
运行的结果:
报错
有这个static修饰全局变量就会报错,不修饰就会正常运行,为什么呢?
我们来分析一下原因:
全局变量是具有外部链接属性的
在多个源文件中在一起编译就会进行链接
[编译+链接 – -生成- ->可执行程序, 有一个链接的过程]
在1.c那个源文件通过链接这个手段,就可以很好的使用全局变量这个值
而static修饰全局变量的时候,它的外部链接属性变成了内部链接属性,它只能在这个源文件内部使用,其他源文件(.c)就不能再使用这个全局变量了
我们在使用static修饰的全局变量,我们的感觉是它的作用域变小了,本质上就是它的外部链接属性变为了内部链接属性
那么这样有什么意义呢?
一个变量的作用域太大也不一件好事
- 你这个全局变量的名字可能会与其他人的名字冲突
- 所有人都能改这个全局变量,这个全局变量很不安全
所以我不想让其他人看这个变量,就加个static修饰就缩小了它的作用域,其他人就不能在外部的.c里面动这个变量
18.4 static修饰的函数
跳转到目录末尾
它是类似于static修饰的全局变量
我们来实践一下
这个就是正常的用函数求和,在一个.c源文件里面
int Add(int x, int y){return x + y;}int main(){int a = 4;int b = 5;int sum = Add(a, b);//函数求和sumprintf("%d", sum);//打印numreturn 0;}
运行的结果:
9
解释一下,函数也是具有外部链接属性
我们把这个函数放在另一个.c源文件里面,使用的时候要像全局变量一样用extern声明一下外部符号,不过我不声明也是可以运行的,不清楚为什么,待我后面学到再解释
1.c源文件extern int Add(int a,int b);int main(){int a = 4;int b = 5;int sum = Add(a, b);//函数求和sumprintf("%d", sum);//打印numreturn 0;}
2.c源文件int Add(int x, int y){return x + y;}
运行的结果:
9
那么我们用static修饰一下函数会怎么样?
1.c源文件extern int Add(int a,int b);int main(){int a = 4;int b = 5;int sum = Add(a, b);//函数求和sumprintf("%d", sum);//打印numreturn 0;}
2.c源文件static int Add(int x, int y){return x + y;}
运行的结果:
报错
函数也是具有外部链接属性,可以猜出来,这个被static修饰的函数,它的外部链接属性变成了内部链接属性
19. 关键字register
跳转到目录末尾
在我们电脑里面,数据是存储在哪里的呢?
我们知道的有硬盘,内存,但是还有一些,高速缓存,寄存器,数据存储在这些地方
CPU(中央处理器)是用来处理这些数据的,运行一些程序,但使用数据的时候需要先把数据从它存储的地方拿出来,但是这四个地方拿出来的速度是不一样的
他们的关系可以用一个图来概括
越往上,它能存储的东西就越少,但能读取的数据就越快,成本就越高,我们的一个电脑的数据不能全部储存在寄存器中,因为它的存储空间不够,但是它可以很快的读取数据
就像一个牛逼的建房工人,建房建的飞快,但是砖用完了,剩下的砖还没用小推车运过来,建房工人只能在那里发呆,CPU就是那个建房工人,CPU傻傻的呆在那里等着数据传过来,但是数据一点一点的挤出来,根本不能发挥CPU飞速处理的能力,这时,寄存器这个大卡车来了,一卡车一卡车的把数据运了过来,CPU狂喜,疯狂建房子,终于能充分发挥自己能力了
好的,回到正题
那么我们怎么来把这个数据存放在寄存器里面呢?
register修饰一下就行啦
就像这样
int main(){int num = 5;return 0;}
一个简简单单的一个代码,我们要把num放在寄存器里面,加一个register
int main(){register int num = 5;return 0;}
提醒一下,这里说放在寄存器里面并不是一定放在寄存器里面,而是建议把这个数据放在寄存器里面
因为每个人都认为自己的数据很重要,都要放在寄存器里面,寄存器它塞不下啊,所以我们只是建议,并不一定可以放在里面
不过我们现在的编译器功能很强大,你不用加register,编译器自己就会把一些数据放在寄存器里面,很有自己的想法
20. #define定义的宏
跳转到目录末尾
我们之前见过#define定义的标识符常量
#define Num 1int main(){int num = Num;printf("%d\n", num);printf("%d\n", Num);}
Num可以直接打印,也可以把值给num,再打印,这都是一样
我们现在讲的#define定义的宏跟函数类似,但是函数是函数,宏是宏,两者是不一样的
就像这样
我们先用函数来求一下两个数的和
int Add(int x, int y){return x + y;}int main(){int a = 4;int b = 5;int sum = Add(a, b);//求和printf("%d", sum);return 0;}
运行的结果:
9
接下来用#define定义的宏来求一下和
#define ADD(x,y) ((x)+(y))int main(){int a = 4;int b = 5;int sum = ADD(a, b);//求和printf("%d", sum);return 0;}
运行的结果:
9
看到没有,两者是非常相似的
ADD
是宏名
(x,y)
是宏的参数
((x)+(y))
是宏体,就是把传过来的数据进行处理的地方
像函数一样,ADD()
把a
和b
传到上面的(x,y)
,然后在((x)+(y))
里面进行运算
其实吧就相当于这样
int main(){int a = 4;int b = 5;int sum = ((a)+(b));//求和printf("%d", sum);return 0;}
看到没有,其实挺简单的,至于有什么作用,那就以后小奔学到这里的时候再给你们详细讲一讲
21. 指针
跳转到目录末尾
都说指针很难,其实很好理解的,我来带你了解一下
21.1 内存
跳转到目录末尾
说指针就不得不说一下内存
内存是电脑上特别重要的存储器
我们可以打开任务管理器
在任务管理器里面可以看到内存的使用情况,有每个软件运行所占的内存,可以看到计算机中程序的运行都是在内存中进行的
但是我们需要有效的使用内存空间,这里我们买个
4G
内存的电脑,可以把4G
的内存分成一个一个的小空间,这样就会方便管理
就像这样,
这是一大块儿的内存,我们要怎么去管理它呢” />
里面有很多的房间,但是都没有编号,我买了个外卖,我说有一个房间是我住的,送外卖的也不知道我在那个房间,只能一个一个的去找.
但是我给每一个房间都编上号,像这样
我说我在305号宿舍,送外卖的是不是唰的一下就找到我了
内存就是13栋宿舍楼,内存单元就是房间,内存这么大的一块儿空间,分成一个一个的内存单元,每一个内存单元都有一个地址,这个地址就是房间的编号,是不是就能很好的管理内存了我们再来说一下地址线
我们的电脑有32位
的,也有64位
,这里指的是有32
条地址线和64
地址线,地址线通电就会产生脉冲,每一个地址线都有两种状态,也就是二进制里面的1/0
这里就说32位
的电脑这里有
32
根的地址线
每根地址线都有1/0
的选择它的组合有:
0000 0000 0000 0000 0000 0000 0000 00000000 0000 0000 0000 0000 0000 0000 00010000 0000 0000 0000 0000 0000 0000 0010…………1111 1111 1111 1111 1111 1111 1111 11101111 1111 1111 1111 1111 1111 1111 1111
一共有223种,也就是
4,294,967,296
种
所以有223个编号,对应223个地址4,294,967,296byte4,294,967,296÷1024=4,194,304kb4,194,304÷1024=4,096MB4,096÷1024=4GB
所以
32位
的电脑可以访问4GB
大小的空间我们现在常见的都是
64位
的电脑,类比上面,可以知道它可以访问很大的空间,它是有这个能力的我们认定
1
个地址管理1
个字节的大小
但是为什么不是一个bit
呢?
之前我们讲过计算机最小的单元是bit(比特位)
,1
个字节=8
个bit我来解释一下
创建一个数据类型里面最小的字符类型
char a;
,这个a
的大小是一个字节,如果我们一个地址只对应一个
bit
,但是一个最小的char
类型的变量a
就占8
个地址,是不是太浪费了
就像送快递,我在13栋305宿舍,你非得给我送到13栋305宿舍第5块瓷砖上,是不是没有必要这样有什么用呢?
来看一下代码的运行过程int main(){int a = 0;&a;return 0;}
这里的
int a = 0;
的意思就是向内存申请4
个字节的空间来存放0,
&
是取地址操作符(之前没有讲),它的作用是把a
这个变量的地址给取出来我们来调试一下
(如何调试看这一篇博客的问题三)打开监视(在调试开始的时候才有)
调试->窗口->监视->输入监视的变量
看到没有,a
被创建了,&a
也把地址取了出来,它的地址是0x009dfa5c
,这个地址里面存储了10
这个值,我们再打开内存(在调试开始的时候才有)
调试->窗口->内存
把
自动
改为4
把a
的地址0x009dfa5c
填在里面,回车,就会查到这个地址
红框就是地址,橙框就是内存中的数据,紫框就是内存数据的文本分析(编译器想尝试分析一下你里面存的是什么东西,很明显,是不准确的)
来说一下橙框里面的
0a 00 00 00
这个是十六进制
a
代表的就是存储的值10
你发现没有,红框里地址
0x009dfa5c
下面的不是0x009dfa5d
,而是0x009dfa60
,相差3
个地址
橙框里0a
就是地址0x009dfa5c
,后面的00 00 00
分别是0x009dfa5d 0x009dfa5e 0x009dfa5f
这三个地址没有写,我们开辟了
4
个字节,分配了四个地址,为什么a
的值存在第一个而不是后面呢?以后我们会讲到之前我们存的是
a
的值,那么我们怎么把&a
取出来的地址存起来呢?int main(){int a = 10;int* p = &a;return 0;}
int* p
就是指针变量p
,跟之前讲的那些变量一样,都是变量,指针变量p
可以把&a
取出来的地址存起来
在int* p
中p
指向的就是a
的地址,而*
就是说明p
是一个指针,int
指的是p
所指向a
的地址所存放的类型是int
类型int main(){char a = 'w';char* p = &a;return 0;}
char* p
的char
就是指针变量p
所指向a
的地址所存放的类型是char
类型我们每一次运行的时候,变量
a
存的地址都是不一样的,而且地址我们是不能改的,编译器已经把地址给你分配好了,不能随便改,这一点要记住(用这个printf("%p",p);
打印一下试试看,看看地址是不是在变化)有一句话,在锤子眼里,什么都是钉子
指针变量眼里,你放在里面的都是地址,它只能用来存放地址既然我们把这个
a
的地址存在了指针变量p
中,那么我们可以通过这个地址来找到它所指向的对象吗
我们用*p;
来进行这个过程
这里的*
叫解引用操作符,意思是通过指针变量p
存放的地址,来找到p
指向的对象,即*p
就是a
总结一下
&
和*
这两个操作符,它们是一一对应的
&
是取出a的地址,*
是根据地址重新回到a
,一来一去的
试一试:#includeint main(){int a = 10;int* p = &a;*p = 20;//这里的*p其实就相当于a了//打印a的地址和a的值printf("%p\n", p);//打印地址用%pprintf("%d\n", a);return 0;}
运行的结果:
008FFD9C
20
可以看到,最开始赋予a
的值是10
,但是把20
赋予*p
后,打印的a
的值是20
,所以就能看出来*p
其实就是变量a
了21.2 指针变量的大小
跳转到目录末尾
之前我们用
sizeof
计算了char,short,int,long,float,double
的大小,分别是1,2,4,4-8,4,8
那么我们来计算一下指针变量的大小int main(){printf("%zu\n", sizeof(char*));printf("%zu\n", sizeof(short*));printf("%zu\n", sizeof(int*));printf("%zu\n", sizeof(long*));printf("%zu\n", sizeof(float*));printf("%zu\n", sizeof(double*));return 0;}
运行的结果:
4
4
4
4
4
4
为什么会是这样,它们指向的类型不是不一样吗?
不管创建什么类型的指针,都是在创建指针变量,指针变量是用来存放地址的
而指针变量的大小取决于一个地址存放的时候需要多大空间
在32位
的机器上的地址有32
个bit
位,也就是4
个byte
在64位
的机器上的地址有64
个bit
位,也就是8
个byte
我们刚才是在
32位
平台上运行的,所以大小是4
个字节
我们改一下,改成
x64
,也就是64位
的平台
再运行一下:
8
8
8
8
8
8
看到没有,指针变量的大小只跟在哪个平台上有关,在32位
平台就是4
个字节,在64
位平台上就是8
个字节写指针变量有两种形式
int a = 10;int* p = &a;int *p = &a;
你可以把
*
向左贴,也可以向右贴,这都是可以的,这个是写代码的习惯,各有优点。
比如,你看看这行代码int* p1, p2, p3;
你认为
p1,p2,p3
都是指针变量吗?
不,当然不是,这个*
其实只给了p1
,只有p1
是指针变量,而p2,p3
都只是跟着int
,没有分配*
,所以它俩都是整型
我们这样写int *p1, p2, p3;
是不是能很好的理解了,
*
只贴向p1
,所以只有p1
是指针变量
我们要把它们全部变成指针变量,就这样做int *p1, *p2, *p3;
所以它们各有优点,看个人习惯
指针就学到这里吧,简单了解一下就行了
⛺22. 结构体
跳转到目录末尾
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如描述学生,学生包含: 名字+年龄+性别+学号 这几项信息。
描述一本书,这本书包括:书名+出版社+目录+作者等信息
这些都是复杂的对象,C语言就给了自定义类型的能力
自定义类型中的有一种叫:结构体struct
结构体是把一些单一类型组合在一起的做法
例如,我们现在来描述一个人,他有名字+年龄+性别+学号 这几项信息
struct Stu{char name[20];//名字int age;//年龄char sex[5];//性别char id[15]; //学号};int main(){return 0;}
我们要盖一个房子,
struct Stu
就是我们的图纸,创建一个结构体对象s
,在里面根据图纸来填写数据⛺22.1 结构体的初始化
跳转到目录末尾
//打印结构体信息struct Stu s = {"张三", 20, "男", "20180101"};
可以把
struct Stu
当成数据类型,s就是创建用来存放数据的结构体对象
,s
里面的数据就是成员名
在struct Stu
不使用的时候,里面的那些类型是不会开辟空间的打印
//.为结构成员访问操作符printf("name = %s age = %d sex = %s id = %s\n", s.name, s.age, s.sex, s.id);
要打印s里面的信息,要按照类型的顺序来打印,
"张三", 20, "男", "20180101"
对应的%s,%d,%s,%s
,顺序是不能颠倒的,且要按照结构体对象.成员名
的形式还有一个打印的方法
//struct Stu *ps = &s;printf("name = %s age = %d sex = %s id = %s\n", (*ps).name, (*ps).age, (*ps).sex, (*ps).id);
ps
是结构体指针变量,这里就是指针那里的方法,不过有点麻烦,可以直接用->
操作符struct Stu *ps = &s;printf("name = %s age = %d sex = %s id = %s\n", ps->name, ps->age, ps->sex, ps->id);
ps->name
的意思就是ps
指向s
中的成员name
,和(*ps).name
的意思一样⛲下载链接
下载everything
链接:提取码为1111
下载编译器Visual Studio Community 2019(简称VS)
链接:提取码为1111
下载MSDN
链接:提取码为1111
下载git
链接:提取码为1111
下载TortoiseGit
链接:提取码为1111很感谢您能看到这里,如果感觉有用的话,那就关注小奔,小奔会持续更新内容哦