简单的 C 语言程序和算法
什么是计算机程序
所谓程序,就是一组计算机能识别和执行的「指令」。
程序: 一堆代码。
eg: 学生成绩统计程序。
程序≠软件。
软件是程序、数据及相关文档的集合,是程序“打包”后的产物。可以理解为:程序是“原材料”,软件是“成品”。
![[软件与程序的关系]]
什么是计算机语言
计算机语言是人与计算机交流的“桥梁”。
- 机器语言(低级语言)
由0与1组成的二进制指令,计算机能直接执行。 - 汇编语言(低级语言)
用助记符(如MOV, ADD)表示机器指令,是机器语言的符号化。 - 高级语言
更接近人类自然语言和数学语言。- 非结构化语言
- 结构化语言(如:C语言)
- 面向对象语言(如: Java, C++)
C 语言的特点
- 语言简洁、紧凑、灵活:仅有32个关键字。
- 运算符和数据类型丰富:能高效处理各种复杂运算。
- 程序设计结构化、模块化:
结构化分为 「顺序结构」「选择结构」「循环结构」。这三大结构是构建所有复杂程序的基石。 - 生成目标代码质量高:程序执行效率高。
- 可移植性好:稍加修改即可在不同类型的计算机上运行。
C 的 32个关键字
这些是系统定义的保留字,不能用作其他定义(如变量名)。
eg: 比如肯德基商标被注册了,你就不能再开一个叫肯德基的店了。
| auto | break | case | char | const |
|---|---|---|---|---|
| continue | default | do | double | else |
| enum | extern | float | for | goto |
| if | int | long | register | return |
| short | signed | sizeof | static | struct |
| switch | typedef | unsigned | union | void |
| volatile | while |
关键字的共同特点:全都是小写字母。
标识符
^97be1e
标识符是为变量、函数等实体起的名字。
- 用户标识符规则: ^764b2c
- 由字母、数字、下划线(
_)组成。 - 不能以数字开头。
- 区分大小写(例如
sum和Sum是两个不同的标识符)。 - 不能与关键字相同。
- 由字母、数字、下划线(
- 命名建议:尽量做到“见名知意”,如用
student_count而非a。
最简单的 C语言程序
#include <stdio.h> // 编译预处理指令:引入标准输入输出头文件
int main() // 主函数,每个C程序有且仅有一个main函数,程序从这里开始执行
{
printf("Hello World\n"); // 语句:调用printf函数在屏幕上输出“Hello World”并换行
return 0; // 语句:主函数结束,并向操作系统返回0,表示程序正常退出
}
C 语言程序的结构
结构特点:
- 函数与主函数
- 程序由一个或多个函数组成。
- 必须有且只能有一个主函数
main()。 - 程序执行从
main开始,在main中结束,可以调用其他函数。
- 程序语句
- C程序由语句组成,完成特定操作。
- 用分号
;作为语句终止符。
- 注释
// 单行注释或/* 多行注释 */,用于解释代码,编译器会忽略。
格式特点:
- 习惯用小写字母,大小写敏感。
- 不使用行号,无程序行概念。
- 可使用空行和空格增强可读性。
- 常用锯齿形(缩进)书写格式,使结构清晰。

运行 C程序的步骤和方法
编写C程序到运行结果,一般需要四个步骤,可以用下图表示:
![[运行 C语言的步骤和方法]]
- 编辑:用文本编辑器编写源代码,保存为
.c文件。 - 编译:用编译器将源代码翻译成机器语言,生成
.obj目标文件。此阶段检查语法错误。 - 链接:将目标文件与系统库文件等连接起来,生成
.exe可执行文件。 - 运行:执行
.exe文件,得到运行结果。
算法
一个程序应该包括两个方面的内容:
- 对数据组织的描述:数据结构。例如顺序存储(数组)和链式存储(链表)。
- 对操作步骤的描述:算法。例如穷举、迭代、递归,以及查找、删除、排序等具体操作。
著名公式:数据结构 + 算法 = 程序
算法,就是为了解决一个问题而采取的确切、有限的方法和步骤。
算法举例:求 1 + 2 + 3 + ... + 100 的和
问题分析:这是一个累加问题,从1加到100。
算法设计(描述):
- 设一个变量
sum存放和,初始值为0;设一个变量i代表当前要加的数,初始值为1。 - 如果
i的值小于或等于100,则执行第3步;否则,转到第5步。 - 将
i的值加到sum中,即sum = sum + i。 - 让
i的值增加1,然后返回第2步。 - 输出
sum的值,它就是最终结果。
思考:这个算法用到了“循环”的思想。你可以尝试设计一个直接套用公式 (1+100)*100/2 的算法,比较两者的不同。
#include <stdio.h> // 包含标准输入输出头文件,以便使用printf函数
int main() {
// 步骤1: 声明并初始化变量
int sum = 0; // 变量sum用来存放和,初始值为0
int i = 1; // 变量i代表当前要加的数,初始值为1
// 步骤2-4: 使用while循环实现累加过程
while (i <= 100) { // 步骤2:判断 i 是否小于或等于100
sum = sum + i; // 步骤3:将 i 的值加到 sum 中
i = i + 1; // 步骤4:让 i 的值增加1,准备下一次循环
} // 循环会一直执行,直到 i > 100,然后跳转到第5步
// 步骤5: 输出结果
printf("1到100的和是:%d\n", sum); // 输出sum的值
return 0; // 程序正常结束
}
代码解析
- 初始化:
int sum = 0;和int i = 1;对应算法步骤1。 - 循环条件:
while (i <= 100)对应步骤2的“如果i的值小于或等于100,则执行...”。 - 循环体:
sum = sum + i;对应步骤3的“将i的值加到sum中”。i = i + 1;对应步骤4的“让i的值增加1”。
- 循环流程:执行完一次循环体后,程序会回到
while行再次检查条件(i <= 100),这对应步骤4的“然后返回第2步”。 - 输出结果:当
i变为101时,不满足i <= 100,循环结束。程序执行到printf语句,输出最终结果。
算法的特性
- 有穷性:算法必须包含有限的操作步骤,不会无限循环。
- 确定性:算法中的每一步骤都应当是明确的,没有歧义。
- 有零个或多个输入:执行算法时可能需要从外界获取数据,但有些算法(如生成随机数)无需输入。
- 有一个或多个输出:算法必须产生结果,没有输出的算法没有意义。
- 有效性:算法中的每一步都必须是计算机可以执行的基本操作,并能得到确定的结果。
基本的数据类型
如何学好 C 语言:
- 理解与记忆:理解核心概念,记忆基本规则(如语法、关键字)。
- 实践与练习:多读、多写、多调试代码。
C语言数据类型总览
C语言的数据类型系统非常丰富,下图展示了其完整的分类:

基本数据类型
我们首先学习最常用、最基本的几种数据类型,它们是构建更复杂数据结构的基石。

常量
定义:在程序运行过程中,其值不能被改变的量。
一、符号常量
用标识符(一个名字)来代表一个常量。
- 定义格式:
#define 符号常量 常量值- 这是编译预处理指令,不是C语句,因此末尾没有分号。
- 在编译前,预处理器会将程序中所有出现该符号常量的地方,直接替换成对应的常量值。
- 习惯:符号常量名通常用大写字母表示,以便与变量区分。
- 优点:
- 含义清晰:用一个有意义的名称(如
PI)代替晦涩的数字(如3.14159),提高程序可读性。 - 一改全改:当常量的值需要修改时(例如税率调整),只需修改
#define这一行即可,避免在代码中四处查找和修改,减少错误。
- 含义清晰:用一个有意义的名称(如
例题:符号常量的使用
#include <stdio.h>
#define PRICE 30 // 定义符号常量PRICE,代表单价30元
int main() {
int total;
total = PRICE * 5; // 编译前,此行会被替换为 total = 30 * 5;
printf("总价格为:%d元\n", total);
// PRICE = 40; // 错误!符号常量不能被重新赋值。
return 0;
}
解析:#define PRICE 30定义后,PRICE就固定代表30。程序计算total = PRICE * 5时,实际执行的是total = 30 * 5。试图给PRICE赋值是语法错误。
二、直接常量(字面常量)
直接写在代码中的常量值,根据其形式分为以下几类:
1. 整型常量
即整常数,可以用三种进制表示:
- 十进制:最常用,由数字0~9和正负号组成。如
123,-456,0。 - 八进制:以数字
0开头,后跟0~7的数字。如0123(十进制为1*8^2 + 2*8^1 + 3*8^0 = 83)。 - 十六进制:以
0x或0X开头,后跟09,AF(或a~f)的数字。如0x1A(十进制为1*16^1 + 10*16^0 = 26)。
例题:不同进制的整型常量
#include <stdio.h>
int main() {
printf("十进制 100: %d\n", 100);
printf("八进制 0100 的十进制值: %d\n", 0100); // 输出64
printf("十六进制 0x100 的十进制值: %d\n", 0x100); // 输出256
return 0;
}
2. 实型常量(浮点型常量)
即带小数点的实数,有两种表示形式:
- 十进制小数形式:必须包含小数点。例如
3.14,0.5(可写为.5),100.(可写为100.0)。 - 指数形式:用于表示很大或很小的数,格式为
a E n或a e n,表示 a × 10^n。- 规则:
E(或e) 前后必须有数字,且指数n必须为整数。 - 例如:
3.14E-2表示 3.14×10^{-2} (即0.0314),-5.6e8表示 -5.6×10^8。
- 规则:
例题:实型常量的表示
#include <stdio.h>
int main() {
printf("小数形式: %f\n", 123.456);
printf("指数形式(大数): %f\n", 1.23456e2); // 等价于 1.23456 * 10^2 = 123.456
printf("指数形式(小数): %f\n", 1.23456E-2); // 等价于 1.23456 * 10^{-2} = 0.0123456
// printf("%f\n”, 1.2E); // 错误!E后面没有数字。
// printf("%f\n”, E5); // 错误!E前面没有数字。
return 0;
}
3. 字符常量
用单引号括起来的一个字符。
- 普通字符:如
'A','2','+'。 - 转义字符:以反斜杠
\开头,后跟特定字符,表示具有特殊功能的字符或难以直接输入的字符。

常用转义字符:\n: 换行\t: 水平制表符(Tab)\\: 表示一个反斜杠本身\': 表示一个单引号\": 表示一个双引号\0: 空字符(NULL),ASCII码为0,常用于字符串结束标志。
例题:字符常量与转义字符
#include <stdio.h>
int main() {
printf("字符常量: %c\n", 'A'); // 输出字符 A
printf("转义字符-换行: Hello\nWorld\n"); // \n使输出换行
printf("转义字符-制表: Name\tAge\nJohn\t20\n"); // \t产生对齐效果
printf("输出单引号本身: \' \n"); // 输出一个 '
printf("输出反斜杠本身: \\ \n"); // 输出一个 \
return 0;
}
4. 字符串常量
用双引号括起来的一串字符序列。
- 例如:
"Hello, World!","A","123"。 - 存储特点:系统会在字符串的末尾自动添加一个空字符
'\0'作为字符串的结束标志。因此,字符串"A"在内存中实际占用2个字节:'A'和'\0';而字符常量'A'只占1个字节。 - 与字符常量的区别:
"A"是字符串常量,'A'是字符常量,两者类型不同,占用内存大小也不同。
例题:字符串常量与结束标志
#include <stdio.h>
#include <string.h> // 引入字符串处理函数头文件
int main() {
printf("字符串: %s\n", "Hello");
// 演示字符串的结束标志 '\0'
char str1[] = "ABC"; // 数组 str1 实际存储为:'A', 'B', 'C', '\0'
char str2[] = {'A', 'B', 'C', '\0'}; // 手动添加结束标志,与str1等价
char str3[] = {'A', 'B', 'C'}; // 没有结束标志,不是规范的C字符串
printf("str1长度(strlen): %zu\n", strlen(str1)); // 输出3,strlen函数遇到'\0'停止计数
printf("str2长度(strlen): %zu\n", strlen(str2)); // 输出3
// printf("str3长度(strlen): %zu\n”, strlen(str3)); // 危险!str3没有'\0',strlen会一直向后读取直到遇到内存中的'\0',结果不可预知。
return 0;
}
解析:这个例题清晰地展示了字符串的存储方式及其与字符数组的区别。理解 '\0' 作为结束标志是掌握C语言字符串操作的关键。
变量
在程序运行期间,值可以发生变化的量。
基本操作:
- 赋值:将一个值存储到变量中。
- 取值:从变量中读取存储的值。
变量命名: 遵守C 语言[[#^764b2c]]标识符命名规则。
使用原则:先定义,后使用。编译器需要知道变量的数据类型,才能为其分配正确的内存空间。
变量的定义与初始化
变量定义语句的一般形式为:
类型标识符 变量名1, 变量名2, ...;
例如:
int a, b; // 定义两个整型变量 a 和 b
float f; // 定义一个单精度浮点型变量 f
double x; // 定义一个双精度浮点型变量 x
char ch; // 定义一个字符型变量 ch
在定义变量的同时可以为其赋初值,这称为初始化:
int sum = 0; // 定义整型变量 sum 并初始化为 0
float e = 2.717; // 定义浮点变量 e 并初始化为 2.717
char ch = 'a'; // 定义字符变量 ch 并初始化为字母 'a'

数据类型转换
在 C 语言中,不同类型的数据进行运算时,编译器会自动或按程序员指示进行类型转换。
1. 自动类型转换(隐式转换)
当不同类型的变量混合运算时,编译器会遵循一套规则自动进行类型提升,以保证精度不丢失。基本规则是:向数据范围更广、精度更高的类型转换。
常见的自动转换方向:
char / short -> int -> unsigned int -> long -> unsigned long -> float -> double
示例:
int i = 10;
float f = 3.14;
double d;
d = i + f; // i 自动转换为 float 与 f 相加,结果再自动转换为 double 赋给 d
// 过程:int(10) -> float(10.0) -> 10.0 + 3.14 = 13.14 -> double(13.14)
在赋值运算中,等号右边的值会自动转换为左边变量的类型。
int num;
float pi = 3.14159;
num = pi; // pi 的值被自动转换为 int 类型,小数部分被截断。num 最终为 3。
2. 强制类型转换(显式转换)
程序员可以使用强制类型转换符,明确指示编译器将一种类型转换为另一种类型。
形式:
(目标类型名) 表达式
示例:
(double) 2 // 将整数 2 强制转换为 double 类型,得到 2.0
(int) 2.2 // 将浮点数 2.2 强制转换为 int 类型,小数部分被丢弃,得到 2
float f = 3.6;
int a = (int)f; // a 的值为 3
int x = 10, y = 4;
float result;
result = (float)x / y; // 将 x 强制转为 float,使除法变为浮点除法,result = 2.5
// 如果不强制转换:result = x / y; 则为整数除法,结果为 2.0
运算符和表达式
算术运算符和算术表达式
基本的算术运算符:+(加) -(减) *(乘) /(除) %(求余,模运算)。
- 结合方向: 从左到右。
- 优先级:
*,/,%>+,-。
重要细节:
- 除法
/:- 如果
/两边的操作数都是整数,则执行整数除法,结果舍弃小数部分,只保留整数。 - 如果
/两边的操作数至少有一个是浮点数,则执行浮点数除法,结果为浮点数。
5 / 2 // 结果为 2 (整数除法) 5.0 / 2 // 结果为 2.5 (浮点数除法) - 如果
- 求余
%:- 求余运算符要求两端的操作数必须都是整数。
- 结果的符号与被除数 (
a % b中的a) 的符号相同。
7 % 3 // 结果为 1 -7 % 3 // 结果为 -1 7 % -3 // 结果为 1 (在大多数编译器中) // 7.5 % 3 // 错误!不能对浮点数使用 % 运算符

赋值运算符和表达式
- 基本赋值运算符:
= - 赋值表达式形式:
变量 = 表达式- 例如:
x = 5;a = 3 + 2;
- 例如:
- 优先级: 非常低,仅高于逗号运算符。
- 结合性: 自右向左。这使得链式赋值成为可能。
a = b = c = 5; // 等价于 c = 5; b = c; a = b; - 说明: 赋值表达式尾部加上分号,即构成赋值语句。
y = x + 10; // 这是一个赋值语句 ; // 单独一个分号是空语句
复合赋值运算符
包含:+=, -=, *=, /=, %=。
- 优先级与结合性: 与基本赋值运算符
=相同。 - 作用: 简化“运算后赋值”的操作。
a += 2; // 完全等效于 a = a + 2;
a -= 2; // 完全等效于 a = a - 2;
a *= 2; // 完全等效于 a = a * 2;
a /= 2; // 完全等效于 a = a / 2;
a %= 2; // 完全等效于 a = a % 2;
// 注意右边是一个整体表达式
b %= b + 2; // 等效于 b = b % (b + 2); 而不是 b = b % b + 2;
自增自减运算符
运算符:自增 (++)、自减 (--)。
作用:使单个变量的值增 1 或减 1。
用法和运算规则:
- 前置运算:
++i,--i【先增减、后运算】 - 后置运算:
i++,i--【先运算、后增减】
1. 前置运算(++i, --i):先变后用
前置就像是“先付钱,再拿货”。程序会先执行加 1 操作,然后再把加完后的新值拿去参与运算。
- 执行逻辑:
i自身增加 1。- 返回
i更新后的值。
代码示例:
int i = 5;
int a = ++i;
// 过程:i 先变成 6,然后把 6 赋值给 a。
// 结果:a = 6, i = 6
2. 后置运算(i++, i--):先用后变
后置就像是“先拿货,后付钱”。程序会先记录下变量当前的值,让这个旧值参与运算,等运算结束的那一刻,再偷偷把变量加 1。
- 执行逻辑:
- 先记住
i当前的值(产生一个临时副本)。 i自身增加 1。- 返回刚才记下的那个“旧值”。
- 先记住
代码示例:
int i = 5;
int a = i++;
// 过程:先记录 i 的旧值 5 赋给 a,然后 i 再变成 6。
// 结果:a = 5, i = 6
重要提示:
- 自增自减运算符的操作数必须是变量,不能是常量或表达式。
int i = 5; i++; // 正确 5++; // 错误!常量不能自增 (i+1)++; // 错误!表达式不能自增 - 在复杂的表达式中避免对同一变量多次使用
++/--,因为其求值顺序可能因编译器而异(未定义行为)。int i = 0; int b = i++ + i++; // 避免这样写!结果不确定。

逗号运算符和表达式
- 形式:
表达式1, 表达式2, ..., 表达式n - 结合性: 从左到右依次计算。
- 优先级: 所有运算符中最低。
- 逗号表达式的值: 等于最后一个表达式
表达式n的值。
例:
// 例1:整个逗号表达式的值是 a*4 的值
a = 3*5, a*4;
// 计算:a先被赋值为15,然后计算 a*4 得60。整个逗号表达式的值是60,但a的值仍是15。
// 例2:赋值只对第一个表达式有效
a = 3*5, a*4, a+5;
// 计算:a=15, 计算 15*4=60(丢弃), 计算 15+5=20。整个表达式的值是20,a=15。
// 例3:括号改变优先级
x = (a = 3, 6 * 3); // 括号内是逗号表达式,值=18。所以 x = 18, a = 3。
// 例4:注意结合性
x = a = 3, 6 * a; // 等价于 (x = a = 3), (6 * a)
// x 和 a 都等于3,整个逗号表达式的值是 6*3=18。
// 例5:逗号作为分隔符 vs 运算符
int a = 1, b = 2, c = 3; // 这里的逗号是分隔符
printf("%d,%d,%d", a, b, c); // 输出:1,2,3
printf("%d,%d,%d", (a, b, c), b, c); // 第一个参数是逗号表达式(a,b,c),值为c=3
// 输出:3,2,3
关系运算符和表达式
- 种类:
<(小于)<=(小于等于)>(大于)>=(大于等于)==(等于)!=(不等于)。 - 结合方向: 自左向右。
- 优先级别:
- 较高:
<,<=,>,>= - 较低:
==,!=
- 较高:
- 关系表达式的值: 关系成立则为逻辑“真”,用整数 1 表示;不成立则为逻辑“假”,用整数 0 表示。
int result; result = (5 > 3); // result 的值为 1 result = (5 == 3); // result 的值为 0
逻辑运算符和表达式
- 种类:
!(逻辑非)&&(逻辑与)||(逻辑或)。

运算规则(真值表简化):
&&(与): 有假即为假。全真才为真。||(或): 有真即为真。全假才为假。!(非): 真假互换。
C语言中的真假:
- 判断运算量时: 0 表示“假”,任何非 0 值(如1, -1, 100)都表示“真”。
- 逻辑运算的结果: 只产生整数 1(真)或 0(假)。
优先级(从高到低): ! > && > ||
结合方向: ! 从右向左;&& 和 || 从左向右。
短路求值特性
逻辑表达式求解时,并非所有运算符都会被执行。只有在必须执行下一个运算符才能确定整个表达式的最终结果时,才会执行它。
a && b:如果a为假(0),则整个表达式结果已确定为假,b将不会被计算。a || b:如果a为真(非0),则整个表达式结果已确定为真,b将不会被计算。
示例:
int i = 0, a = 5;
if (i != 0 && a / i > 2) { // 由于 i!=0 为假,`a / i` 这个危险的除法根本不会执行
// 安全地避免了除零错误
}
int j = 10;
if (j > 5 || ++j < 20) { // 由于 j>5 为真,`++j` 不会执行,j 的值仍为 10
printf(“%d”, j); // 输出 10
}

条件运算符和表达式
条件运算符 (?:) 是 C 语言中唯一的三目运算符(需要三个操作数),它提供了一种简洁的表达“二选一”逻辑的方式,可以看作是 if-else 语句的简化形式。
-
一般形式:
表达式1 ? 表达式2 : 表达式3表达式1:通常是一个关系或逻辑表达式,用于判断条件。表达式2:当表达式1为真(非0) 时,整个条件表达式的值。表达式3:当表达式1为假(0) 时,整个条件表达式的值。
-
执行过程(“二选一”):
- 首先计算
表达式1的值。 - 如果
表达式1的值为真(非0),则计算表达式2的值,并将该值作为整个条件表达式的最终结果。表达式3不会被执行。 - 如果
表达式1的值为假(0),则计算表达式3的值,并将该值作为整个条件表达式的最终结果。表达式2不会被执行。
其逻辑可以概括为:为真取前者(表达式2),为假取后者(表达式3)。
![[条件运算符执行过程]]
- 首先计算
-
优先级与结合性:
- 优先级(13): 高于赋值运算符 (
=),但低于大多数其他运算符(如算术、关系、逻辑运算符)。通常为了清晰,建议在复杂表达式中使用括号()。 - 结合性:自右向左。这一特性在嵌套使用时尤为重要,决定了多个条件运算符同时出现时的求值顺序。
- 优先级(13): 高于赋值运算符 (
-
嵌套使用:
由于结合性是自右向左,可以将多个条件运算符串联起来,实现多分支选择(类似于if-else if-else链)。示例:将成绩分数转换为等级
int score = 85; char grade; // 使用嵌套的条件表达式 grade = (score >= 90) ? 'A' : (score >= 80) ? 'B' : (score >= 70) ? 'C' : (score >= 60) ? 'D' : 'F'; printf("成绩 %d 的等级是:%c\n", score, grade); // 输出:成绩 85 的等级是:B执行顺序解析(自右向左结合):
- 先看最右边的
(score >= 60) ? 'D' : 'F',这是一个独立的条件表达式。 - 它作为其左边表达式
(score >= 70) ? 'C' : (上一步结果)的“假值部分”。 - 以此类推,从左到右进行判断,直到找到第一个为真的条件。
- 先看最右边的
-
常见用途:
- 简化简单的
if-else赋值:int a = 5, b = 10, max; // if-else 写法 if (a > b) { max = a; } else { max = b; } // 条件运算符写法(更简洁) max = (a > b) ? a : b; - 在函数调用或表达式中进行内联选择:
printf("绝对值是:%d\n", (x >= 0) ? x : -x); int fee = (age < 18) ? 50 : 100; // 根据年龄选择费用
注意: 虽然条件表达式很简洁,但过度嵌套或逻辑复杂时会降低代码可读性。在这种情况下,使用
if-else语句通常是更好的选择。 - 简化简单的
数据的输入和输出
C语言没有 I/O 语句,I/O 操作由函数实现
#include <stdio.h>
数据输出
-
字符输出函数
格式:putchar(c)
参数:c 为字符常量、变量或者表达式
功能: 把字符 c 输出到显示器上
例题#include <stdio.h> main() { int c; char a; c = 65 ; a = 'B'; putchar(c);putchar('\n');putchar(a); // A // B } -
格式输出函数
格式:printf("格式输出串",输出表)
功能: 按指定格式向显示器输出数据
输出表:要输出的数据(可以没有,多个时以“,”分隔)
格式输出串: 包含两种信息
- 格式说明: %[修饰符] 格式字符,用于指定输出格式
- 普通字符或转义序列:原样输出
格式字符

[!note]
说明
- 格式字符要用小写
- 格式字符与输出项个数应相同,按照先后顺序一一对应
- 输出转换: 格式字符与输出项类型不一致,自动按指定格式输出
有符号的 -1 就等价于无符号的 65535

数据输入
在C语言中,要从键盘获取用户输入的数据,最常用的是字符输入函数 getchar() 和格式输入函数 scanf()。
1. 字符输入函数 getchar()
- 函数原型:
int getchar(void); - 功能: 从标准输入流(通常为键盘) 读取下一个字符(包括空格、制表符、换行符)。
- 返回值:
- 成功时,返回读取到的字符(转换为
unsigned char类型,再转为int)。 - 遇到文件结束符(EOF,在Windows命令行中通常由
Ctrl+Z产生)或发生错误时,返回EOF(通常定义为 -1)。
- 成功时,返回读取到的字符(转换为
- 特点: 它是一个阻塞式函数,程序会暂停并等待用户输入,直到用户按下回车键。回车键本身(
\n)也会作为一个字符被读取或留在输入缓冲区中。
示例:从键盘读取一个字符并回显
#include <stdio.h>
int main() {
char ch;
printf("请输入一个字符,然后按回车键:");
ch = getchar(); // 等待用户输入,读取第一个字符
printf("您输入的字符是:%c\n", ch);
printf("它的ASCII码是:%d\n", ch);
// 一个常见用法:用getchar()来“吃掉”scanf输入后留在缓冲区的换行符
int num;
printf("请输入一个数字:");
scanf("%d", &num);
getchar(); // 读取并丢弃数字输入后残留的换行符,为后续输入清空缓冲区
printf("现在请输入一个字母:");
ch = getchar();
printf("字母是:%c\n", ch);
return 0;
}
2. 格式输入函数 scanf()
- 函数原型:
int scanf(const char *format, ...); - 格式:
scanf("格式控制串", 地址表); - 功能: 按照“格式控制串”指定的格式,从标准输入读取数据,并依次存入“地址表”中对应的变量内存地址里。输入同样以回车键结束。
- 地址表: 必须是变量的内存地址。对于基本类型变量(如
int,float,char数组首字符),使用取地址运算符&来获取其地址。对于字符数组名(字符串)和指针变量,其本身代表地址,则不需要&。
示例:读取不同类型的数据
#include <stdio.h>
int main() {
int age;
float height;
char name[50]; // 字符数组,用于存储字符串
printf("请依次输入您的年龄、身高(米)和姓名(用空格隔开):\n");
// 注意:name是数组名,本身代表地址,所以不需要 &name
int count = scanf("%d %f %s", &age, &height, name);
printf("成功读取了 %d 个数据。\n", count);
printf("年龄:%d岁,身高:%.2f米,姓名:%s\n", age, height, name);
return 0;
}
输入分隔符的指定:
- 一般以空格或回车键作为分隔符
- 其他字符做分隔符:格式串中两个格式符间字符
3. gets() 是一个 C 语言函数,作用是:从键盘读入一整行文字,存到你给它的变量里。
gets(str[i]); 到底在做什么?
假设你有一个字符串数组,大概是这样定义的:
c
运行
char str[100][100]; // 可以存 100 个字符串,每个最长100字符
那么:
str = 整个字符串数组
str[i] = 第 i 个字符串的地址(就是一段可以放文字的空间)
gets(str[i]) =
从键盘读入一行文字,直接存到第 i 个字符串里
fgets
编写程序,将⽤户输⼊的字符串中所有的字符a去掉,然后输出剩余的字符。
#include <stdio.h>
int main(){
char a[100],b[100];
int i ,j=0;
fgets(a,100,stdin); // 100 限定长度,stdin 标准输入
/*
* //scanf("%s",a) 雷同,这是整个地址。
*/
for(i = 0; i<100 ; i++){
if(a[i] != 'a'){
b[j] = a[i];
j++;
}
}
puts(b); // 输出,与 gets 对应
}
\0 写法,节约时间和运行空间
//编写程序,将⽤户输⼊的字符串中所有的字符a去掉,然后输出剩余的字符。
#include <stdio.h>
int main(){
char a[100],b[100];
int i ,j=0;
fgets(a,100,stdin); // 100 限定长度,stdin 标准输入
/*
* //scanf("%s",a) 雷同,这是整个地址。
*/
for(i = 0; a[i] != '\0' ; i++){
if(a[i] != 'a'){
b[j] = a[i];
j++;
}
}
b[j] = '\0';
puts(b);
}
重要的格式控制符与示例:
| 格式符 | 含义 | 变量类型 | 示例输入 (scanf语句) |
|---|---|---|---|
%d |
读取十进制整数 | int * |
scanf(“%d”, &num); |
%f |
读取浮点数 | float * |
scanf(“%f”, &price); |
%lf |
读取双精度浮点数 | double * |
scanf(“%lf”, &pi); |
%c |
读取单个字符 | char * |
scanf(“ %c”, &ch); (注意%c前的空格) |
%s |
读取一个字符串(遇空格/换行终止) | char * (字符数组) |
scanf(“%s”, str); |
%[...] |
扫描字符集合 | char * |
scanf(“%[A-Z]”, str); //只读入大写字母 |
当 %d%d 在一起,一定要有间隔
当 %c%c 在一起,不需要间隔
当 %d%c 在一起,不需要间隔
当 %c%d在一起,有没有间隔都可以
%c 前面一定不可以有间隔
⚠️ 关键注意事项(常见陷阱与解决方案):
-
scanf与getchar的缓冲区问题:
scanf在读取数字或字符串后,会将用户按下的回车符(\n) 留在输入缓冲区。如果后面紧跟着一个getchar()或scanf(“%c”, &ch),它会立刻读取到这个残留的\n,导致程序“跳过”等待。
解决方案: 在读取字符前,使用getchar()清空缓冲区。int a; char op; scanf(“%d”, &a); // 输入 10<回车> getchar(); // 读取并丢弃缓冲区中的回车符 op = getchar(); // 现在可以正常等待用户输入运算符,如 ‘+’ -
scanf读取字符串的安全问题:
scanf(“%s”, str);不会检查数组边界,如果输入过长会导致缓冲区溢出,这是严重的安全漏洞。
更安全的替代方案: 使用fgets()函数读取整行。char safe_str[100]; fgets(safe_str, sizeof(safe_str), stdin); // 从标准输入读取,最多99个字符+‘\0’ -
scanf的返回值:
scanf返回成功匹配并赋值的数据项个数。可以利用它来判断输入是否符合预期格式。if (scanf(“%d %d”, &x, &y) != 2) { printf(“输入格式错误!需要两个整数。\n”); // 清空错误输入缓冲区 while (getchar() != ‘\n’); }
复合语句
形式 : a=2;b=3;c=a+b;
说明: 复合语句在语法上视为一条语句
空语句
形式: ;
说明: 空语句的功能是什么也不作
选择结构
if 语句
1. 单分支 if 语句
用于条件成立时执行特定操作。
语法:
if (条件表达式) {
语句块;
}
示例:
if (score >= 60) {
printf(“及格\n”);
}
2. 双分支 if-else 语句
用于在两种可能的情况下选择其一执行。
语法:
if (条件表达式) {
语句块1;
} else {
语句块2;
}
示例:
if (score >= 60) {
printf(“及格\n”);
} else {
printf(“不及格\n”);
}
3. 多分支 if-else if-else 语句
用于在多个条件中进行选择。
语法:
if (条件表达式1) {
语句块1;
} else if (条件表达式2) {
语句块2;
} // ... 可以有多個 else if
else {
语句块N; // 所有条件都不满足时执行
}
示例:
if (score >= 90) {
printf(“优秀\n”);
} else if (score >= 80) {
printf(“良好\n”);
} else if (score >= 60) {
printf(“及格\n”);
} else {
printf(“不及格\n”);
}
switch 语句
用于基于一个整型或字符型表达式的值,从多个选项(case)中选择一个执行。与 if 相比,switch 在判断离散、固定的值时结构更清晰。
语法:
switch (表达式) {
case 常量表达式1:
语句块1;
break; // 通常需要 break 来跳出 switch
case 常量表达式2:
语句块2;
break;
// ... 可以有多个 case
default: // 可选的,当所有 case 都不匹配时执行
语句块N;
break;
}
关键点:
表达式的结果必须是整型或字符型。case后的常量表达式必须是编译时常量(如1,‘A’,MONDAY)。break语句的作用:跳出整个switch语句。如果省略break,程序会继续执行下一个case中的语句,直到遇到break或switch结束(这称为“case穿透”或“落空”)。default子句是可选的,用于处理所有case都不匹配的情况。
示例:
int day = 3;
switch (day) {
case 1:
printf(“星期一\n”);
break;
case 2:
printf(“星期二\n”);
break;
case 3:
printf(“星期三\n”);
break;
default:
printf(“无效的日期\n”);
break;
}
// 输出:星期三
break 语句在 switch 中的作用
在 switch 语句中,break 语句用于终止当前 case 的执行并跳出整个 switch 语句体,防止程序继续执行下一个 case 的代码(即避免“case穿透”)。
图示说明了 break 如何控制执行流的跳出:
对比示例 (无 break):
int option = 2;
switch (option) {
case 1: printf(“执行选项1\n”);
case 2: printf(“执行选项2\n”); // 从这里开始执行
case 3: printf(“执行选项3\n”); // 因为没有break,会继续执行
default: printf(“执行默认\n”); // 继续执行
}
// 输出:
// 执行选项2
// 执行选项3
// 执行默认
总结:
if语句:适用于条件判断逻辑复杂、范围判断(如x > 10)、或条件非整型的情况。switch语句:适用于根据一个离散的整型或字符型值进行多路分支选择,结构清晰。break:在switch中是控制流程的关键,通常每个case后都应添加,除非刻意利用“穿透”特性。
循环结构
C语言可实现循环的语句
- while语句
- do~while语句
- for 语句
while 语句
一般形式:
while(表达式)
循环体语句;
执行流程 :
![[while 执行流程]]
特点: 先判断表达式,后执行循环体。适用于循环次数不确定,但循环条件明确的场景。
例题1:计算 1 到 100 的累加和
#include <stdio.h>
int main() {
int i = 1;
int sum = 0;
while (i <= 100) {
sum += i; // 等价于 sum = sum + i;
i++; // 循环变量自增,避免死循环
}
printf("1到100的和为: %d\n", sum);
return 0;
}
例题2:输入一系列正整数,以0结束,计算它们的和
#include <stdio.h>
int main() {
int num, sum = 0;
printf("请输入正整数(输入0结束): ");
scanf("%d", &num);
while (num != 0) {
sum += num;
scanf("%d", &num); // 继续读取下一个数
}
printf("所有数的和为: %d\n", sum);
return 0;
}
do~while 语句
一般形式:
do {
循环体语句;
} while(表达式); // 注意分号
执行流程:
![[do~while 语句]]
特点: 先执行循环体,后判断表达式。循环体至少会执行一次。适用于需要先执行操作再检查条件的场景(如菜单显示)。
例题:用户密码验证(至少给一次输入机会)
#include <stdio.h>
int main() {
int password;
int correct_pwd = 123456;
do {
printf("请输入密码: ");
scanf("%d", &password);
if (password != correct_pwd) {
printf("密码错误!\n");
}
} while (password != correct_pwd); // 密码错误则继续循环
printf("登录成功!\n");
return 0;
}
for 语句
一般形式:
for([expr1]; [expr2]; [expr3]) {
循环体语句;
}
// expr1: 循环变量初始化(只执行一次)
// expr2: 循环条件(每次循环前判断)
// expr3: 循环变量更新(每次循环体执行后执行)
执行流程:
![[for 语句执行流程]]
先执行 expr1 ,再判断 expr2 ,如果为真则执行循环体,然后执行 expr3,接着再次判断 expr2... 如果 expr2 为假则跳出循环。
例题1:打印九九乘法表
#include <stdio.h>
int main() {
int i, j;
for (i = 1; i <= 9; i++) { // 外层循环控制行
for (j = 1; j <= i; j++) { // 内层循环控制列
printf("%d*%d=%-2d ", j, i, i * j); // %-2d 左对齐,占2位
}
printf("\n"); // 换行
}
return 0;
}
例题2:计算 n 的阶乘
#include <stdio.h>
int main() {
int n;
long long factorial = 1; // 阶乘结果可能很大,用 long long
printf("请输入一个正整数: ");
scanf("%d", &n);
// for循环的三个表达式都可以灵活变化
for (int i = 1; i <= n; i++) {
factorial *= i;
}
printf("%d! = %lld\n", n, factorial);
return 0;
}
三种循环对比与选择
| 循环类型 | 特点 | 适用场景 |
|---|---|---|
while |
“当型”循环,先判断后执行。可能一次都不执行。 | 循环次数不确定,但结束条件明确。例如:读取数据直到文件结尾。 |
do...while |
“直到型”循环,先执行后判断。至少执行一次。 | 需要至少执行一次的场景。例如:菜单显示、输入验证。 |
for |
结构紧凑,将循环变量的初始化、条件判断、更新写在一起。 | 循环次数确定或需要精确控制循环变量的场景。例如:遍历数组、固定次数的计算。 |
提示:
- 三种循环可以互相转换,选择哪种主要看代码可读性和逻辑清晰度。
- 循环中可以使用
break语句立即终止整个循环,或使用continue语句跳过本次循环的剩余代码,直接进入下一次循环条件判断。- 小心死循环!确保循环条件最终能变为假,或在循环体内有能跳出循环的机制(如
break)。
break 与 continue
break 语句
- 功能:立即终止所在的
switch语句或循环(for,while,do...while),并跳出该结构,继续执行结构之后的代码。 - 一般形式:
break; - 注意:
break语句只能用于switch语句或循环语句内部。
示例:在循环中查找元素
#include <stdio.h>
int main() {
int numbers[] = {1, 3, 5, 7, 9, 2, 4, 6};
int target = 5;
int found = 0; // 0 表示未找到
for (int i = 0; i < 8; i++) {
if (numbers[i] == target) {
printf("在索引 %d 找到了数字 %d。\n", i, target);
found = 1; // 标记为已找到
break; // 找到后立即跳出循环,不再继续遍历
}
}
if (!found) {
printf("数组中未找到数字 %d。\n", target);
}
return 0;
}
continue 语句
- 功能:结束本次循环的当前迭代,跳过循环体中
continue之后尚未执行的语句,直接进入下一次循环的条件判断(for循环则会先执行迭代语句,如i++)。 - 一般形式:
continue; - 注意:它并不终止整个循环,只是“跳过”一轮。
示例:打印 1 到 10 之间的奇数
#include <stdio.h>
int main() {
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) { // 如果 i 是偶数
continue; // 跳过本次循环的剩余部分,不执行下面的 printf
}
printf("%d ", i); // 只会打印奇数
}
// 输出: 1 3 5 7 9
return 0;
}
循环嵌套
一个循环体内包含另一个完整的循环结构,称为循环嵌套。
- 执行流程:
- 外层循环执行一次。
- 对于外层的这一次迭代,内层循环要完整地执行其所有迭代。
- 内层循环结束后,回到外层循环,进行下一次迭代(如
i++),然后重复步骤2。 - 当外层循环的条件变为假时,整个嵌套循环结束。
- 循环体的确定:
- 如果循环体包含多条语句,必须用花括号
{}括起来形成一个复合语句。 - 如果循环体只有一条语句,可以省略
{},此时循环体到第一个分号;结束。
- 如果循环体包含多条语句,必须用花括号
示例:打印九九乘法表
#include <stdio.h>
int main() {
for (int i = 1; i <= 9; i++) { // 外层循环,控制行数(被乘数)
for (int j = 1; j <= i; j++) { // 内层循环,控制每行的列数(乘数)
printf("%d*%d=%2d ", j, i, i * j); // %2d 用于对齐
}
printf("\n"); // 每行结束后换行
}
return 0;
}
示例:使用 break 在嵌套循环中
#include <stdio.h>
int main() {
for (int i = 0; i < 3; i++) {
printf("外层循环 i = %d\n", i);
for (int j = 0; j < 5; j++) {
if (j == 2) {
break; // 此 break 只跳出当前的**内层** for 循环
}
printf(" 内层循环 j = %d\n", j);
}
}
return 0;
}
/* 输出:
外层循环 i = 0
内层循环 j = 0
内层循环 j = 1
外层循环 i = 1
内层循环 j = 0
内层循环 j = 1
外层循环 i = 2
内层循环 j = 0
内层循环 j = 1
*/
注意:此处的
break仅跳出内层循环,外层循环继续正常执行。
数组的应用
数组: 是一组具有相同数据类型的数据的有序集合
定义格式: 类型说明符 数组名 [常量表达式] :
例如: int a [10]
它表示定义了一个整形数组,数组为 a,此数组有 10 个元素
a[0] , a[1],a[2] ......a[9]]
补充说明:
- 常量表达式:通常是一个整型常量,它指定了数组的大小(元素个数),在编译时必须确定,不能是变量。
- 下标范围:C语言数组下标从
0开始,到[数组长度-1]结束。访问a[10]是越界的,会导致未定义行为。
一维数组的引用
引用方式:
数组名[下标]
下标可以是整型常量或整型表达式
例如:
int a[10];
a[0]=2
a[1] = a[5]+a[7]-a[2*3]
补充说明:
- 数组必须先定义,后使用。
- 引用时,下标表达式的值必须在合法的下标范围内。
一维数组的初始化
- 在定义数组时对数组元素赋以初值。
int a [10] = {0,1,2,3,4,5,6,7,8,9}; - 可以只给一部分元素赋值------没赋值的默认为 0
int a [10]= {0,1,2,3,4,5}; - 在对全部数组元素赋初值时,由于数组的个数已经确定,因此可以不指定数组长度
例如:int a [5] = {1,2,3,4,5};
也可以写成int a [] = {1,2,3,4,5} ;
补充说明与例题:
- 初始化与赋值的区别:初始化是在定义数组时完成的。定义后,不能再用
a = {1,2,3};这样的语法整体赋值。 - 例题1:数组遍历与求和
#include <stdio.h> int main() { int nums[] = {12, 45, 7, 23, 56}; // 不指定长度,由初始化列表决定 int sum = 0; int size = sizeof(nums) / sizeof(nums[0]); // 计算数组元素个数 for(int i = 0; i < size; i++) { printf("nums[%d] = %d\n", i, nums[i]); sum += nums[i]; } printf("数组元素总和为:%d\n", sum); return 0; }
冒泡排序
//编写一个c语言程序,输入十个学生的成绩,储存在一维数组中,用冒泡法进行排序,按分数从高到低输出成绩。
#include <stdio.h>
int main(){
float a[10],t;
int i ,j;
for(i=0;i<10;i++){
scanf("%f",&a[i]);
}
for(j=0;j <9 ; j++){ // 次数=要排序的数量-1
for(i=0;i<9-j;i++){
if(a[i]<a[i+1]){
t = a[i];
a[i] = a[i+1];
a[i+1] = t;
}
}
}
for(i=0;i<10;i++){
printf("%.2f ",a[i]);
}
return 0;
}
选择排序

#include <stdio.h>
int main(){
float a[10] ,t ;
int i ,j , z ;
for(i=0;i<10;i++){
scanf("%f",&a[i]);
}
for(i=0;i<9;i++){
z = i;
for(j=i+1;j<10;j++){
if (a[j]<a[z]){ // 这里是后者比前者 j=i+1 , 所以是下标大的比下标小的
z = j;
}
}
t = a[i]; // 因为此时的 z 不是等于 i 的状态是等于 j 的状态,所以要把 i 和 z交换
a[i] =a[z];
a[z] = t ;
}
for(i=0;i<10;i++){
printf("%f\n",a[i]);
}
return 0;
}
二维数组的定义与引用
定义:
类型说明符 数组名 [常量表达式] [常量表达式];
例如
定义 a 为 3*4 (3 行四列的数组),b为 5*10 (5 行 10 列)的数组
float a[3] [4], b [5] [10]
补充说明:
- 可以将二维数组理解为“数组的数组”。例如
a[3][4]是一个包含3个元素的数组,每个元素又是一个包含4个整数的数组。 - 在内存中,二维数组的元素是按行连续存放的。
a[0][0],a[0][1],a[0][2],a[0][3],a[1][0]... 依次排列。
二维数组的初始化
- 分行给二维数组赋初值
int a[3] [4] = {{1,2,3,4},{5,6,7,8}}
| 1 | 2 | 3 | 4 |
|---|---|---|---|
| 5 | 6 | 7 | 8 |
| 0 | 0 | 0 | 0 |
- 可以将所有数据写在一个花括弧内,按数组排列的顺序对各个元素赋初值。
int a [2] [4] = {1,2,3,4,5,6,7,8}
| 1 | 2 | 3 | 4 |
|---|---|---|---|
| 5 | 6 | 7 | 8 |
- 可以对部分元素赋初值 ----未赋值的为 0
int a [3] [3] ={{1},{5},{9}};
| 1 | 0 | 0 |
|---|---|---|
| 5 | 0 | 0 |
| 9 | 0 | 0 |
- 如果说对全部元素都赋初值,则定义数组时对第一维的长度可以不指定,但第二维的长度不能省
int a [] [4] = {1,2,3,4,5,6,7,8,9,10,11,12}
补充例题:
- 例题2:二维数组遍历与找最大值
#include <stdio.h> int main() { int matrix[][3] = {{9, 8, 1}, {4, 12, 7}, {2, 6, 11}}; // 3x3矩阵 int max = matrix[0][0]; int maxRow = 0, maxCol = 0; for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { printf("%4d", matrix[i][j]); if(matrix[i][j] > max) { max = matrix[i][j]; maxRow = i; maxCol = j; } } printf("\n"); } printf("最大值是:%d,位于[%d][%d]\n", max, maxRow, maxCol); return 0; }
字符数组
用来存放字符数据的数组是字符数组,字符数组中的一个元素存放一个字符
char c[5];
c[0] = 'C' ; c[1]='H' ; c[2]='I' ;c[3]='N';c[5]='A';
注:上面例子中 c[5]='A'; 是错误的,因为数组 c 的下标有效范围是 0 到 4。c[5] 是越界访问。
字符数组的初始化
对字符数组初始化,最容易理解的方式是逐个字符赋给数组中各元素`
char c[10]={'I',' ','a','m',' ','h','a','p','p','y'};
//如果为 char c[5]; 如果只定义为赋值,那么里面的数为随机值
char c[15]={'I',' ','a','m',' ','h','a','p','p','y'}; // 未赋值的默认为'\0'
char c[]={'I',' ','a','m',' ','h','a','p','p','y'}; // 可以为空
char c[5]={'I',' ','a','m',' ','h','a','p','p','y'}; // 前面小,后面大,这是错的
字符串和字符串结束标志
C语言规定了字符串结束标志,以字符'\0' 作为标志
可以用字符串常量来使字符数组初始化
char c [6] = {"China"}; // 相当于 China后还有一个\0
char c [6] = "China";
char c [6] = {'C','h','i','n','a','\0'};
// 以上三者在计算机内部是相等的,都是一样的大小,有无花括号都可以
关键补充:
\0(空字符):ASCII码为0,是字符串的终止符。strlen、printf(“%s”)等函数都依赖它来判断字符串在哪里结束。- 数组大小必须足够:用字符串初始化字符数组时,数组长度必须至少是
字符串长度 + 1(为\0预留空间)。char s[5] = “Hello”;是错误的,因为"Hello"需要6个字节(5个字符+1个\0)。
字符数组的输入和输出
%c 输出任一一个字符
%s 输出一串字符
如果输出包含多个\0 那么截止到第一个\0的就输出
gets puts 专门针对字符串
重要补充:
scanf(“%s”, str):用于输入字符串,遇到空格、制表符或换行符即停止。它不会检查数组边界,有缓冲区溢出的风险。gets(str)(已废弃):读取整行,直到换行符,但不安全(不检查边界)。在C11标准中已被移除。fgets(str, sizeof(str), stdin)(推荐):安全地读取一行,包括换行符,并自动在末尾添加\0。它能防止缓冲区溢出。puts(str):输出字符串,并自动追加一个换行符。- 例题3:安全的字符串输入输出
#include <stdio.h> #include <string.h> int main() { char name[20]; printf("请输入您的姓名(可包含空格): "); fgets(name, sizeof(name), stdin); // 去掉 fgets 可能读取的换行符 size_t len = strlen(name); if (len > 0 && name[len-1] == '\n') { name[len-1] = '\0'; } printf("您好,"); puts(name); // puts 会自动换行 return 0; }
字符串处理函数 使用之前要加上 #include <string.h>
-
stract函数---连接
char s[20]="ABC";
char t[20] = "DEF";
strcat(s,t); //把 t 连接到 s 的后面
printf("%s,%s\n",s,t);
输出结果为 ABCDEF,DEF -
strcpy 函数----复制
char s[20]="ABC";
char t[20] = "DEF";
strcpy(s,t); //把 t 复制给 s
printf("%s,%s\n",s,t);
输出结果
DEF,DEF -
strcmp 函数----比较(ASCII码)
strcmp("ABC","ABC"); // 0
strcmp("ABC","ABB"); // 1
strcmp("ABC","ACB"); // -1, B和 c 已经比较出来了,后面的就不需要比了如果字符串 1=字符串 2 函数值为 0
如果字符串 1> 字符串 2,函数值为一正整数
如果字符串 1<字符串 2,函数值为一负整数 -
strlen 函数 ----- 长度
char str [10] ={"China"};
printf("%d",strlen(str));
输出结果
5
strlen 是测试字符串长度的函数,函数的值为字符串中的实际长度(不包括'\n'在内) -
strlwr 函数-----小写
一般形式为: strlwr(字符串)
strlwr 函数的作用是将字符串中大写字母换成小写字母 -
strupr 函数-----大写
一般形式为: strupr(字符串)
strlwr 函数的作用是将字符串中小写字母换成大写字母
补充重要知识点:
-
strcat与strcpy的安全性:这两个函数不检查目标数组的剩余空间,容易导致缓冲区溢出。更安全的替代函数是strncat和strncpy,它们允许指定最大拷贝/连接字符数。char dest[10] = "Hello"; strncat(dest, " World!", sizeof(dest) - strlen(dest) - 1); // 安全连接 -
strcmp的返回值:标准规定,相等返回0,大于返回正数,小于返回负数。不一定是1或-1,所以判断时应使用if(strcmp(s1, s2) == 0),而非if(strcmp(s1, s2))。 -
strlwr和strupr:这两个函数不是C标准库函数,而是某些编译器(如MSVC)提供的扩展。在可移植的代码中,应避免使用,可以自己循环遍历字符并用tolower()/toupper()函数转换(需#include <ctype.h>)。 -
例题4:综合应用——用户登录验证(简化版)
#include <stdio.h> #include <string.h> int main() { char username[20]; char password[20]; char correctUser[] = "admin"; char correctPass[] = "123456"; printf("请输入用户名: "); scanf("%19s", username); // 限制输入长度 printf("请输入密码: "); scanf("%19s", password); // 比较用户名和密码 if (strcmp(username, correctUser) == 0 && strcmp(password, correctPass) == 0) { printf("登录成功!\n"); } else { printf("用户名或密码错误。\n"); } // 演示字符串连接和长度 char welcomeMsg[50] = "欢迎,"; strcat(welcomeMsg, username); printf("%s\n", welcomeMsg); printf("欢迎信息的长度是:%zu\n", strlen(welcomeMsg)); return 0; }
函数
函数的定义
定义无参函数的一般形式:
类型标识符 函数名 ()
{
声明部分
语句部分
}
定义有参函数的一般形式:
类型标识符 函数名 (形式参数表列)
{
声明部分
语句部分
}
定义有参函数 max
int max (int x , int y)
{
int z ;
z = x > y ? x : y ; //x>y 成立走 x,不成立走 y
return (z);
}
定义空函数的一般形式:
类型标识符 函数名 ()
{
}
函数的调用
函数的调用形式
-
语句调用
例如:fun(a,b); -
表达式调用
例如:y=fun(x); -
参数的调用
将函数的返回值作为实参进行调用,成为函数的参数调用
int max (int x , int y) // 形参,负责接受实参传过来的参数
{
int z ;
z = x > y ? x : y ; //x>y 成立走 x,不成立走 y
return (z);
}
// 从主函数开始读
main()
{
int a=3,b=5,c;
c=max(a,b); // 实参,负责往外传数据
printf("%d\n",c)
}
函数参数和函数的值
形式参数: 在定义函数时函数名后面括弧中的变量名,简称形参,作用是接受传来的数据
实际参数: 在主调函数中调用一个函数的时候,函数名后括弧中的参数(可以是一个表达式),简称实参
函数返回值: return 后面表达式的值
函数返回值类型: 定义函数时的类型
[!tip]+ 形参与实参的说明
1.形参需单独定义,属于临时变量,调用函数时为其分配存储单元,函数调用结束后释放其存储单元。
2.实参可以是常量、变量或表达式 (形参不可以是常量,因为形参需要接受数据,他是变化的)
3.实参的类型可以与形参类型不一致,以形参的类型为准
4.实参向形参的数据传递是“值传递”,单向传递,只由实参传给形参,而不能由形参传回来给实参。
5.实参与形参各占用独立的存储单元。
[!note] 关于返回值的说明
- 如果需要从被调用函数(max)带回一个函数值供主调函数(main) 使用,被调用函数中必须包含 return 语句。如果不需要从被调用函数带回函数值可以不要 return 语句。
- 一个函数中可以有一个以上的 return 语句,执行到哪一个 return 语句,哪一个语句起作用,return语句后面的括弧也可以不要。
- 对于不带回值的函数,应当用“void”定义函数为“无类型”(或称“空类型”)。这样,系统就保证不使函数带回任何值,即禁止在调用函数中使用被调用函数的返回值,此时在函数体中不得出现 return 语句。
函数声明
声明的作用: 是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用的时候,编译系统能正确识别函数并检查调用是否合法
如果被调用函数的定义出现在主调函数之前,可以不必加以声明
函数的嵌套调用
c 语言不能嵌套定义函数(嵌套定义就是在定义一个函数时,其函数体又包含另一个函数的完整定义),但可以嵌套调用函数,也就是说,在调用一个函数的过程中,又调用另一个函数
函数的递归调用
在调用一个函数的过程中又出现了直接或间接地调用该函数本身,成为函数的递归调用
int f (int x)
{
int y, z;
z = f(y);
return (2*z);
}
局部变量和全局变量
局部变量: 所有函数内部、复合语句内部定义的变量都是局部变量,仅仅在其所属范围有效
全局变量: 在所有函数外面定义的变量是全局变量,它的作用范围是从定义开始直至源文件结束
变量的存储类别
存储方式分为两大类: 静态存储类和动态存储类
具体包含四种: 自动的(auto), 静态的(static),寄存器的(register),外部的(extern)
静态局部变量
静态(static)局部变量的初值在编译时赋予,在整个程序运行期间存在,其作用域只限于定义它的函数,其他函数不能引用
指针
指针与变量
- 指针的概念
一个变量的地址称为该变量的指针 - 指针的定义
类型名 *指针变量名;
int *p; // p 就是指针
这里的* 号就是指针说明符 - 指针的赋值
-
通过取地址运算符:
int a, *p;p是个指针
p= &a; // 把 a 的地址给了 p
![[取地址运算符图解]]int a ,*p=&a这里也是说把 a 的地址给了 p 而不是*p,因为*只是说明符 -
通过另一个指针
int a, *p ,*q;
p=&a,q=p; -
空指针的定义
int *p =NULL;这种写法是正确的,但是没有意义,不能用,一般多出现判断题
-
int a =3 , b, *P;
p = &a;
b =*p+2; // 把 p 指向的内容拿出来,再+2 ,因为 a 把地址给了 p,所以这里是 a 的值
printf("%d,%d\n",a,b)
/*
如果 p =&a ,那么*p 就是 a 的值
*/
// 假设输入 3 5
int a,b,*p,*q,t;
p = &a;q=&b;
scanf("%d%d",p,q); // 这里就相当于scanf("%d%d,&a,&b"); 所以 现在a=3,b=4
t = *p ; *p = *q;*q =t;
printf("%d,%d\n",*p,*q)
// 交换内容,地址不变
// 5 3
int a,b,*p,*q,t;
p = &a;q=&b;
scanf("%d%d",p,q);
t = p ; p = q;q = t; // 这里没有* ,所以其实交换的是内存地址
printf("%d,%d\n",*p,*q)
// 5 3
// 交换地址,内容不变
指针与一维数组
指向数组元素的指针
int a[10];
int *p;
p = &a[0]; // p指向了 a[0]的位置
通过指针引用数组元素
- 下表法,如 a[i] 形式;
- 指针法,如
*(a+i)或*(p+1).
其中 a是数组名,p 是指向数字元素的指针变量,其初值 p=a
首地址就是第 0 个地址,比如&a[0]
a <=> a[0]
a + 1 <=> a[1]
a + i <=> a[i]
*(a+i) <=> a[i]
指针与二维数组
二维数组的地址
int a[3][4]
说明: a是二维数组名,即二维数组的首地址,第 0 行地址
*(a+1)+2 : 第一行第二列的地址
*(*(a+2)+3): 第二行第三列的地址
a[1]+2 : 第一行第二列的地址
*(a[2]+3): 第二行第三列的地址
二维数组的四种引用形式
a[i][j] <=> *(*(a+i)+j) <=> *(a[i]+j) <=> (*(a+i))[j]

浙公网安备 33010602011771号