行业知识
C语言从入门到入坟
前言
1.初识程序
���穷性
在有限的操作步骤内完成。有穷性是算法的重要特性,任何一个问题的解决不论其采取什么样的算法,其终归是要把问题解决好。如果一种算法的执行时间是无限的,或在期望的时间内没有完成,那么这种算法就是无用和徒劳的,我们不能称其为算
法。
确定性
每个步骤确定,步骤的结果确定。算法中的每一个步骤其目的应该是明确的,对问题的解决是有贡献的。如果采取了一系列步骤而问题没有得到彻底的解决,也就达不到目的,则该步骤是无意义的。
可行性
每个步骤有效执行,得到确定的结果。每一个具体步骤在通过计算机实现时应能够使计算机完成,如果这一步骤在计算机上无法实现,也就达不到预期的目的,那么这一步骤是不完善的和不正确的,是不可行的。
零个或多个输入
从外界获得信息。算法的过程可以无数据输入,也可以有多种类型的多个数据输入,需根据具体的问题加以分析。
一个或多个输出
算法得到的结果就是算法的输出(不一定就是打印输出)。算法1 的目的是为解决一个具体问题,一旦问题得以解决,就说明采取的算法是正确的,而结果的输出正是验证这一目的的最好方式。
2.编程语言的演变
2.1 计算机语言
计算机语言(ComputerLanguage) 指用于人与计算机之间通讯的语言。计算机语言是人与计算机之间传递信息的媒介。计算机系统最大特征是指令通过一种语言传达给机器。为了使电子计算机进行各种工作,就需要有一套用以编写计算机程序的数字、字符和语法规划,由这些字符和语法规则组成计算机各种指令(或各种语句)。这些就是计算机能接受的语言。
2.2 分类
#include // 头文件// 入口函数int main() { printf("Hello World!\n"); //打印输出}// 注释 2.3 解释型vs编译型
解释型语言:解释性语言编写的程序不进行预先编译,以文本方式存储程序代码。执行时才翻译执行。程序每执行一次就要翻译一遍。
优缺点:跨平台能力强,易于调,执行速度慢。
编译型语言: 编译型语言在执行之前要先经过编译过程,编译成为一个可执行的机器语言的文件,比如exe。
因为翻译只做一遍,以后都不需要翻译,所以执行效率高。
编译型语言的优缺点:执行效率高,缺点是跨平台能力弱,不便调试。
3. 初识C语言
3.1 C语言的简介
C语言是一门面向过程的计算机编程语言,与C++、C#、Java等面向对象编程语言有所不同。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、仅产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言描述问题比汇编语言迅速、工作量小、可读性好、易于调试、修改和移植,而代码质量与汇编语言相当。C语言一般只比汇编语言代码生成的目标程序效率低10%-20%。因此,C语言可以编写系统软件。
3.2 C语言的发展历史
C语言诞生于美国的贝尔实验室,由丹尼斯·里奇(Dennis MacAlistair Ritchie)以肯尼斯·蓝·汤普森(Kenneth Lane Thompson)设计的B语言为基础发展而来,在它的主体设计完成后,汤普森和里奇用它完全重写了UNIX,且随着UNIX的发展,c语言也得到了不断的完善。为了利于C语言的全面推广,许多专家学者和硬件厂商联合组成了C语言标准委员会,并在之后的1989年,诞生了第一个完备的C标准,简称“C89”,也就是“ANSI C”,截至2020年,最新的C语言标准为2018年6月发布的“C18”。 [5]
C语言之所以命名为C,是因为C语言源自Ken Thompson发明的B语言,而B语言则源自BCPL语言。
1967年,剑桥大学的Martin Richards对CPL语言进行了简化,于是产生了BCPL(Basic Combined Programming Language)语言。
20世纪60年代,美国AT&T公司贝尔实验室(AT&T Bell Laboratories)的研究员肯·汤普森(Kenneth Lane Thompson)闲来无事,手痒难耐,想玩一个他自己编的,模拟在太阳系航行的电子游戏——Space Travel。他背着老板,找到了台空闲的小型计算机——PDP-7。但这台电脑没有操作系统,而游戏必须使用操作系统的一些功能,于是他着手为PDP-7开发操作系统。后来,这个操作系统被命名为——UNICS(Uniplexed Information and Computing Service)。
1969年,美国贝尔实验室的Ken Thompson,以BCPL语言为基础,设计出很简单且很接近硬件的B语言(取BCPL的首字母),并且用B语言写了初版UNIX操作系统(叫UNICS)。
1971年,同样酷爱Space Travel的丹尼斯·里奇为了能早点儿玩上游戏,加入了汤普森的开发项目,合作开发UNIX。他的主要工作是改造B语言,使其更成熟。 [6]
1972年,美国贝尔实验室的丹尼斯·里奇在B语言的基础上最终设计出了一种新的语言,他取了BCPL的第二个字母作为这种语言的名字,这就是C语言。
1973年初,C语言的主体完成。汤普森和里奇迫不及待地开始用它完全重写了UNIX。此时,编程的乐趣使他们已经完全忘记了那个“Space Travel”,一门心思地投入到了UNIX和C语言的开发中。随着UNIX的发展,C语言自身也在不断地完善。直到2020年,各种版本的UNIX内核和周边工具仍然使用C语言作为最主要的开发语言,其中还有不少继承汤普逊和里奇之手的代码。 [6]
在开发中,他们还考虑把UNIX移植到其他类型的计算机上使用。C语言强大的移植性(Portability)在此显现。机器语言和汇编语言都不具有移植性,为x86开发的程序,不可能在Alpha、SPARC和ARM等机器上运行。而C语言程序则可以使用在任意架构的处理器上,只要那种架构的处理器具有对应的C语言编译器和库,然后将C源代码编译、连接成目标二进制文件之后即可在哪种架构的处理器运行。 [6]
1977年,丹尼斯·里奇发表了不依赖于具体机器系统的C语言编译文本《可移植的C语言编译程序》。
C语言继续发展,在1982年,很多有识之士和美国国家标准协会(ANSI)为了使C语言健康地发展下去,决定成立C标准委员会,建立C语言的标准。委员会由硬件厂商、编译器及其他软件工具生产商、软件设计师、顾问、学术界人士、C语言作者和应用程序员组成。1989年,ANSI发布了第一个完整的C语言标准——ANSI X3.159-1989,简称“C89”,不过人们也习惯称其为“ANSI C”。C89在1990年被国际标准化组织(International Standard Organization,ISO)一字不改地采纳,ISO官方给予的名称为:ISO/IEC 9899,所以ISO/IEC9899:1990也通常被简称为“C90”。1999年,在做了一些必要的修正和完善后,ISO发布了新的C语言标准,命名为ISO/IEC 9899:1999,简称“C99”。 [6]在2011年12月8日,ISO又正式发布了新的标准,称为ISO/IEC9899:2011,简称为“C11”。
3.3 C语言的应用
广泛应用于底层开发。C语言能以简易的方式编译、处理低级存储器。C语言是仅产生少量的机器语言以及不需要任何运行环境支持便能运行的高效率程序设计语言。尽管C语言提供了许多低级处理的功能,但仍然保持着跨平台的特性,以一个标准规格写出的C语言程序可在包括类似嵌入式处理器以及超级计算机等作业平台的许多计算机平台上进行编译。
3.4 C语言的特点和缺点
主要特点
- 简明的语言
- 具有结构化的结构语句
- 丰富的数据类型
- 丰富的运算符
- 可对物理地址进行直接操作
- 代码具有较好的可移植性
- 可生产高质量、目标代码执行效率高的程序
特有特点
- 广泛性
- 简洁性
- 结构完善
缺点
-
C语言的缺点主要表现为数据的封装性弱,这一点使得C在数据的安全性上有很大缺陷,这也是C和C++的一大区别。
-
C语言的语法限制不太严格,对变量的类型约束不严格,影响程序的安全性,对数组下标越界不作检查等。从应用的角度,C语言比其他高级语言较难掌握。也就是说,对用C语言的人,要求对程序设计更熟练一些。
一、Visual Studio 安装
**预先攻其事必先利其器。**写代码也是如此,需要寻找一款“趁手”的 IDEA 来进行开发。
Visual Studio 2022 系统要求:
https://learn.microsoft.com/zh-cn/visualstudio/releases/2022/system-requirements
受支持的操作系统
在以下 64 位操作系统上支持 Visual Studio 2022:
- Windows 11 最低支持的 OS 版本或更高版本:家庭版、专业版、专业教育版、专业工作站版、企业版和教育版
- 可在此处找到支持的 Windows 11 OS:Windows 11 企业版和教育版支持
- Windows 10 最低支持的 OS 版本或更高版本:家庭版、专业版、教育版和企业版。
- 可在此处找到支持的 Windows 10 OS:Windows 10 企业版和教育版支持
- Windows Server Core 2022
- Windows Server Core 2019
- Windows Server 核心 2016
- Windows Server 2022:Standard 和 Datacenter。
- Windows Server 2019:Standard 和 Datacenter。
- Windows Server 2016:Standard 和 Datacenter。
不支持以下各项:
32 位和 ARM32 操作系统。
Windows 11 家庭版 S 模式、Windows IoT 企业版、Windows 10 IoT 核心板、Windows 10 企业版 LTSC 版本、Windows 10 S 和 Windows 10 团队版。 可以使用 Visual Studio 2022 生成在这些 Windows 版本上运行的应用。
Windows Server 的服务器 IoT 和最小服务器接口选项。
Windows 容器(排除 Visual Studio 生成工具)。
在虚拟机环境中运行,而无完整的 Windows 操作系统。
应用程序虚拟化解决方案,例如 Microsoft App-V for Windows 或 MSIX for Windows 或者第三方应用虚拟化技术。
多个用户在同一计算机上同时使用该软件,包括共享虚拟桌面基础结构计算机或共用 Windows 虚拟桌面主机池。
以下就是 Visual Studio 的安装示例:
Visual Studio 官网地址:
https://visualstudio.microsoft.com/zh-hans/
点击此处跳转
跳转到官网后在导航菜单点击下载
下载社区版
下载完启动器后,双击启动
点击继续,出现以下页面,等待其下载完毕
安装成功
向下方滑动找到使用C++的桌面开发选项,并勾选该选项
勾选完,点击安装,等待下载完毕,即可使用。
安装完成后,启动应用,点击启动 Visual Studio 即可进入。
二、C语言之 hello 项目的创建
首先,点击创建新项目选项,进入项目模块页面,选择控制台应用选项。
接着,给把项目名称改为 hello,修改文件存放位置,点击创建即可。
进入以下页面,VS 会自动生成 C++ 的代码
点击运行按钮,VS开始编译运行代码
效果如下:
这时候就有小伙伴疑惑了,我要如何编写C语言的代码呢?
不急请看以下操作:
- 在右侧边栏找到 hello.cpp 的文件,将文件后缀改为 .c
-
再将自动生成的代码全选删除,写入以下代码
#include // 注释 头文件// 入口函数int main() {printf("hello world!");} -
最后点击运行按钮或按 Ctrl + F5,运行代码即可。
三、C语言基础语法
1.常量与变量
#include int main() {// %d 占位符// 10 常量+ 20 常量+ 30 常量 在程序运行过程中,其值不会发生改变// 变量:其值可以改变的量被称为变量// 如何定义一个变量 数据类型 变量名 = 值;int a = 10;int b = 20;a = 30; // = 起到赋值的作用,跟数学的 "=" 用法不同printf("总价格为:%d", a + b);/** 变量规则:* 1. 不能以数字开头* 2. 不能以特殊符号开头,下划线 _ 可以* 3. 不能以关键字(标识符)开头,如:int*/}2.关键字
cuto brcak casc char const continue default do double else enw extern flcat for goto if int long register returm short signed sizeof static struct switch typedef uni on unsigned void volatile while 3.数据类型
3.1. 整型数据
#include // 引入头文件// 入口int main() {short bank_sum = 32768;unsigned int band_sum = 4294967297;int price = 1000;long sum = 1000L; // l L 1000L是一个长整型的常量int length = sizeof(sum); // sizeof() 计算数据类型占的字节数printf("总价格 %u", band_sum);int a1 = 123; //十进制int a2 = 0123; //八进制int a3 = 0x123; //十六进制 1-9 a b c d e fint a4 = 0b101; //二进制printf("%d", a4);printf("%o", a2);printf("%x", a3);// printf("%b", a4); 二进制的显示结果要自己写}int 4字节 -2,147,438,648 到 2,147,438,647 unsigned int 4字节 0 到 4,294,967,295 short 2字节 -32,768 到 32,768 unsigned short 2字节 0 到 65,535 long 4字节 -2,147,438,648 到 2,147,438,647 unsigned long 4字节 0 到 4,294,967,295 C语言中的占位符
%d 输出十进制的(有符号)整型
%u 输出无符号十进制的整数
%o 输出八进制(有符号)的整数
%x 输出十六进制(有符号)的整数
%c 输出字符
%s 输出字符串
%p 输出指针地址
拓展:
long 和 int 的区别
在16位系统中:long是4字节,int是2字节
在32位系统中:long是4字节,int是4字节
在64位系统中:long是4字节,int是4字节
指针的长度默认是 unsigned long
3.2 浮点型数据
#include // 引入头文件// 入口int main() {// 浮点型(实型)int price = 12;//float totalPrice = price * 0.9;//double totalPrice = price * 0.9;double num1 = 10 / 3; // 结果为3.00000double num2 = 10.0 / 3; // 结果为3.33333// 10 和 10.0 是两个不同数据类型的数,10是整型、10.0是浮点型,导致进行数学运算的结果不同。float a = 0.1f;// %f 输出单精度浮点数// %lf 输出双精度浮点数printf("价格 %f", num2);}关键字 字节 数值范围 float 4字节 3.4E-38 到 3.4E+38 double 8字节 1.7E-308 到 1.7E+308 3.3 字符串类型
#include // 引入头文件// 入口int main() {// 字符串类型printf("欢迎谢小然回来\n"); // 字符串常量 /* * 如何让一行字符进行换行?* 使用转义字符 \n*/char a = 'a'; // 单引号里面只能放一个字符// 双引号 其实是两个字符,占两个字节printf("字符是 %c", a);// %c 输出字符// 怎么存汉字? 汉字占两个字节// 怎么存多个字符?char str[] = "谢小然"; // 字符串要用双引号printf("字符串是 %s", str); // %s 输出字符串常量}常用的转义字符
\n 换行 \t 空格
unsigned char 空字符(NULL)\r 回车 \' 一个单引号 \" 一个双引号1字节 0 到 255 unsigned char 1字节 -128 到 127 4.运算符
a * b / c + 6
用算数运算符将运算对象(操作数)连接起来的,符合C语法规则的式子,称为C算术表达式,对象包括常量、变量和函数等。
运算符的分类:
1、双目运算符:即参加运算的操作数有两个
a++
2、单目运算符:参加运算的操作数只有一个 ++ 自增运算符,给变量值 +1
int a = b > c ? 10 : 20
3、三目运算符:即参加运算的操作符有三个 ()?())
4.1 算术运算符
#include int main() {int a = 10;int b = 3;int c = a % b;int time = 1000;int hours = time / 60;int mins = time % 60;printf("%d:%d", hours, mins);}+,-,*,/,%,+=.-=,/=,%=
4.2 关系运算符
复合运算符:
a += 1 相当于 a = a + 1;
4.3 逻辑运算符
>、=、int a = 28;//printf("结果是%d", a 30); // 结果是0/** 比较结果* 1 为真,成立* 0 为假,不成立*/int price = 28;if (price == 28) {printf("符合条件");}bool isMatch = true;printf("%d", isMatch);/** 布尔值类型 boolean true false* C89 没有布尔值类型* C99 增加了布尔值类型*/}
#include int main() {//printf("%d\n", 1 4);// && 同真为真,同假为假//printf("%d", 1 > 2 || 3 > 4);// || 一真为真,同假为假int price = 300; // 总价格int type = 1; // 类型 0 代表食品 1 家电 2 衣服int day = 2; // 6 星期六 7 星期日if (price > 200 && type == 1 && !(day == 6 || day == 7)) {price *= 0.7;printf("%d", price);}// && 短路int gender = 0; // 0 女 1 男gender == 0 && printf("女");// 左真才会执行}-
&& 逻辑与
两个条件为真,结果为真
-
|| 逻辑或
一个条件为真另一个条件为假,则结果为真;两个条件都为假,则结果为假
-
! 逻辑非
4.4 三目运算符
#include int main() {//int age = 20;/*if (age >= 18) {printf("您已成年");}*/int age = 20 >= 18 ? printf("您已经成年") : printf("未成年");}- 语法:条件 ? 条件为true时执行 : 条件为false时执行
4.5 逗号运算符
#include int main() {// 定义一个整数,先加一再平方int num = 10;num = (num += 1, num * num);printf("结果是%d", num); // 121}int a = (1,2) 逗号运算符的结果是后边表达式的结果
4.6 自增自减运算符
- 进行自增运算
- ++
- 分成两种,前置 ++ 和后置 ++
- 前置 ++,会先把值自动加一,再返回
- 后置 ++,会先把值返回,再自动加一
- 进行自减
- --
- 分两种,前置 - - 和后置 - -
- 前置 - -,会先把值自动减一,再返回
- 后置 - -,会先把值返回,再自动减一
#include int main() {int a = 1;//a++;//++a;int result = 0;result = 10 + a++; // 先计算再自增//result = 10 + ++a; // 先自增再计算printf("%d %d", result, a);}
- 在表达式中按照优先级先后进行运算,优先级高的先于优先级低的运算
4.7 运算符的优先级
运算符优先级
sum = x + y + z;
- 优先级一样的按结构性来运算
运算符结构性
左结合性:从左向右运算
#include int main() {int a, b, c, d;a = b = c = d = 100;a += b += c += d += 100;printf("%d %d %d %d", a, b, c, d); // 500 400 300 200}右结合性:从右向左运算
5.分支语句
5.1 if 分支结构
#include int main() {//int age = 18;//if (age >= 18) {//// 条件达成//printf("您已成年");//}//else {//// 条件未达成//printf("您还未成年");//}int price = 100; // 满200减20 满100减10if (price >= 200) {price -= 20;printf("%d", price);}else if (price >= 100) {price -= 10;printf("%d", price);}else {printf("%d", price);}}if 语句
通过一个 if 语句来决定代码是否执行
语法: if (判断条件) {要执行的代码}
if else 语句
通过 if 条件来决定执行哪一个 {} 里面的代码
语法: if (判断条件) {条件为true时要执行的代码} else {条件为false时要执行的代码}
if else if 语句
通过 if 和 else if 来设置多个条件进行判断
语法: if(条件1) {条件1为true时要执行的代码} else if (条件2) {条件2为true时要执行的代码}
#include int main() {/*案例1:判断奇偶数被 2 整除的是偶数;不能被 2 整除的是奇数*/int num = 0;printf("请输入数字\n");scanf_s("%d", &num);/*if (num % 2 == 0) {printf("%d是偶数", num);}else {printf("%d是奇数", num);}*/if (!(num % 2)) {printf("%d是偶数", num);}else {printf("%d是奇数", num);}/* 案例2:根据0~100的数字输出成绩[90, 100] 输出A[80, 90) 输出A[70, 80) 输出A[60, 70) 输出A[0, 60) 输出A*/int score = 0;printf("请输入成绩\n");scanf_s("%d", &score);if (score >= 90) {printf("A");} else if (score >= 80) {printf("B");}else if (score >= 70) {printf("C");}else if (score >= 60) {printf("D");}else {printf("E");}/*案例3:判断闰年什么是闰年?世纪闰年:公历年份是整百数的,必须是400的倍数才是闰年普通闰年:公历年份是4的倍数,且不是100的倍数世纪闰年: year % 400 == 0普通闰年: year % 4 == 0 && year % 100 != 0*/int year = 0;printf("请输入年份\n");scanf_s("%d", &year);if (year % 400 == 0 || year % 4 == 0 && year % 100 != 0) {printf("%d是闰年", year);}else {printf("%d是平年", year);}}案例
5.2 switch 条件分支结构
switch (要判断的变量) { case 情况1: 情况1要执行的代码 break; case 情况2: 情况2要执行的代码 break; default: 上述情况不满足时执行的代码}-
对于某一个变量的判断
-
语法:
#include int main() { /* 案例:根据成绩匹配相应的等级 */int score = 0;printf("请输入成绩0——100: \n");scanf_s("%d", &score);int flag = score / 10;switch (flag) {case 10:case 9:printf("A");break;case 8:printf("B");break;case 7:printf("C");break;case 6:printf("D");break;default:printf("E");}}案例
6.循环结构
- 循环结构,就是根据某些给出的条件,重复执行同一段代码
- 初始化
- 循环必须要有某些固定的内容组成
- 条件判断
- 要执行的代码
- 自身改变
6.1 while 循环
6.2 do while 循环
语法: while (条件) {满足条件就执行}
因为满足条件就执行,所以我们写的时候一定要注意,设定一个边界值,否则程序会一直执行下去
#include int main() {// 初始条件int n = 0;while (n#include int main() {int n = 1;int sum = 0;do {sum += n;n++;} while (n // 案例1:利用 for循环计算1-100的和/*int sum = 0;for (int i = 1; i if (i % 400 == 0 || i % 100 != 0) {printf("%d是闰年 ", i);count++;if (count % 4 == 0) {printf("\n");}}} //案例5:求质数 质数除了一和本身没有其他的约数 printf("请输入数字:\n"); int num = 0; scanf_s("%d", &num); int flag = true; for (int i = 2; i age);*/struct student stu[3] = {{"谢小然", 18},{"顾优", 20},{"陈轩宇", 18}};struct student* sp = stu;printf("%s\n", sp[0].name);printf("%d\n", sp[0].age);printf("%s\n", (sp + 1)->name);printf("%s\n", (sp + 2)->name);}do while 循环,先执行一次代码,然后再进行条件判断
语法: do {要执行的代码} while (条件)
5. 结构体与函数
6. 结构体内存分配
给函数传结构体变量的地址
#include #include #include typedef struct student {char name[100];int score;} STU;void input(STU *p) {printf("录入名字和成绩\n");scanf_s("%s%d", p->name, 100, &p->score);}float sumfunc(STU *p, int num) {float sum = 0;for (int i = 0; i score;}return sum;}int main() {int num = 0;printf("请输入要录入的人数\n");scanf_s("%d", &num);STU* stu = (STU*)malloc(num * sizeof(STU));if (!stu) return;for (int i = 0; i - 成员中只有 char 型数据,以1个字节开为单位辟内存;
- 出现 short int 类型数据,没有比其更大的数据时,以2个字节为单位开辟内存;
- 出现 int float 类型数据,没有比其更大的数据时,以4个字节为单位开辟内存;
- 出现 double 类型数据,以 8 个字节为单位开辟内存。
- char 1字节对齐,即存放 char 型的变量,内存单元的编号是 1 的倍数即可;
- short int 2字节对齐,起始内存单元的编号是 2 的倍数即可;
- int 4字节对齐,起始内存单元的编号是 4 的倍数即可;
- long int 在32位系统下,4字节对齐,起始内存单元的编号是 4 的倍数即可;
- float 4字节对齐,起始内存单元的编号是 4 的倍数即可;
- double 8字节对齐,起始内存单元的编号是 8 的倍数即可。
结构体变量大小是所有成员大小之和。
规则1
以多少个字节位单位开辟内存给结构体变量分配内存时,会去结构体变量中找基本类型的成员。
哪个基本类型的成员占字节数多,就以它的大小开辟内存。
规则2
struct stu { char a; int b;};字节对齐的好处
用空间来换时间,提高 cpu 读取数据的效率
7. 链表
#include #include struct student {char name[100];int iNumber;struct student* pNext;};struct student* Create() {struct student* pHead = NULL;struct student* pEnd, * pNew;pEnd = pNew = (struct student*)malloc(sizeof(struct student));if (!pNew) return NULL;printf("请录入姓名和学号,输入数字0则会退出操作\n");scanf_s("%s", pNew->name, 100);scanf_s("%d", &pNew->iNumber);pNew->pNext = NULL;if (pNew->iNumber != 0) {pHead = pNew;}while (pNew->iNumber != 0) {pNew = (struct student*)malloc(sizeof(struct student));if (!pNew) return NULL;scanf_s("%s", pNew->name, 100);scanf_s("%d", &pNew->iNumber);if (pNew->iNumber != 0) {pNew->pNext = NULL;pEnd->pNext = pNew;pEnd = pNew;}}return pHead;}void Print(struct student* pHead) {struct student* pTemp = pHead;while (pTemp != NULL) {printf("%s,%d\n", pTemp->name, pTemp->iNumber);pTemp = pTemp->pNext;}}int main() {struct student* pHead = Create();Print(pHead);}概念:链表是一种五路存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中指针连接次序实现的。
七、共用体
typedef struct { char name[100]; int score;}stu;typedef struct { char name[100]; int salary;}tea;typedef union { stu student; tea teacher;}any;共用体和结构体类似,也是一种构造类型的数据结构。
几个不同的变量共同占用一段内存的结构,在C语言中,被称为“共用体”类型结构
共用体所有成员占有一段地址空间,共用体的大小是其占用内存长度最大的 成员的大小
-
- 同一内存段可以用来存放几种不同类型的成员,但每一瞬间只有一种起作用
- 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖
- 共用体变量的地址和它的各成员的地址都是同一地址
共用体的特点:
八、 枚举
enum TYPE {STU = 1, TEA};enum TYPE type;if (type == STU) { // ...}将变量的值一一列举出来,变量的值只限于列举出来的值的范围内
语法:enum 枚举类型名 {枚举值列表};
在枚举值表中应列出所有可用值,也称为枚举元素;
枚举元素是常量,编号默认是从 0 开始的
- 宏定义是一个值/表达式,不是一种类型
- 枚举是一种类型,可以定义枚举类型的一个变量
1. 原码 反码 补码
2. 位运算
正数在内存中以原码形式存放,负数在内存中以补码形式存放
正数的原码 = 反码 = 补码
原码:将一个整数,转换成二进制,就是其原码。(如字节的 5 的原码为:0000 0101;则 -5 的原码为 1000 0101。)
反码:正数的反码就是其原码;负数的反码是将原码中,除符号位以外,每一位取反。(如单字节的 5 的反码为:0000 0101;-5 的反码为 1111 1010。)
补码:正数的补码就是其原码;负数的反码 +1 就是补码。(如单字节的 5 的补码为:0000 0101; -5 的补码为 1111 1011。)
2. 选择性编译
无论是正数还是负数,编译系统都是按照内存中存储的内容进行位运算。
-
& 按位 与
任何值与 0 得 0,与 1 保持不变
-
| 按位 或
任何值或 1 得 1,或 0 保持不变
-
~ 按位取反
1 变 0,0 变 1
-
^ 按位异或
相异得 1,相同得 0
-
位移
>> 右移 b?a:b)
将来在预处理时,替换成实参代替字符串的形参,其他字符保留
带参宏和带参函数的区别
- 带参宏被调用多少次就会展开多少次,执行代码的时候没有函数调用的过程,不需要压栈弹栈。所以带参宏
是浪费了空间,因为被展开多次,节省时间。
- 带参函数,代码只有一份,存在代码段,调用的时候去代码段取指令,调用的时候要,压栈弹栈。有个调用的
过程。所以说,带参函数是浪费了时间,节省了空间。
- 带参函数的形参是有类型的,带参宏的形参没有类型名。
#ifdef XXR代码段一#else 代码段二#endif
-
#ifndef XXR代码段一#else 代码段二#endif
-
#if XXR == 1代码段一#elif XXR == 2 代码段二#else 代码段三#endif
- if else 语句都会被编译,通过条件选择性执行代码
-
- 选择性编译,只有一块代码被编译
1. 初始文件
2. 标准 io 库函数对磁盘文件的读取
文件用来存放程序、文档、音频、视频数据、图片数据。
文件就是存放在磁盘上的一些数据集合。
磁盘文件:指一组相关数据的有序集合,通常存储在外介质(如磁盘上),使用时才调入内存。
设别文件:在操作系统中把每一个与主机相连的输入、输出设备看作是一个文件,把它们的输入、输出等同于对磁盘文件的读和写。
键盘:标准输入文件
屏幕:标准输出文件
其他设备:打印机、触摸屏、摄像头、音箱等
3. 磁盘文件的分类
文件缓冲区是库函数申请的一段内存,由库函数对其进行操作,程序员没有必要知道存放在哪里,只需要知道对文件操作时的一些缓冲特点即可。
- 文本文件:基于字符编码的文件
- 二进制文件:基于值编码的文件
文本文件、二进制文件对比
译码:
文本文件编码基于字符定长,译码容易些;
二进制文件编码是变长的,译码难一些(不同的二进制文件格式,有不同的译码方式,一般需要特定软件进行译码)。
空间利用率
二进制文件用一个比特来代表一个意思(位操作);
而文本文件任何一个意思至少是一个字符。
所以二进制文件,空间利用率高。
可读性:
文本文件用通用的记事本工具就几乎可以浏览所有文本文件
二进制文件需要一个具体的文件解码器
typedef struct _iobuf { int cnt; // 剩余的字符,如果输入缓冲区,那么就表示缓冲区还有多少个字符未被读取 char *ptr; // 下一个要被读取的字符的地址 char *base; // 缓冲区基地址 int flag; // 读写状态标志位 int fd; // 文件描述符}FILE;文件指针在程序中用来标识(代表)一个文件的,在打开文件的时候得到文件指针,文件指针就用来代表咱们打开的文件。
FILE*指针变量标识符;
- 对文件进行读写等操作之前 要打开文件得到文件指针
- 可以通过文件指针对文件进行读写等操作
- 读写等操作完毕后,要关闭文件,关闭文件后,就不能在通过此文件指针操作文件了
- 参数1:打开的文件路径
- 参数2:文件打开的方式,即以什么方式打开 r w a +
功能 r 或 rb 以只读方式打开一个文本文件(不创建文件) w 或 wb 以写方式打开文件(使文件长度截断为0字节创建一个文件) a 或 ab 以追加方式打开文件,即在末尾添加内容,当文件不存在时,创建文件用于写 r+ 或 rb+ 以可读、可写的方式打开文件(不创建新文件) w 或 wb+ 以可读、可写的方式打开文件(使文件长度为0字节,创建一个文件) a 或 ab+ 以追加方式打开文件,打开文件并在末尾更改文件(如果文件不存在,则创建文件) - 成功:打开文件对应的文件指针
返回值:
- 失败:返回 NULL
- 成功返回 0
- 失败返回非0
#include int main() {FILE* fp1, * fp2;char ch;int error1 = fopen_s(&fp1, "xxr.txt", "r");int error2 = fopen_s(&fp2, "copy.txt", "a");if (error1 != 0) {printf("打开错误");return;}if (error2 != 0) {printf("打开错误");return;}while ((ch = fgetc(fp1)) != EOF) {//printf("%c", ch);fputc(ch, stdout);fputc(ch, fp2);}fclose(fp1);fclose(fp2);}int fgetc(FILE *stream);
fgetc 从 stream 所标识的文件中读取一个字节,将字节值返回
**返回值:**读到文件结尾返回 EOF
EOF 是在 stdio.h 文件中定义的符号常量,值为-1
int fputc(int C, FILE *stream)
fputc将c的值写到 stream 所代表的文件中。
返回值:
如果输出成功,则返回输出的字节值;
如果输出失败,则返回一个 EOF。
8.fgets 与 fputs
#include int main() {FILE* fp1, * fp2;int error1 = fopen_s(&fp1, "xxr.txt", "r");int error2 = fopen_s(&fp2, "copy.txt", "a");if (error1 != 0) {printf("打开错误");return;}if (error2 != 0) {printf("打开错误");return;}char str[100];fgets(str, 100, fp1);printf("*%s*", str);fputs(str, fp2);fclose(fp1);fclose(fp2);}char *fgets(char *s, int size, FILE *stream);
从 stream 所代表的文件中读取字符,在读取的时候碰到换行符或者是碰到文件的末尾停止读取,或者是读取了
size-1个字节停止读取,在读取的内容后面会加一个10, 作为字符串的结尾
int fputs(const char *s, FILE *stream);
将s指向的字符串,写到 stream 所代表的文件中
9.fread
10.fwrite
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread 函数从 stream 所标识的文件中读取数据,每块是 size个字节,共nmemb块,存放到ptr指向的内存里返回值:实际读到的块数。
11.rewind
size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream);
fwrite 函数将 ptr 指向的内存里的数据,向stream 所标识的文件中写入数据,每块是size 个字节,共nmemb 块。
12.fseek
rewind 复位读写位置
void rewind(文件指针);
把文件内部的位置指针移到文件首
- 文件开头 SEEK SET O
- 文件当前位置 SEEK_CUR 1
6. fclose
int fclose(FILE *fp);
关闭 fp 所代表的文件
返回值:
7. fgetc 和 fputc
int fseek(FILE *stream, long offset, int whence);
移动文件流的读写位置.
whence 起始位置
#include #include int main() {printf("注册密码:");char password[7] = "";scanf_s("%s", password, 7);FILE* fp;int error = fopen_s(&fp, "xxr.txt", "w+");if (error != 0) {return;}fputs(password, fp);printf("注册成功\n");printf("登录密码:");scanf_s("%s", password, 7);//rewind(fp);//fseek(fp, 0, SEEK_SET);//fseek(fp, -6, SEEK_CUR);fseek(fp, -6, SEEK_END);char password_db[7] = "";fgets(password_db, 7, fp);int result = strcmp(password, password_db);if (result == 0) {printf("登录成功");}else {printf("登录失败%d", result);}} - 文件未尾 SEEK_END 2
位移量:以起始点为基点,向前、后移动的字节数,正数往文件末尾方向偏移,负数往文件开头方向偏移。
13. ftell
1ong int length;length = fte11(fp)
测文件读写位置距文件开始有多少个字节
long ftell(文件指针);
返回值:返回当前读写位置(距离文件起始的字节数),出错时返回-1.
注意和 if else 语句的区别
十一、 文件
一个文件通常是磁盘上一段命名的存储区,计算机的存储在物理上是二进制的,所以物理上所以的磁盘文件本质上都是一样的,以字节为单位进行顺序存储。
从用户或者操作系统使用的角度
把文件分为:
4. 文件指针
在缓冲文件系统中,每个被使用的文件都要在内存中开辟一块 FILE 类型的区域,存放与操作文件相关的信息
对文件操作的步骤:
5. fopen
FILE *fopen(const char *path, const char *mode);
函数的参数:
模式 - 带参宏被调用多少次就会展开多少次,执行代码的时候没有函数调用的过程,不需要压栈弹栈。所以带参宏
-
注意: