有穷性
在有限的操作步骤内完成。有穷性是算法的重要特性,任何一个问题的解决不论其采取什么样的算法,其终归是要把问题解决好。如果一种算法的执行时间是无限的,或在期望的时间内没有完成,那么这种算法就是无用和徒劳的,我们不能称其为算
法。
确定性
每个步骤确定,步骤的结果确定。算法中的每一个步骤其目的应该是明确的,对问题的解决是有贡献的。如果采取了一系列步骤而问题没有得到彻底的解决,也就达不到目的,则该步骤是无意义的。
可行性
每个步骤有效执行,得到确定的结果。每一个具体步骤在通过计算机实现时应能够使计算机完成,如果这一步骤在计算机上无法实现,也就达不到预期的目的,那么这一步骤是不完善的和不正确的,是不可行的。
零个或多个输入
从外界获得信息。算法的过程可以无数据输入,也可以有多种类型的多个数据输入,需根据具体的问题加以分析。
一个或多个输出
算法得到的结果就是算法的输出(不一定就是打印输出)。算法1 的目的是为解决一个具体问题,一旦问题得以解决,就说明采取的算法是正确的,而结果的输出正是验证这一目的的最好方式。
2.1 计算机语言
计算机语言(ComputerLanguage) 指用于人与计算机之间通讯的语言。计算机语言是人与计算机之间传递信息的媒介。计算机系统最大特征是指令通过一种语言传达给机器。为了使电子计算机进行各种工作,就需要有一套用以编写计算机程序的数字、字符和语法规划,由这些字符和语法规则组成计算机各种指令(或各种语句)。这些就是计算机能接受的语言。
2.2 分类
#include // 头文件
// 入口函数
int main() {
printf("Hello World!\n"); //打印输出
}
// 注释
2.3 解释型vs编译型
解释型语言:解释性语言编写的程序不进行预先编译,以文本方式存储程序代码。执行时才翻译执行。程序每执行一次就要翻译一遍。
优缺点:跨平台能力强,易于调,执行速度慢。
编译型语言: 编译型语言在执行之前要先经过编译过程,编译成为一个可执行的机器语言的文件,比如exe。
因为翻译只做一遍,以后都不需要翻译,所以执行效率高。编译型语言的优缺点:执行效率高,缺点是跨平台能力弱,不便调试。
C语言是一门面向过程的计算机编程语言,与C++、C#、Java等面向对象编程语言有所不同。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、仅产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言描述问题比汇编语言迅速、工作量小、可读性好、易于调试、修改和移植,而代码质量与汇编语言相当。C语言一般只比汇编语言代码生成的目标程序效率低10%-20%。因此,C语言可以编写系统软件。
C语言诞生于美国的贝尔实验室,由丹尼斯·里奇(Dennis MacAlistair Ritchie)以肯尼斯·蓝·汤普森(Kenneth Lane Thompson)设计的B语言为基础发展而来,在它的主体设计完成后,汤普森和里奇用它完全重写了UNIX,且随着UNIX的发展,c语言也得到了不断的完善。为了利于C语言的全面推广,许多专家学者和硬件厂商联合组成了C语言标准委员会,并在之后的1989年,诞生了第一个完备的C标准,简称“C89”,也就是“[ANSI C](https://baike.baidu.com/item/ANSI C/7657277?fromModule=lemma_inlink)”,截至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”。
广泛应用于底层开发。C语言能以简易的方式编译、处理低级存储器。C语言是仅产生少量的机器语言以及不需要任何运行环境支持便能运行的高效率程序设计语言。尽管C语言提供了许多低级处理的功能,但仍然保持着跨平台的特性,以一个标准规格写出的C语言程序可在包括类似嵌入式处理器以及超级计算机等作业平台的许多计算机平台上进行编译。
主要特点
特有特点
缺点
C语言的缺点主要表现为数据的封装性弱,这一点使得C在数据的安全性上有很大缺陷,这也是C和C++的一大区别。
C语言的语法限制不太严格,对变量的类型约束不严格,影响程序的安全性,对数组下标越界不作检查等。从应用的角度,C语言比其他高级语言较难掌握。也就是说,对用C语言的人,要求对程序设计更熟练一些。
**预先攻其事必先利其器。**写代码也是如此,需要寻找一款“趁手”的 IDEA 来进行开发。
Visual Studio 2022 系统要求:
https://learn.microsoft.com/zh-cn/visualstudio/releases/2022/system-requirements
受支持的操作系统
在以下 64 位操作系统上支持 Visual Studio 2022:
不支持以下各项:
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 即可进入。
以上就是完整的安装示例。
首先,点击创建新项目选项,进入项目模块页面,选择控制台应用选项。
接着,给把项目名称改为 hello,修改文件存放位置,点击创建即可。
进入以下页面,VS 会自动生成 C++ 的代码
点击运行按钮,VS开始编译运行代码
效果如下:
这时候就有小伙伴疑惑了,我要如何编写C语言的代码呢?
不急请看以下操作:
再将自动生成的代码全选删除,写入以下代码
#include // 注释 头文件
// 入口函数
int main() {
printf("hello world!");
}
最后点击运行按钮或按 Ctrl + F5,运行代码即可。
#include
int main() {
// %d 占位符
// 10 常量+ 20 常量+ 30 常量 在程序运行过程中,其值不会发生改变
// 变量:其值可以改变的量被称为变量
// 如何定义一个变量 数据类型 变量名 = 值;
int a = 10;
int b = 20;
a = 30; // = 起到赋值的作用,跟数学的 "=" 用法不同
printf("总价格为:%d", a + b);
/*
* 变量规则:
* 1. 不能以数字开头
* 2. 不能以特殊符号开头,下划线 _ 可以
* 3. 不能以关键字(标识符)开头,如:int
*/
}
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 |
#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 f
int 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
#include // 引入头文件
// 入口
int main() {
// 浮点型(实型)
int price = 12;
//float totalPrice = price * 0.9;
//double totalPrice = price * 0.9;
double num1 = 10 / 3; // 结果为3.00000
double 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 |
#include // 引入头文件
// 入口
int main() {
// 字符串类型
printf("欢迎谢小然回来\n"); // 字符串常量
/*
* 如何让一行字符进行换行?
* 使用转义字符 \n
*/
char a = 'a'; // 单引号里面只能放一个字符
// 双引号 其实是两个字符,占两个字节
printf("字符是 %c", a);
// %c 输出字符
// 怎么存汉字? 汉字占两个字节
// 怎么存多个字符?
char str[] = "谢小然"; // 字符串要用双引号
printf("字符串是 %s", str); // %s 输出字符串常量
}
常用的转义字符
\n 换行
\t 空格
\0 空字符(NULL)
\r 回车 \' 一个单引号 \" 一个双引号
unsigned char | 1字节 | 0 到 255 |
---|---|---|
unsigned char | 1字节 | -128 到 127 |
用算数运算符将运算对象(操作数)连接起来的,符合C语法规则的式子,称为C算术表达式,对象包括常量、变量和函数等。
运算符的分类:
1、双目运算符:即参加运算的操作数有两个
a * b / c + 6
2、单目运算符:参加运算的操作数只有一个 ++ 自增运算符,给变量值 +1
a++
3、三目运算符:即参加运算的操作符有三个 ()?())
int a = b > c ? 10 : 20
+,-,*,/,%,+=.-=,/=,%=
#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);
}
复合运算符:
a += 1 相当于 a = a + 1;
>、<、==、>=、<=、!=
#include
#include // 引入该头文件才能使用布尔值类型
int main() {
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 < 2 && 3 > 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("女");
// 左真才会执行
}
条件 ? 条件为true时执行 : 条件为false时执行
#include
int main() {
//int age = 20;
/*if (age >= 18) {
printf("您已成年");
}*/
int age = 20 >= 18 ? printf("您已经成年") : printf("未成年");
}
int a = (1,2)
逗号运算符的结果是后边表达式的结果
#include
int main() {
// 定义一个整数,先加一再平方
int num = 10;
num = (num += 1, num * num);
printf("结果是%d", num); // 121
}
++
--
#include
int main() {
int a = 1;
//a++;
//++a;
int result = 0;
result = 10 + a++; // 先计算再自增
//result = 10 + ++a; // 先自增再计算
printf("%d %d", result, a);
}
运算符优先级
运算符结构性
左结合性:从左向右运算
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
}
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() {
//int age = 18;
//if (age >= 18) {
// // 条件达成
// printf("您已成年");
//}
//else {
// // 条件未达成
// printf("您还未成年");
//}
int price = 100; // 满200减20 满100减10
if (price >= 200) {
price -= 20;
printf("%d", price);
}
else if (price >= 100) {
price -= 10;
printf("%d", price);
}
else {
printf("%d", price);
}
}
案例
#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);
}
}
对于某一个变量的判断
语法:
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");
}
}
语法: while (条件) {满足条件就执行}
因为满足条件就执行,所以我们写的时候一定要注意,设定一个边界值,否则程序会一直执行下去
#include
int main() {
// 初始条件
int n = 0;
while (n < 10) {
printf("%d\n", n);
n++; // 调整步长
}
}#include <stdio.h>
int main() {
// 案例1 利用 while 循环计算1-100之间的和
/*int sum = 0;
int num = 1;
while (num <= 100) {
sum += num;
num++;
}
printf("1-100之间的和为:%d", sum);*/
// 案例2 利用 while 循环算数的阶乘
printf("请输入数字:\n");
int num = 0; // 3! = 3*2*1
scanf_s("%d", &num);
int temp = num;
int f = 1;
while (num >= 1) {
f *= num;
num--;
}
printf("%d的阶乘是:%d", temp, f);
}
do while 循环,先执行一次代码,然后再进行条件判断
语法: do {要执行的代码} while (条件)
#include
int main() {
int n = 1;
int sum = 0;
do {
sum += n;
n++;
} while (n <= 100);
printf("%d", sum);
}
语法: for (int i = 0; i < 10; i++) {要执行的代码}
#include
int main() {
// 案例1:利用 for循环计算1-100的和
/*int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}*/
//printf("%d", sum);
//案例2:求用户输入数字的阶乘
/*printf("请输入数字:\n");
int num = 0;
scanf_s("%d", &num);
int sum = 1;
for (int i = num; i >= 1; i--) {
sum *= i;
}
printf("%d", sum);*/
// 案例3:计算1000-2000年之间所有的闰年
// 世纪闰年 是400的整倍数
// 普通闰年 是4的倍数且不能被100整除
/*for (int i = 2000; i <= 2023; i++) {
if (i % 400 == 0 || i % 4 == 0 && i % 100 != 0) {
printf("%d是闰年\n", i);
}
}*/
// 案例4:计算用户输入某年到某年之间所有的闰年
printf("请输入开始年份与结束年份:\n");
int beginYear = 0;
int endYear = 0;
scanf_s("%d%d", &beginYear, &endYear);
int count = 0;
for (int i = beginYear; i <= endYear; i+=4) {
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 < num; i++) {
if (num % i == 0) {
flag = false;
}
}
printf("%d", flag);
if (flag) {
printf("%d是质数", num);
}
else {
printf("%d不是质数", num);
}
int i;
for (i = 2; i <= num / 2; i++) {
if (num % i == 0) {
break;
}
}
if (i <= num / 2) {
printf("%d不是质数", num);
}
else {
printf("%d是质数", num);
}
// 案例6:打印九九乘法表
/*
* 1*1=1
* 1*2=2 2*2=4
* 1*3=3 2*3=6 3*3=9
* 1*4=4 2*4=8 3*4=12 4*4=16
* 1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
* 1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
* 1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
* 1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
* 1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
* 1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
*/
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
printf("%d*%d=%d\t",j,i,i*j);
}
printf("\n");
}
}
循环控制语句
函数就是把任意一段代码包装起来,在需要执行的时候,就调用这个函数
#include
// 定义一个函数
sayHello() {
printf("hello");
}
int main() {
sayHello(); // 调用函数
}
在定义函数是出现的()
是存放参数的位置,参数分为实参和形参。
void function(形参写这里) {
// 一段代码
}
function(实参写这里)
形参与实参的作用
,
隔开#include
// 单个形参
isEven(int num) {
if (num % 2 == 0) {
printf("%d是偶数\n", num);
}
else {
printf("%d是奇数\n", num);
}
}
// 两个或多个形参
getSum(int x, int y, int z) {
int sum = 0;
sum = x + y + z;
printf("%d", sum);
}
int main() {
isEven(3);
isEven(4);
isEven(89);
getSum(10, 90, 454);
}
return
返回的意思,其实就是给函数一个返回值 和 中断函数
返回值
函数调用本身也是一个表达式,表达式就应该有一个值出现
return
关键字就是可以给函数执行完毕一个结果
#include
// 两个或多个形参
int getSum(int x, int y, int z) {
int sum = 0;
sum = x + y + z;
//printf("%d", sum);
return sum;
}
int main() {
int mySum = getSum(10, 20, 30);
printf("%d", mySum); // 结果为 60
}
中断函数
return
关键字可以将函数中断,在 return
之后的代码将不再执行为什么要声明函数?
编译器在编译C语言时,程序是从上到下执行的,如果没有对函数进行声明,则编译器不认识该函数。
直接声明法
#include
// 直接声明法
void sayHello();
int main() {
sayHello();
}
void sayHello() {
printf("hello");
}
间接声明法
将函数的声明放在头文件中,.c文件中包含头文件即可
main.c
#include "k.h"
k.h
extern void func(void);
使用函数的好处?
- 定义一次,可以多次调用,减少代码的冗余度。
- 使代码模块化更好,方便调试程序,方便阅读。
1). 堆
在动态申请内存的时候,在堆里开辟内存。
2). 栈
主要存放局部变量。
3). 静态全局区
1:未初始化的静态全局区
静态变量(定义变量的时候,前面加 static修饰),或全局变量,没有初始化的,存在此区
2:初始化的静态全局区
全局变量、静态变量、赋过初值的,存放此区
4). 代码区
存放程序代码
5). 文字常量区
存放常量
在函数外部定义的变量成为全局变量
int num = 100; // 这就是一个全局变量
int main() {
return 0;
}
作用范围:
生命周期:
注意:
static int num = 100; // 这就是一个静态全局变量
int main() {
return 0;
}
作用范围: 只能在它定义的.c(源文件)中有效
生命周期:在程序的整个运行过程中,一直存在
注意:定义静态全局变量的时候,如果不赋值,它的默认值为0
在函数中定义或在复合语句中定义的变量成为局部变量
int main() {
int num; //普通局部变量
if (1) {
int a; // 普通局部变量
}
}
作用范围:
定义局部变量的时候,前面加 static 修饰作用范围:在它定义的函数或复合语句中有效。
生命周期:第一次调用函数的时候,开辟空间赋值,函数结束后,不释放,以后再调用函数的时候,就不再为其开辟空间,也不赋初值,用的是以前的那个变量。
在定义函数的时候,返回值类型前面加 static 修饰,这样的函数被称为静态函数。
static 限定了函数的作用范围,在定义的.c 文件中有效。
数组是若干个行同类型的变量在内存中有序存储的集合。
字符数组
char s[10];
短整型数组
shor a[10];
整形数组
int a[10];
长整型数组
long a[5];
浮点型数组
float a[6]; a[4] = 3.14f;
``double a[8]; a[7] = 3.115926;`
指针数组
char *a[10]
结构体数组
struct student a[10]
数组名 [行下标] [列下标]
int a[3][3]
#include
int main() {
int grades[2][2] = {
{10,20},
{30,40}
};
printf("%d\n", grades[0][1]);
char str[][100] = { "张三", "李四", "Mike" };
for (int i = 0; i < sizeof(str) / (sizeof(char) * 100); i++) {
printf("%s\n", str[i]);
}
printf("%d\n", str[0][2]);
}
#include
int main() {
int arr[10] = { 4,1,6,78,64,88,67,32,70,23 };
for (int i = 0; i < sizeof(arr) / sizeof(int) - 1; i++) {
for (int j = 0; j < sizeof(arr) / sizeof(int) - i - 1; j++) {
if (arr[j + 1] > arr[j]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
if (i == 0) {
printf("{%d,", arr[i]);
}
else if (i == sizeof(arr) / sizeof(int) - 1) {
printf("%d}", arr[i]);
}
else {
printf("%d,", arr[i]);
}
}
}
#include
int main() {
int arr[] = { 21,3,5,76,32,65,11,45,1,54 };
int index = 0; // 假定第0个位置对应的数字是最小的
//for (int i = 1; i < sizeof(arr) / sizeof(int); i++) {
// if (arr[i] < arr[index]) {
// index = i;
// //printf("%d", index);
// }
//}
//int temp = arr[0];
//arr[0] = arr[index];
//arr[index] = temp;
//int index2 = 1; // 假定第1个位置对应的数字是最小的
//for (int i = 2; i < sizeof(arr) / sizeof(int); i++) {
// if (arr[i] < arr[index2]) {
// index2 = i;
// //printf("%d", index);
// }
//}
//int temp2 = arr[1];
//arr[1] = arr[index2];
//arr[index2] = temp2;
for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
int index = i;
for (int j = i + 1; j < sizeof(arr) / sizeof(int); j++) {
if (arr[index] > arr[j]) {
index = j;
}
}
int temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}
for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
if (i == 0) {
printf("{%d,", arr[i]);
}
else if (i == sizeof(arr) / sizeof(int) - 1) {
printf("%d}", arr[i]);
}
else {
printf("%d," , arr[i]);
}
}
}
系统给虚拟内存的每个存储单元分配了一个编号,从0x00 00 00 00 ~ 0xff ff ff ff
这个编号咱们称之为地址,指针就是地址
指正变量:用来存放地址编号的变量
在32位平台下,地址总线是32位的,所以地址是32位编号,指针变量是32位的即4个字节
字符变量 char ch = 'b'; ch 占 1 个字节,它有一个地址编号,这个地址编号就是 ch 的地址整型变量 int a = 0x12 34 56 78; a 占 4 个字符,它占有 4 个字节的存储单位,有4个地址编号。
简单的指针变量
数据类型 *指针变量名
int *p; // 定义了一个指针变量 p
在定义指针变量的时候 * 是用来修饰变量的,说明变量 p 是个指针变量
关于指针的运算符
& 取地址、* 取值
int a = 0x1234abcd;
int *p; // 在定义指针变量时,*代表修饰的意思,修饰 p 是个指针变量
p = &a; // 把 a 的地址给 p 赋值,&是取地址符。
#include
void swap(int *num1, int *num2) {
int temp = *num1;
*num1 = *num2;
*num2 = temp;
}
void soft(int *myArr, int length) {
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - i - 1; j++) {
if (myArr[j] > myArr[j + 1]) {
// 调用交换数值函数
swap(&myArr[j], &myArr[j + 1]);
}
}
}
}
void printArray(int *myArr, int length) {
for (int i = 0; i < length; i++) {
if (i == 0) {
printf("{%d,", myArr[i]);
}
else if (i == length - 1) {
printf("%d}", myArr[i]);
}
else {
printf("%d,", myArr[i]);
}
}
}
int main() {
int arr[] = { 2,34,5,123,675,23,132,1,4,87 }; // arr 拿到的是数组最开始数字的地址值
// 调用排序函数
soft(arr, sizeof(arr) / sizeof(int));
// 调用打印数组函数
printArray(arr, sizeof(arr) / sizeof(int));
printf("\n%p\n", arr); // arr &arr &arr[0] 存的是数组第0个数字的指针地址
printf("%p\n", &arr);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[1]); // &arr[1] 存的是数组第1个数字的指针地址
}
int a[5];
int *p;
p = &a[0];
指针变量 p 保存了数组 a 中第 0 个元素的地址,即 a[0]的地址
通过指针变量运算加取值的方法来引用数组的元素
#include
void swap(int* num1) {
int temp = *num1;
*num1 = *(num1 + 1);
*(num1 + 1) = temp;
}
void soft(int* myArr, int length) {
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - i - 1; j++) {
if (myArr[j] > myArr[j + 1]) {
// 调用交换数值函数
swap(&myArr[j]);
}
}
}
}
void printArray(int* myArr, int length) {
for (int i = 0; i < length; i++) {
if (i == 0) {
printf("{%d,", myArr[i]);
}
else if (i == length - 1) {
printf("%d}", myArr[i]);
}
else {
printf("%d,", myArr[i]);
}
}
}
int main() {
//int arr[] = { 11,22,33 };
//int* p = arr;
//printf("%p\n", arr);
//printf("%p\n", &arr);
//printf("%p\n", &arr + 1); // 跳过整个数组的长度
//printf("%p\n", arr + 1);
//printf("%p\n", p + 1);
//printf("%d\n", *(p + 1));
int arr[] = { 2,34,5,123,675,23,132,1,4,87 }; // arr 拿到的是数组最开始的地址值
// 调用排序函数
soft(arr, sizeof(arr) / sizeof(int));
// 调用打印数组函数
printArray(arr, sizeof(arr) / sizeof(int));
}
按指针指向的数据的类型来分
char *p;//定义了一个字符指针变量,只能存放字符型数据的地址编号
short *p;//定义了一个短整型的指针变量 p,只能存放短整型变量的地址
int *p;//定义了一个整型的指针变量 P,只能存放整型变量的地址
1ong *p;//定义了一个长整型的指针变量 P,只能存放长整型变量的地址
float *p;//定义了一个 float 型的指针变量 p,只能存放 float 型变量的地址
double *p;//定义了一个 double 型的指针变量 p,只能存放 double 型变量的地址
函数指针
结构体指针
指针的指针
数组指针 T
通用指针 void *p;
无论什么类型的指针变量,在 32位系统下,都是4个字节。
指针只能存放对应类型的变量的地址编号。
#include
int main() {
/*int arr[] = { 11,22,33 };
int* p = { 11,22,33 };*/
/*char ch[] = { 'x','x','r' };
char *p = { 'x','x','r' };*/
char ch[] = "xxr";
char* p = ch;
/**(p + 2) = 'x';
printf("%s", ch);*/
char* p1 = "xxr"; // 可读
printf("%c", *(p1 + 1));
char* p1 = "xxr"; // 不可写
printf("%c", *(p1 + 1));
}
文字常量区里的内容是不可被修改的
定义一个数组,数组有若干个相同类型指针变量,这个数组被称为指针数组int *p[5]
指针数组本身就是个数组,是若干个相同类型的指针变量构成的集合
#include
int main() {
char* str1 = "xxr";
char* str2 = "Aclicse";
char* str3 = "gy";
//char* str[3] = { str1,str2,str3 };
char* str[3] = { "xxr","Aclicse","gy" };
/*for (int i = 0; i < sizeof(str) / sizeof(char*); i++) {
printf("%p\n", str[i]);
}*/
for (int i = 0; i < sizeof(str) / sizeof(char*); i++) {
printf("%s\n", str[i]);
}
int arr1[] = { 10,20,30 };
int arr2[] = { 40,50,60 };
int arr3[] = { 1,2,3 };
int* arr[] = { arr1,arr2,arr3 }; // 数组arr本身就是一个指针地址,存放的是数组第0个数字的地址
for (int i = 0; i < sizeof(arr) / sizeof(int*); i++) {
//printf("%d\n", arr[i][0]);
printf("%d\n", *(arr[i] + 1));
}
}
指针的指针,即指针的地址 指针占8个字节
int a = 0x12345678;
假如:a的地址是 0x00000002
int *p = &a;
则 p 中存放的是 a 的地址编号 即0x00000002
假如:p的地址是 0x00000003
int **q = &p; // q 保存了 p 的地址
q 中存放的是 p 的地址编号 即0x00000003
本身是个指针,指向一个数组,加1跳下个数组即指向下个数组,占8个字节
int (*p)[5] 指向数组的类型 (*指针变量名) [指向的数组的元素个数]
假设数组a中的第0个元素的地址为1000,那么每一个一维数组的首地址如下图所示:
#include
int main() {
// 数组指针,至少指向二维数组
int arr[][3] = {
{10,20,30}, // 一班
{40,50,60}, // 二班
{70,80,90} // 三班
};
/* arr 存放的是第一行的地址
*arr 存放的是第一行第一列的地址
**arr 第一行第一列元素arr[0][0]的值
*/
//printf("%d\n", *(*(arr)+1));
/*
int a
a 存放第0个元素的地址
*a 取到第0个元素的值
a+1 第一行的地址
*(a+1) 第一行第一列的地址
*(a+1)+1 第一行第二列的地址
*(*(a+1)+2) 第一行第二列元素a[1][2]的值
*/
int(*p)[3] = arr; // 指向二维数组的指针
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
//printf("%d\n", arr[i][j]);
printf("%d\n", *(*(p+i)+j));
}
}
}
变成 数组指针
#include
int main() {
int arr[] = { 1,2,3,4,5 };
printf("%p\n", arr);
printf("%p\n", &arr);
printf("%p\n", arr+1);
printf("%p\n", &arr+1);
int(*p)[5] = &arr;
printf("%d", (*p)[0]);
//for (int i = 0; i < 5; i++) {
// //printf("%d", arr[i]);
// printf("%d\n", (*p)[i]);
//}
}
arr 和 &arr 所代表的地址编号是一样的,即它们指向同一个存储单元,但是 arr 和 &arr 的指针类型不同
arr 是个 int* 类型的指针,是arr[0]的地址,&arr 变成了数组指针,加1跳一个 10 元素的整型一维数组
int a[5];
int *p = a;
相同点:
a 是数组的名字,也是a[0]的地址,把a赋值给p,即p保存了a[0]的地址,即 a 和 p 都指向 a[0],所以在引用数组元素时,a 和 p 等价引用数组元素。
不同点:
因为 a 是数组名字,所以对a取地址结果是数组的指针
p是指针变量,对p取地址结果是指针的指针
要想改变主调函数中变量的值,必须传变量的地址,而且还得通过*+地址去赋值。无论这个变量是什么类型的。
#include
void func(int *a) {
*a = 200;
}
void func2(char **a) {
*a = "gy";
}
void func3(int *arr) {
*(arr + 1) = 55;
}
void func4(int (*arr)[2]) {
arr[1][1] = 66;
}
void func5(int** arr) {
arr[0][1] = 77;
}
int main() {
int x = 100;
int* p = &x;
func(p);
printf("%d\n", x);
char* str = "xxr";
func2(&str);
printf("%s\n", str);
int arr[3] = { 11,22,33 };
func3(arr);
printf("%d\n", arr[1]);
int arr2[2][2] = {
{11,22},
{33,44}
};
func4(arr2);
printf("%d\n", arr2[1][1]);
int arr3[3] = { 11,22,33 };
int arr4[3] = { 44,55,66 };
int *arr5[] = { arr3,arr4 };
func5(arr5);
printf("%d\n", arr3[1]);
}
#include
typedef int(*xxrerwei)[2]; // 给基础的数据变量定义新名字
int* func1() {
static int a = 100;
int* p = &a;
return p;
}
int* func2() {
static int arr[] = { 11,22,33 };
return arr;
}
//int (*func3())[2] { // 定义数组指针返回值类型名字太复杂了,用 typedef 方法给它定义一个新名字
// static int arr[][2] = {
// {11,22},
// {33,44}
// };
//
// return arr;
//}
xxrerwei func3() {
static int arr[][2] = {
{11,22},
{33,44}
};
return arr;
}
int** func4() {
static int arr1[] = { 11,22 };
static int arr2[] = { 33,44 };
static int* arr3[] = { arr1,arr2 };
return arr3;
}
int main() {
// 返回值是个指针
int* p = func1();
printf("%d\n", *p);
int* p2 = func2();
printf("%d\n", *(p2 + 1));
int(*p3)[2] = func3();
printf("%d\n", *(*(p3 + 1)+1));
int** p4 = func4();
printf("%d\n", p4[1][1]);
}
#include
char* func() {
char* p = "xxr";
return p;
}
int* swap(int num1, int num2) {
int temp = num1; // 定义一个临时变量
num1 = num2;
num2 = temp;
static int arr[2];
arr[0] = num1;
arr[1] = num2;
return arr;
}
void soft(int* myArr, int length) {
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - i - 1; j++) {
if (myArr[j] > myArr[j + 1]) {
// 调用交换数值函数
int *p = swap(myArr[j], myArr[j + 1]);
myArr[j] = p[0];
myArr[j + 1] = p[1];
}
}
}
}
void printArray(int* myArr, int length) {
for (int i = 0; i < length; i++) {
if (i == 0) {
printf("{%d,", myArr[i]);
}
else if (i == length - 1) {
printf("%d}", myArr[i]);
}
else {
printf("%d,", myArr[i]);
}
}
}
int main() {
char* p = func();
printf("%s\n", p);
int arr[] = { 2,34,5,123,675,23,132,1,4,87 }; // arr 拿到的是数组最开始的地址值
// 调用排序函数
soft(arr, sizeof(arr) / sizeof(int));
// 调用打印数组函数
printArray(arr, sizeof(arr) / sizeof(int));
}
我们定义的函数,在运行程序的时候,会将函数的指令加载到内存的代码段,所以函数也有起始地址。
C语言规定:函数的名字就是函数首地址,即函数的入口地址,就可以定义一个指针变量,来存放函数的地址,这个指针变量就是函数指针变量。
定义:
int max(int x,int y) {
...
}
int (*p)(int,int); // 定义了一个函数指针变量 p,p 指向的函数
p = max;
调用:
(*p)(30,50);
p(30,50);
#include
int max(int a, int b) {
if (a > b) {
return a;
}
else {
return b;
}
}
int min(int a, int b) {
if (a < b) {
return a;
}
else {
return b;
}
}
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int main() {
int (*p[])(int, int) = { add,sub,max,min };
int res1 = (*p)(1, 2);
printf("%d\n", res1);
int res2 = (p[1])(1, 2);
printf("%d\n", res2);
int res3 = (*(p+2))(11, 22);
printf("%d\n", res3);
int res4 = (*(p + 3))(11, 22);
printf("%d\n", res4);
}
#include
void myShow(int* video);
// 库函数
void gstreamerParVideo(int *originVideoData,
void (*p)(int *videoData)) {
//处理数据
for (int i = 0; i < 3; i++) {
originVideoData[i] += 100;
}
// 回调函数传入解析后的数据
p(originVideoData);
}
//自己封装的函数
void parseVideo(int *video) {
//引入gstreamerPar库
gstreamerParVideo(video, myShow);
}
//自己封装一个回调函数
void myShow(int * videoData) {
for (int i = 0; i < 3; i++) {
printf("%d\n", videoData[i]);
}
}
int main() {
// 调用 gstreamerParVideo 库
int arr[] = { 11,22,33 };
parseVideo(arr);
}
空类型指针 (void*)
void* 通用指针,任何类型的指针地址都可以给 void*类型的指针变量赋值
空指针 NULL
char *p = NULL;
p 哪里都不指向,也可以认为 p 指向内存为 0 的存储单位。
#include
// 宏定义
#define XXR_INT 1
#define XXR_FLOAT 2
void swap(void* a, void* b,int type) {
if (type == XXR_INT) {
int temp = *(int *)a;
*(int*)a = *(int*)b;
*(int*)b = temp;
}
else if (type == XXR_FLOAT) {
float temp = *(float*)a;
*(float*)a = *(float*)b;
*(float*)b = temp;
}
}
int main() {
// 空指针
char* p = NULL;
printf("%p\n", p);
// 空类型指针
int num = 100;
void* vp = # // 只能保存指针的值,不能记录指针指向的类型
printf("%d\n", *(int *)vp);
int a = 200;
int b = 300;
float c = 1.1f;
float d = 2.2f;
swap(&a, &b, XXR_INT);
swap(&c, &d, XXR_FLOAT);
printf("%d\n", a);
printf("%d\n", b);
printf("%f\n", c);
printf("%f\n", d);
}
C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态的分配内存空间,也可把不再使用的空间回收再次利用。
静态分配
动态分配
void * malloc(int size)
在内存的动态存储区(堆区)中分配一块长度为 size 字节的连续区域,用来存放类型说明字符指定的类型。函数原型返回 void*指针,使用时必须做相应的强制类型转换。
返回值:
注意:
free 函数释放 p 指向的内存
char *p = (char *)malloc(100);
free(p);
在内存中,申请 n 块,每块的大小为 size 个字节的连续区域
函数的返回值:
注意:malloc 和 calloc 函数都是用来申请内存的。
区别:
在原先s指向的内存基础上重新申请内存,新的内存的大小为 new_size 个字节,如果原先内存后面有足够大的空间,就追加,如果后边的内存不够用,则relloc 函数会在堆区找一个 newsize 个字节大小的内存申请,将原先内存中的内容拷贝过来,然后释放原先的内存,最后返回 新内存的地址。
申请的内存,首地址丢了,找不了,再也没法使用了,也没法释放了,这块内存就被泄露。
int main() {
char *p;
p = (char *)malloc(100);
// 接下来,可以用 p 指向的内存了
p = "xxr"; // p 指向别的地方了
// 从此以后,再也找不到你申请的 100 个字节,则动态申请的 100 个字节就被泄露了
}
void func() {
char *p;
p = (char *)malloc(100);
}
int main() {
func();
func();
}
// 每一次调用 fun 函数 泄露 100 个字节
拷贝 scr 指向的字符串到 dest 指针指向的内存中,'\0'也会拷贝
#include
#include
#include
int main() {
char* p = (char*)malloc(100);
char* str = "xxr";
if (!p) return;
/*for (int i = 0; i < 4; i++) {
p[i] = str[i];
}*/
/*printf("%s", p);*/
// strcpy_s(p, 7, str);
strncpy_s(p, 7, str, 2);
printf("%s", p);
free(p);
}
测字符指针 s 指向的字符串中字符的个数,不包括'\0'
#include
#include
#include
int main() {
char p1[] = "xxr";
char* p2 = "xxr";
printf("%lld\n", sizeof(p1) / sizeof(char));
printf("%lld\n", sizeof(p2));
printf("%zd\n", strlen(p1));
printf("%zd\n", strlen(p2));
char* p = (char*)malloc(100);
if (!p) return;
strcpy_s(p, strlen(p2) + 1, p2);
printf("%s", p);
free(p);
}
strcat 函数追加 src 字符串到 dest 指向的字符串的后面,追加的时候会追加'\0'
#include
#include
#include
int main() {
/*char* p = (char*)malloc(120);
if (!p) return;
scanf_s("%s", p, 100);
char* str = "先生/女士";
strcat_s(p, 120, str);
printf("%s\n", p);*/
char p[100] = { 'x','x','r' };
//char* p = "xxr"; // 字符串常量不能被修改
char* str = "先生/女士";
//strcat_s(p, 100, str);
strncat_s(p, 100, str, 4);
printf("%s", p);
}
比较 s1 和 s2 指向的字符串的大小,比较方法:逐个字符去比较 ASCII 码,一旦比较出大小返回。如果所有的字符都一样,则返回0。
#include
#include
#include
int main() {
char* a = "abc";
char* b = "acd";
char* c = "aaa";
char* d = "abd";
//int res = strcmp(a, d);
int res = strncmp(a, d, 3);
printf("%d\n", res);
char* p1 = (char*)malloc(100);
char* p2 = (char*)malloc(100);
if (!p1) return;
if (!p2) return;
printf("请输入密码\n");
scanf_s("%s", p1, 100);
printf("请再次输入密码\n");
scanf_s("%s", p2, 100);
if (strcmp(p1, p2) == 0) {
printf("密码正确\n");
}
else {
printf("两次输入的密码不匹配\n");
}
}
在字符指针 s 指向的字符串中,找 ASCII 码为 c 的字符 注意,是首次匹配,如果 s 指向的字符串中有多个 ASCII 为 c 的字符,则找的是第1个字符
#include
#include
int main() {
//char* str = "xxr";
char* res = strchr(str, 'k');
//char* res = strrchr(str, 'x'); // 末次匹配
//if (res == NULL) {
// printf("不在");
// return;
//}
//printf("%p\n", str);
//printf("%p\n", res);
//printf("%d\n", (int)(res-str));
// 模糊查询
char* str[] = { "xxr", "xp", "gy"};
printf("请输入你要查询的字母\n");
char search = 0;
scanf_s("%c", &search, 1);
for (int i = 0; i < sizeof(str) / sizeof(char*); i++) {
if (strchr(str[i], search) != NULL) {
printf("%s\n", str[i]);
}
}
}
char *strstr(const char *a, const char *b);
在 a 指向的字符串中查找 b 指向的字符串,也是首次匹配
#include
#include
int main() {
// 模糊查询
char* str[] = { "xxr", "xp", "gy", "xxx"};
printf("请输入你要查询的字母串\n");
char search[100];
scanf_s("%s", &search, 100);
// search 拼接 \0
strcat_s(search, 100, "");
for (int i = 0; i < sizeof(str) / sizeof(char*); i++) {
if (strstr(str[i], search) != NULL) {
printf("%s\n", str[i]);
}
}
}
atoi/atol/atof 字符转换功能
函数声明:int atoi(const char *nptr);
int num;
num = atoi("12岁");
则 num 的值为 12
函数声明:char *strtok(char *str, const char *delim);
字符串切割,按照 delim 指向的字符串中的字符,切割 str 指向的字符串。其实就是在 str 指向的字符串中发现了delim 字符串中的字符,就将其变成10, 调用一次 strtok 只切割一次,切割一次之后,再去切割的时候 strtok的第一个参数传 NULL,意思是接着上次切割的位置继续切
#include
#include
#include
void split(char* str, char** splitStr, char* p) {
char* buf = NULL;
splitStr[0] = strtok_s(str, p, &buf);
//printf("%s\n", splitStr[0]);
int i = 0;
while (splitStr[i]) {
i++;
splitStr[i] = strtok_s(NULL, p, &buf);
}
//printf("%s\n", splitStr[3]);
}
int main() {
// 录入地址
char str[100] = "广东|广州|天河区";
char* splitStr[100];
//printf("%s\n", str);
split(str, splitStr, "|");
int m = 0;
while (splitStr[m]) {
printf("%s\n", splitStr[m]);
m++;
}
}
函数声明:void* memeset(void *ptr,int value,size_t num);
memset 函数是将 ptr 指向的内存空间的 num 个字节全部赋值为 value
#include
#include
#include
int main() {
//char* p = (char*)calloc(25,4);
char* p = (char*)malloc(100);
char p1[] = "xxr";
if (!p) return;
memset(p, 100, 100);
/*for (int i = 0; i < 100; i++) {
printf("%d\n", p[i]);
}*/
memset(p1, 0, 4);
/*for (int i = 0; i < 4; i++) {
printf("%d\n", p1[i]);
}*/
int* ip = (int*)malloc(100);
if (!ip) return;
memset(ip, -1, 100); // int数组 只能改成 0 和 -1
for (int i = 0; i < 100/sizeof(int); i++) {
printf("%d\n", ip[i]);
}
}
在程序开发时,有些时候需要将不同类型的数据组合成一个有机的整体
struck {
char name[100];
int score;
int age;
}
结构体是一种构造类型的数据结构,是一种或多种基本类型或构造类型的数据整合
结构体变量,是若干个相同或不同数据结构构成的集合:
#include
#include
typedef struct ADDRESS {
char* province;
char* city;
char* district;
} ADDR;
typedef struct student {
//char name[100];
char* name;
int age;
//char* address[3];
ADDR address;
} STU;
int main() {
STU student0 = {
"谢小然",
18,
{
"广东",
"广州",
"天河"
}
};
STU student1 = {
"xiaoming"
};
//student1.name = "zhangsan"; // name[100] 是字符数组,存储的是常量无法被修改
//strcpy_s(student1.name, 100, "zhangsan");
student1.name = "zhangsan";
student1.age = 100;
//student1.address[1] = "深圳";
student1.address.city = "深圳";
//printf("%s,%d", student0.name, student0.age);
printf("%s,%d", student1.name, student1.age);
printf("%s", student1.address.city);
}
结构体数组是个数组,邮若干个相同类型的结构体变量构成的集合
struct 结构体类型名 数组名 [元素个数];
struct student stu[3];
结构体指针:即结构体的地址,结构体变量存放内存中,也有起始地址。
在64位系统环境下,结构体指针占8个字节。
语法:struct 结构体类型名 *结构体指针变量名
#include
struct student {
char* name;
int age;
};
int main() {
/*struct student stu = {"谢小然", 18};
struct student *sp = &stu;
printf("%s\n", (*sp).name);
printf("%s\n", sp->name);
printf("%d\n", sp->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);
}
给函数传结构体变量的地址
#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 < num; i++) {
sum += (p + 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 < num; i++) {
input(stu+i);
}
float sum = 0;
sum = sumfunc(stu, num);
printf("平均成绩%f\n", sum / num);
free(stu);
}
结构体变量大小是所有成员大小之和。
规则1
以多少个字节位单位开辟内存给结构体变量分配内存时,会去结构体变量中找基本类型的成员。
哪个基本类型的成员占字节数多,就以它的大小开辟内存。
规则2
字节对齐的好处
用空间来换时间,提高 cpu 读取数据的效率
struct stu {
char a;
int b;
};
#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);
}
共用体和结构体类似,也是一种构造类型的数据结构。
几个不同的变量共同占用一段内存的结构,在C语言中,被称为“共用体”类型结构
共用体所有成员占有一段地址空间,共用体的大小是其占用内存长度最大的 成员的大小
typedef struct {
char name[100];
int score;
}stu;
typedef struct {
char name[100];
int salary;
}tea;
typedef union {
stu student;
tea teacher;
}any;
共用体的特点:
将变量的值一一列举出来,变量的值只限于列举出来的值的范围内
语法:enum 枚举类型名 {枚举值列表};
在枚举值表中应列出所有可用值,也称为枚举元素;
枚举元素是常量,编号默认是从 0 开始的
enum TYPE {STU = 1, TEA};
enum TYPE type;
if (type == STU) {
// ...
}
注意:
正数在内存中以原码形式存放,负数在内存中以补码形式存放
正数的原码 = 反码 = 补码
原码:将一个整数,转换成二进制,就是其原码。(如字节的 5 的原码为:0000 0101;则 -5 的原码为 1000 0101。)
反码:正数的反码就是其原码;负数的反码是将原码中,除符号位以外,每一位取反。(如单字节的 5 的反码为:0000 0101;-5 的反码为 1111 1010。)
补码:正数的补码就是其原码;负数的反码 +1 就是补码。(如单字节的 5 的补码为:0000 0101; -5 的补码为 1111 1011。)
无论是正数还是负数,编译系统都是按照内存中存储的内容进行位运算。
& 按位 与
任何值与 0 得 0,与 1 保持不变
| 按位 或
任何值或 1 得 1,或 0 保持不变
~ 按位取反
1 变 0,0 变 1
^ 按位异或
相异得 1,相同得 0
位移
>> 右移 << 左移
预编译
将.c 中的头文件展开,宏展开生成的文件是 .i 文件
编译
将预处理之后的 .i 文件生成 .s 汇编文件
汇编
将 .s 汇编文件生成 .o 目标文件
链接
将 .o 文件链接成目标文件
执行
定义宏用 define 去定义,宏是在预编译时进行替换。
不带参数
#define PI 3.1415
在预编译时如果代码出现了 PI 就用 3.1415 去替换
带参数
#define Max(a,b) (a>b?a:b)
将来在预处理时,替换成实参代替字符串的形参,其他字符保留
带参宏和带参函数的区别
#ifdef XXR
代码段一
#else
代码段二
#endif
#ifndef XXR
代码段一
#else
代码段二
#endif
#if XXR == 1
代码段一
#elif XXR == 2
代码段二
#else
代码段三
#endif
注意和 if else 语句的区别
文件用来存放程序、文档、音频、视频数据、图片数据。
文件就是存放在磁盘上的一些数据集合。
磁盘文件:指一组相关数据的有序集合,通常存储在外介质(如磁盘上),使用时才调入内存。
设别文件:在操作系统中把每一个与主机相连的输入、输出设备看作是一个文件,把它们的输入、输出等同于对磁盘文件的读和写。
键盘:标准输入文件
屏幕:标准输出文件
其他设备:打印机、触摸屏、摄像头、音箱等
文件缓冲区是库函数申请的一段内存,由库函数对其进行操作,程序员没有必要知道存放在哪里,只需要知道对文件操作时的一些缓冲特点即可。
一个文件通常是磁盘上一段命名的存储区,计算机的存储在物理上是二进制的,所以物理上所以的磁盘文件本质上都是一样的,以字节为单位进行顺序存储。
从用户或者操作系统使用的角度
把文件分为:
文本文件、二进制文件对比
译码:
文本文件编码基于字符定长,译码容易些;
二进制文件编码是变长的,译码难一些(不同的二进制文件格式,有不同的译码方式,一般需要特定软件进行译码)。
空间利用率
二进制文件用一个比特来代表一个意思(位操作);
而文本文件任何一个意思至少是一个字符。
所以二进制文件,空间利用率高。
可读性:
文本文件用通用的记事本工具就几乎可以浏览所有文本文件
二进制文件需要一个具体的文件解码器
文件指针在程序中用来标识(代表)一个文件的,在打开文件的时候得到文件指针,文件指针就用来代表咱们打开的文件。
FILE*指针变量标识符;
typedef struct _iobuf {
int cnt; // 剩余的字符,如果输入缓冲区,那么就表示缓冲区还有多少个字符未被读取
char *ptr; // 下一个要被读取的字符的地址
char *base; // 缓冲区基地址
int flag; // 读写状态标志位
int fd; // 文件描述符
}FILE;
在缓冲文件系统中,每个被使用的文件都要在内存中开辟一块 FILE 类型的区域,存放与操作文件相关的信息
对文件操作的步骤:
FILE *fopen(const char *path, const char *mode);
函数的参数:
模式 | 功能 |
---|---|
r 或 rb | 以只读方式打开一个文本文件(不创建文件) |
w 或 wb | 以写方式打开文件(使文件长度截断为0字节创建一个文件) |
a 或 ab | 以追加方式打开文件,即在末尾添加内容,当文件不存在时,创建文件用于写 |
r+ 或 rb+ | 以可读、可写的方式打开文件(不创建新文件) |
w 或 wb+ | 以可读、可写的方式打开文件(使文件长度为0字节,创建一个文件) |
a 或 ab+ | 以追加方式打开文件,打开文件并在末尾更改文件(如果文件不存在,则创建文件) |
返回值:
int fclose(FILE *fp);
关闭 fp 所代表的文件
返回值:
int fgetc(FILE *stream);
fgetc 从 stream 所标识的文件中读取一个字节,将字节值返回
**返回值:**读到文件结尾返回 EOF
EOF 是在 stdio.h 文件中定义的符号常量,值为-1
int fputc(int C, FILE *stream)
fputc将c的值写到 stream 所代表的文件中。
返回值:
如果输出成功,则返回输出的字节值;
如果输出失败,则返回一个 EOF。
#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);
}
char *fgets(char *s, int size, FILE *stream);
从 stream 所代表的文件中读取字符,在读取的时候碰到换行符或者是碰到文件的末尾停止读取,或者是读取了
size-1个字节停止读取,在读取的内容后面会加一个10, 作为字符串的结尾
int fputs(const char *s, FILE *stream);
将s指向的字符串,写到 stream 所代表的文件中
#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);
}
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread 函数从 stream 所标识的文件中读取数据,每块是 size个字节,共nmemb块,存放到ptr指向的内存里返回值:实际读到的块数。
size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream);
fwrite 函数将 ptr 指向的内存里的数据,向stream 所标识的文件中写入数据,每块是size 个字节,共nmemb 块。
rewind 复位读写位置
void rewind(文件指针);
把文件内部的位置指针移到文件首
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);
}
}
测文件读写位置距文件开始有多少个字节
long ftell(文件指针);
返回值:返回当前读写位置(距离文件起始的字节数),出错时返回-1.
1ong int length;
length = fte11(fp)