C语言学习笔记——其一
C语言学习笔记
该笔记持续更新,仅更新部分,如有错误请在评论区指正,谢谢🙏
首次更新2025-08-31,最后一次更新2025-12-29
常量和变量
常量是在程序运行过程中 值不能改变 的数据;变量是在程序运行过程中 值可以改变 的存储空间。
- 初始化变量:
<类型名称> <变量名称> = <初始值> - 类型名称:告诉编译器,这个变量可以存储什么类型的数据。
- 例如:
int表示整数,float表示单精度浮点数,char表示字符等。
- 例如:
- 变量名称:是程序中引用这个数据的“名字”,方便我们后续使用。
- 定义变量的过程会为它 分配内存空间。
📌 示例:
int age = 18; // 定义(声明)变量 age 并赋值 18
数据类型
整数数据类型
| 整数数据类型 | 大小(字节)* | 取值范围(典型 32 位系统) | 说明 |
|---|---|---|---|
short / short int | 2 | -32,768 ~ 32,767 | 短整型 |
unsigned short | 2 | 0 ~ 65,535 | 无符号短整型 |
int | 4 | -2,147,483,648 ~ 2,147,483,647 | 默认整型 |
unsigned int | 4 | 0 ~ 4,294,967,295 | 无符号整型 |
long / long int | 4(Win) / 8(Linux 64 位) | 取决于系统 | 长整型 |
unsigned long | 同上 | 同上 | 无符号长整型 |
long long | 8 | 约 ±9×10¹⁸ | 长长整型 |
unsigned long long | 8 | 0 ~ 1.8×10¹⁹ | 无符号长长整型 |
short int=short,int可忽略- 使用
unsigned int类型赋值时,输出时的整数占位符需要改为%u - 使用
long类型赋值时,初始值后需要加L,输出时的整数占位符需要改为%ld - 使用
long long类型赋值时,初始值后需要加LL,输出时的整数占位符需要改为%lld
小数数据类型(浮点型)
| 小数数据类型 | 大小(字节) | 有效数字位数 | 范围(约) | 说明 |
|---|---|---|---|---|
float | 4 | 6~7 位 | 3.4E-38 ~ 3.4E38 | 单精度浮点数 |
double | 8 | 15~16 位 | 1.7E-308 ~ 1.7E308 | 双精度浮点数 |
long double | 10/12/16(取决于编译器) | >16 位 | 范围更大 | 扩展精度浮点数 |
💡 浮点数的输入和输出
| 类型 | scanf | printf |
|---|---|---|
float | %f | %f、 %e(科学计数法) |
double | %lf | %f、 %e(科学计数法) |
浮点数的范围与精度
- printf输出inf表示超过范围的浮点数:+∞
- printf输出nan表示不存在的浮点数
在科学计数法里,E(或者 e)表示 “×10 的多少次幂”。
符号可以是-或+也可以省略(表示+)
%.2lf表示保留两位小数(以此类推)
使用float类型赋值时,初始值后需要加F或f
字符类型
| 类型 | 大小(字节) | 取值范围 | 说明 |
|---|---|---|---|
char | 1 | -128 ~ 127(signed)或 0 ~ 255(unsigned) | 存储单个字符(ASCII 码) |
signed char | 1 | -128 ~ 127 | 有符号字符 |
unsigned char | 1 | 0 ~ 255 | 无符号字符 |
1 字节(byte) = 8 位(bit)
定义一个长度为 4 的 char 数组,名为 str :
char str[4] = "aaa"
// 4为字节(aaa+\0)
// C 语言在字符串末尾会自动添加一个 \0(空字符) 作为结束标志。
char str[999];
scanf("%s"),&str);// 键盘录入
printf("录入%s",str);
sizeof测量某种类型的大小
sizeof可以用来测量数据类型或变量在内存中占用的字节数。
sizeof(数据类型)
sizeof(变量名)
sizeof(数据类型)→ 直接测量某种类型的大小sizeof(变量名)→ 测量某个变量的大小(根据变量的类型决定)
示例
#include <stdio.h>
int main() {
printf("int 占用 %zu 字节\n", sizeof(int));
printf("double 占用 %zu 字节\n", sizeof(double));
char c = 'A';
printf("变量 c 占用 %zu 字节\n", sizeof(c));
int arr[10];
printf("数组 arr 占用 %zu 字节\n", sizeof(arr)); // 整个数组的字节数
printf("arr[0] 占用 %zu 字节\n", sizeof(arr[0])); // 单个元素的字节数
return 0;
}
转义字符
转义字符(escape character) 是以反斜杠 \ 开头的特殊字符序列,用来表示一些无法直接输入或具有特殊意义的字符。
| 转义字符 | 含义 | 示例 |
|---|---|---|
\n | 换行(newline) | printf("Hello\nWorld"); → 输出两行 |
\t | 水平制表符(tab) | printf("A\tB"); → A 和 B 之间有一个制表空格 |
\ | 反斜杠本身 | printf("\"); → 输出 \ |
' | 单引号 | printf("'"); → 输出 ' |
" | 双引号 | printf("""); → 输出 " |
\0 | 空字符(字符串结尾标志) | "abc\0def" 只会输出 abc |
\r | 回车(carriage return) | 光标移动到行首 |
\b | 退格(backspace) | 光标回退一格 |
\xhh | 十六进制 ASCII 码 | \x41 → A |
\ooo | 八进制 ASCII 码 | \101 → A |
类型转换
- 隐式类型转换(自动转换):低精度 → 高精度
- 显式类型转换(强制类型转换 ):
(新类型) 表达式
double pi = 3.14;
int x = (int)pi; // x = 3,小数部分被截断
char ch = (char)97; // ch = 'a'
赋值计算
加减乘除计算
// 加减计算
int a;
int b;
scanf("%d %d",&a,&b);
printf("%d",a+b);
注:
- 整数计算的结果一定是整数
- 小数直接参与计算结果可能不准确
- 取余的数据必须都是整数
- 取余的正负是跟第一个数字保持一致的
- 单目(+/-)优先级高于乘除
- 强制类型转换的优先级高于四则运算
printf("%d\n",2 + 1); // 3
printf("%d\n",10 / 3); // 3
printf("%d\n",-10 / 3); // -1
复合赋值
- 5个算术运算符:“
+=,-=,*=,/=”
total += 5; // total = total+5
total *= sum + 12 // total = total*(sum+12)
- 递增递减运算符:“++,—” (变量+1 / -1)
- a++和a—都可以
- a++是a+1以前的值,也就是说a++的值是10+1=11之前的a值,等于10,那么此时a=11,而++a这一步是说a此时值已经是11了,但++a的值是直接等于a+1运算之后的结果,所以a=12
#include <stdio.h>
int main()
{
int a;
a = 10;
printf("a++=%d\n", a++);
printf("a=%d\n", a);
printf("++a=%d\n", ++a);
printf("a=%d\n", a);
return 0;
}
a++=10 // 后缀自增:先用 10,再变 11
a=11
++a=12 // 前缀自增:先变 12,再用 12
a=12
逻辑运算
| 运算符 | 名称 | 功能说明 |
|---|---|---|
&& | 逻辑与(AND) | 两个操作数都为真时,结果才为真 |
! | 逻辑非(NOT) | 对操作数取反,真变假,假变真 |
| | |
- 表达$ a∈(4,6) $ ➡️
a>4 && a<6 - 判断是否为大写字母:
c≥’A’ && c≤’Z’ - 优先级:
!>&&>||
逻辑运算自左向右,若左边不成立,直接短路,右边的也不会执行,例如
a==6 && b+=1
条件运算和逗号运算
如果条件为真,则1,否则2
条件表达式 ? 表达式1 : 表达式2
表达式1, 表达式2, 表达式3
表达式1, 表达式2, 表达式3
if语句
if (im < 0) {
im = 60 + im;
ih --;
}
关系语句
| 关系运算符 | 意义 | |
|---|---|---|
| == | 相等 | |
| ! = | 不相等 | |
| >/≥/</≤ | 大于/大于或等于/小于/小于或等于 |
关系运算的结果:符合预期为整数1,反之为0
printf("%d",5==3); // 0
printf("%d",5!=3); // 1
所有关系运算符的优先级比算术运算符低,但是比赋值运算高
printf("%d",7 >=3+4); // 1
但! =和==的优先级较其他关系运算符低
6 > 5 > 4 // 0
/*6>5 变成了 1 ,1 > 4不成立,结果为0*/
else
找零程序示例:
// 初始化变量
int price = 0;
int bill = 0;
// 提示输入并获取数据
printf("请输入金额: ");
scanf("%d", &price);
printf("请输入票面: ");
scanf("%d", &bill);
// 计较零
if (bill >= price) {
printf("应该找您: %d\n", bill - price);
} else {
printf("您的钱不够\n");
}
if (diff < 0) diff += 24 * 60; // = if (diff < 0) { ...}
级联的if-else
分段函数
int main()
{
int x = 2;
int f;
if (x < 0) {
f = -1;
} else if (x == 0) {
f = 0;
} else {
f = 2 * x;
};
printf("f is %d",f);
return 0;
}
switch-case语句
switch (表达式) {
case 常量1:
// 代码块1
break;
case 常量2:
// 代码块2
break;
default:
// 可选,处理其他情况
break;
}
- switch语句可以看作是一种基于计算的跳转,计算控制表达式的值后,程序会跳转到相匹配的casee(分支标号)处。分支标号只是说明switch内部位置的路标,在执行完分支中的最后一条语句后如果后面没有break,就会顺序执行到下面的case里去,直到遇到一个break,或
者switch结束为止 - 表达式:通常是一个整型或枚举类型的值
示例输出:Wednesday
#include <stdio.h>
int main() {
int day = 3;
switch (day) {
case 1:
printf("Monday\n");
break;
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break;
default:
printf("Other day\n");
break;
}
return 0;
}
循环
while循环
- 计算数字位数程序
int x;
int n=0;
scanf("%d",&x);
n++;
x /=10;
while (x > 0)
{
n++;
x /=10;
}
printf("%d\n",n);
-
do-while循环 先执行一次循环体,再判断条件
#include <stdio.h>
int main()
{
int x;
int n = 0;
scanf("%d", &x);
do {
n++;
x /= 10;
} while (x > 0);
printf("%d\n", n);
return 0;
}
猜数游戏
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand(time(0)); // 用当前时间初始化随机数种子
int number = rand() % 100 + 1; // 生成 1~100 的随机整数
int count = 0; // 猜测次数
int a = 0; // 玩家输入的数字
printf("我已经想好了一个1到100之间的数。\n");
do {
printf("请猜这个1到100之间的数:");
scanf("%d", &a);
count++;
if (a > number) {
printf("你猜的数大了。\n");
} else if (a < number) {
printf("你猜的数小了。\n");
}
} while (a != number);
printf("太好了,你用了%d次就猜到了答案。\n", count);
return 0;
}
算平均数
- 读取一组整数,遇到
-1时停止输入,并计算这些数的平均值。
#include <stdio.h>
int main()
{
int number; // 用于存储当前输入的数
int sum = 0; // 所有输入数的累加和
int count = 0; // 已输入的数字个数
scanf("%d", &number); // 先读入一个数字
while ( number != -1 ) { // 当输入的不是 -1 时循环继续
sum += number; // 把当前数字加到 sum
count ++; // 计数器加 1
scanf("%d", &number); // 再读取下一个数字
}
printf("%f\n", 1.0*sum/count);
return 0;
}
整数求逆
一个整数的分解:
- 对一个整数%10,求出它的个位数
- 再对这个整数/10,将它的个位数丢掉
- 对步骤2后的数再%10,求出新的数的个位
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
int digit;
int ret = 0;
while (x > 0) {
digit = x%10;12
ret = ret*10 + digit;
printf("x=%d,digit=%d,ret=%d\n",x,digit,ret);
x/=10;
}
printf("%d",ret);
return 0;
}
for循环
- 用
while循环做阶乘
#include <stdio.h>
int main()
{
int n;
scanf("%d",&n);
int fact = 1;
int i = 1;
while (i<=n) {
fact *= i;
i++;
}
printf("%d!=%d\n",n,fact);
return 0;
}
- 使用
for循环做阶乘
#include <stdio.h>
int main()
{
int n;
scanf("%d",&n);
int fact = 1;
int i = 1;
for ( i=1; i<=n; i++) {
fact *= i;
}
printf("%d!=%d\n",n,fact);
return 0;
}
// 因为编译器原因,i+1可以改为int 1 =1并把上句的i初始化删去
i=1为初始化表达式,i<=n为条件表达式(循环继续的条件),i++为更新表达式(循环每轮要做的事情),{}里为循环体语句;
for (初始化表达式; 条件表达式; 更新表达式) {
循环体语句;
}
- for循环执行流程:
- 执行初始化表达式。
- 判断条件表达式,为真 → 执行循环体。
- 执行更新表达式。
- 重复步骤 2~3,直到条件为假。
🔹 小技巧:
for的三个部分可以省略,但分号不能省,例如for(;;)是死循环。for循环常用来遍历数组、执行计数任务等。- 如果有固定次数,用
for;必须执行一次,用do-while;其他用while
循环控制
使用continent和break来控制循环

1767013759728.png
素数的判断和输出
- 素数的判断
#include <stdio.h>
int main()
{
int x,i,isPrime = 1;//isPrime为1的时候是素数,为0的时候不是素数
scanf("%d",&x);
for (i=2;i<x;i++) {
if (x%i == 0) {
isPrime = 0;
break; //break跳出循环
}
}
if (isPrime == 1) {
printf("是素数\n");
} else {
printf("不是素数\n");
}
return 0;
}
- 使用嵌套循环输出100以内的素数
#include <stdio.h>
int main()
{
int x;
for (x=2;x<100;x++)
{
int i;
int isPrime = 1; //isPrime为1的时候是素数,为0的时候不是素数
for (i=2;i<x;i++) {
if (x%i == 0) {
isPrime = 0;
break; //break跳出循环
}
}
if (isPrime == 1) {
printf("%d\n",x);
}
}
return 0;
}
函数
void sum(int begin,int end) //函数头 **函数声明**
{
int i;
int sum=0;
for (i=begin;i<=end;i++) {
sum += i;
}
printf("%d到%d的和是%d\n",begin,end,sum);
} //函数体
void为返回类型sum为函数名int begin,int end为参数表
调用函数
函数名(实参1, 实参2, ...);
- 函数名:要调用的函数的名字
- 实参(实际参数):调用时传递给函数的值,无参数也要()
从函数返回值
返回类型 函数名(参数列表) {
// 执行一些操作
return 表达式; // 返回一个值
}`
返回类型决定了return后面的表达式类型return的值会交给调用者- 函数返回类型是
void,表示这个函数执行完后 不会返回任何值。
函数原型
作用:
- 提前声明 在调用函数之前,如果函数的定义(函数体)还没有出现,编译器需要通过“函数原型声明”来了解该函数的返回值类型和参数类型。
- 类型检查 编译器会根据函数原型检查实参和形参是否匹配,从而避免类型不一致的错误。
#include <stdio.h>
// 函数原型声明
int add(int a, int b); //函数原型 形参
int main() {
int result = add(3, 5); // 可以直接调用
printf("%d\n", result);
return 0;
}
// 函数定义
int add(int a, int b) {
return a + b; //返回 a+b
}
- 参数名可省略
只声明类型即可:
int add(int, int);
数组
一维数组
定义数组(**数组就是 一组相同类型的变量,在内存中连续存放,每个元素通过 下标(索引) 来访问**)
数据类型 数组名[元素个数];
- 遍历数组 = 把数组里的每一个元素 从头到尾依次访问一次。
读入一组整数,求平均数,然后输出比平均数大的数:
#include <stdio.h>
int main() {
int x;
double sum = 0; // 保存输入数字的总和
int cnt = 0; // 计数器,记录输入了多少个数
int number[100]; // 定义数组,最多存 100 个数
// 输入第一个数
scanf("%d", &x);
// 当输入不是 -1 时循环
while (x != -1) {
number[cnt] = x; // 保存到数组
sum += x; // 累加到 sum
cnt ++; // 数字个数 +1
scanf("%d", &x); // 输入下一个数
}
// 如果至少输入了一个数
if (cnt > 0) {
printf("%f\n", sum / cnt); // 输出平均数
int i;
for (i = 0; i < cnt; i++) { // 遍历数组
if (number[i] > sum / cnt) {
printf("%d\n", number[i]); // 输出比平均数大的数
}
}
}
return 0;
}

1767013794691.png
数组元素的计算
int a[5];
printf("%zu\n", sizeof(a) / sizeof(a[0])); // 输出 5
二维数组
二维数组可以看作是 “数组的数组”。
- 一维数组:一条线 → 存放一组元素
- 二维数组:一个表格(矩阵) → 存放“行 × 列”多个元素
例如:
int a[3][4];
这表示:
a是一个二维数组- 它有 3 行,每行有 4 列
- 一共存储
3 × 4 = 12个int元素
可以把它理解成一个 3 行 4 列的表格。

1767013823692.png
指针
指针变量
指针就是一个变量,但它存储的不是普通的数值,而是 内存地址。
&i 表示变量在内存中的位置(也就是内存地址)。
#include<stdio.h>
int main(){
int a =10;
int *p = &a;
printf("%d\n",*p);
return 0;
}
| 变量名 | 存储内容 |
|---|---|
a | 10 |
&a | 0x7ffeefb4 |
p | 0x7ffeefb4(a 的地址(16进制) |
*P | 10 |
要输出 指针(地址),应该用 %p:
printf("%p\n", p);
#include <stdio.h>
void f(int *p);
int main() {
int i = 6;
printf("&i=%p\n", &i); // 打印 i 的地址
f(&i);
return 0;
}
void f(int *p) {
printf("p=%p\n", p); // 打印传进来的地址
}
&i=0x7ffcc3f4a5ac
p=0x7ffcc3f4a5ac
算最大最小值:
#include <stdio.h>
void minmax(int a[],int len,int *min,int *max);
int main(){
int a[]={1,2,3,4,5,12,32,99};
int min,max;
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("min=%d,max=%d\n",min,max);
return 0;
}
void minmax(int a[],int len,int *min,int *max){
int i;
*min = *max=a[0];
for ( i = 0; i < len; i++)
{
if (a[i]<*min)
{
*min =a[i];
}
if (a[i] > *max) {
*max =a[i];
}
}
}
指针与const
const 的作用:表示 不能修改
const int *p;或int const *p;指向常量的指针(pointer to const)(你不能通过*p修改值,但p本身可以指向别的地方。)
const int a = 10;
const int b = 20;
const int *p = &a;
*p = 30; // ❌ 错误,不能修改 a 的值
p = &b; // ✅ 可以改变指针指向
int *const p;常量指针(const pointer)(指针p本身不能改(指向固定),但是能通过它修改指向的值。)
int a = 10, b = 20;
int *const p = &a;
*p = 30; // ✅ 可以改 a 的值
p = &b; // ❌ 错误,p 的指向不能改
- const数组
const int arr[] = {1, 2, 3, 4};
arr是一个数组,数组类型是const int。- 这意味着 数组中的元素不可修改,即:
arr[0] = 10; // ❌ 错误,不能修改
指针的运算
指针的加法是按类型大小移动
| 运算 | 含义 | 举例 |
|---|---|---|
p + n | 向后移动 n 个元素 | p + 1 指向下一个元素 |
p - n | 向前移动 n 个元素 | p - 1 指向上一个元素 |
p1 - p2 | 计算两个指针之间相差多少个元素 | 仅当它们指向同一数组 |
| 比较运算 | 比如 p1 < p2,判断谁在前面 | 仅同一数组中有意义 |
例如:
int a[3] = {10, 20, 30};
int *p = a;
printf("%d\n", *p); // 输出10
printf("%d\n", *(p+1)); // 输出20
printf("%d\n", *(p+2)); // 输出30
| 操作 | 地址变化 | 说明 |
|---|---|---|
p | 指向 a[0] | 假设地址是 0x1000 |
p + 1 | 地址变成 0x1004 | 因为 int 占4字节 |
p + 2 | 地址变成 0x1008 | 再加4字节 |
p - 1 | 地址变成 0x0FFC | 向前4字节 |
字符数组指针
一个字符串(用数组定义字符串)
char str[10] = "hello";
解释:
- 它是一个长度为10的字符数组;
- 实际内容为:
{'h','e','l','l','o','\0',?,?,?,?}; - 可以像这样访问:
printf("%c\n", str[1]); // e
printf("%s\n", str); // hello
存放多个字符串的数组
char *names[] = {"tom", "jerry", "david"};
printf("%s\n", names[2]); // 输出 david
- 如果你用 %p(打印地址),就可以看到类似:
0x7ffeefbff5a0,这个地址是 names[0] 的地址,也就是指针数组的首地址。
字符串指针
- printf 中的 %s 要求传入一个 char*(字符串指针)
#include <stdio.h>
int main(){
char *a = "abcd";
// 写法1:直接用 a(推荐)
printf("%s\n", a); // 正确!a 本身就是 char*
// 写法2:显式取地址(多此一举但也对)
printf("%s\n", &a[0]); // &a[0] 就是第一个字符的地址
// 写法3:如果你非要打印单个字符,用 %c
printf("%c\n", *a); // 正确!打印 'a'
return 0;
}
- a实际上是
const char *a,由于历史原因,编译器不接受带const的写法,试图修改s所指的字符串会错误 %s需要的是:char *(字符串首地址),从这个地址开始,连续打印字符,直到'\0'- 数组名在表达式中会自动退化为指向首元素的指针
- 字符串指针执行首地址,*p问第一个自付
转换
- 使用指针传参
void f(const int *p); //待定义
int main(void)
{
int a = 10;
f(&a); //传递a的地址
return 0;
}
字符与字符串
单字符的输入输出
**getchar**
- 从 **标准输入(通常是键盘) 读取 一个字符**。
- 返回值是该字符的 ASCII 码(
**int**类型),如果到达文件末尾或出错,返回**EOF**(通常是 -1)
#include <stdio.h>
int main() {
char c;
printf("请输入一个字符:");
c = getchar(); // 读取一个字符
printf("你输入的是:");
putchar(c); // 输出该字符
printf("\n");
return 0;
}
**putchar**
- 向 **标准输出(通常是屏幕) 输出 一个字符**。
- 返回输出的字符,如果出错返回
**EOF**。- 结合
#include <stdio.h>
int main() {
int c;
printf("输入字符,按 Ctrl+D 结束:\n");
while ((c = getchar()) != EOF) { // 读取直到文件结束
putchar(c); // 输出读取的字符
}
return 0;
}
- 这个程序会原样输出你输入的内容,直到输入结束(Linux 用 Ctrl+D,Windows 用 Ctrl+Z)。
getchar()每次取 缓冲区的一个字符 → 返回给c,putchar(c)每次输出 一个字符,循环就实现了 原样输出你输入的内容
strlen函数
接受字符串指针作为实参:
语法/函数原型:
#include <string.h>
size_t strlen(const char *str);
示例:
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello";
char *p = str;
printf("%zu\n", strlen(str)); // 输出 5
printf("%zu\n", strlen(p)); // 输出 5
return 0;
}
其他:
| 占位符 | 类型 | 用途 |
|---|---|---|
%lu | unsigned long | 输出无符号长整型 |
%zu | size_t | 输出数组长度、内存大小等,跨平台安全 |
strcmp函数
- 按 ASCII 顺序 比较两个字符串。
- 返回值:
< 0:第一个字符串小于第二个;0:两个字符串相等;> 0:第一个字符串大于第二个
#include <stdio.h>
#include <string.h>
int main() {
char a[] = "abc";
char b[] = "abd";
int r = strcmp(a, b);
printf("%d\n", r); // 输出负数,'c' < 'd'
return 0;
}
注意点:
- 区分大小写,
'A' != 'a'。 - 只比较到第一个不同的字符或遇到
\0为止。
strcpy函数
函数原型:返回dest指针
#include <string.h>
char *strcpy(char *dest, const char *src);
示例:
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello";
char dest[10];
strcpy(dest, src);
printf("%s\n", dest); // 输出 Hello
return 0;
}
strcat函数
- 用于 字符串拼接:把源字符串
src添加到目标字符串dest的末尾。 - 会覆盖
dest原来的结尾\0,然后在拼接后的末尾加上新的\0。
示例:
#include <stdio.h>
#include <string.h>
int main() {
char str1[20] = "Hello";
char str2[] = " World";
strcat(str1, str2);
printf("%s\n", str1); // 输出 "Hello World"
return 0;
}
结构体
struct 标签名 {
数据类型 成员名1;
数据类型 成员名2;
// ...
数据类型 成员名N;
};
再此之后声明/定义结构体变量,结构体变量包含N个成员
C 语言允许函数返回结构体类型,意味着函数可以返回一个完整的结构体变量,包含所有成员的值。
空结构体
struct EmptyStruct
{
// todo
};
- C++明确允许空结构体(大小为1字节),但C语言严格遵循标准时不支持
匿名结构体
struct { // 定义一个匿名结构体(无类型名)
int id; // 成员1:整型id
int age; // 成员2:整型age
} stu1, stu2; // 直接声明两个变量 stu1 和 stu2
- 通过
<font style="color:rgb(0, 0, 0);">.</font>运算符访问成员
stu1.id = 1001;
stu1.age = 20;
printf("ID: %d, Age: %d\n", stu2.id, stu2.age);
结构体变量与typedef
struct Student {
char name[50];
int age;
};
struct Student s1 = {"Alice", 20}; // s1 是结构体变量 初始化
- 下面二者等同:
typedef struct Student {
char name[50];
int age;
} Student;
Student s1 = {"Alice", 20} // 定义时初始化
struct Student {
char name[50];
int age;
};
typedef struct Student Student; // 单独声明别名
- 也可以按成员名赋值:
struct Student s2 = {.age = 20, .name = "Alice", .score = 88.0};
注意:这种“点号初始化”方式是 C99 标准新增的。
- 访问结构体变量
#include<stdio.h>
typedef struct Birthday
{
int year;
int month;
int day;
}Birthday;
typedef struct Student
{
int id;
char *name;
int age;
float score;
Birthday Birthday;
}Student;
int main(){
Student stu1 = {1001,"nanzhi",18,100,
{2007,1,1 }};
Student stu2 = {1002,"test",18,100,
{2007,1,1 }};
printf("学号:%d\t姓名:%s\t年龄:%d\t成绩:%.2f\t生日:%d-%d-%d\n",stu1.id,stu1.name,stu1.age,stu1.score,
stu1.Birthday.year,stu1.Birthday.month,stu1.Birthday.day);
printf("学号:%d\t姓名:%s\t年龄:%d\t成绩:%.2f\t生日:%d-%d-%d\n",stu2.id,stu2.name,stu2.age,stu2.score,
stu2.Birthday.year,stu2.Birthday.month,stu2.Birthday.day);
return 0;
}
- 结构体作为函数参数
#include<stdio.h>
typedef struct Birthday
{
int year;
int month;
int day;
}Birthday;
typedef struct Student
{
int id;
char *name;
int age;
float score;
Birthday Birthday;
}Student;
void printStudentInfo(Student stu) {
printf("学号:%d\t姓名:%s\t年龄:%d\t成绩:%.2f\t生日:%d-%d-%d\n",stu.id,stu.name,stu.age,stu.score,
stu.Birthday.year,stu.Birthday.month,stu.Birthday.day);
}
int main(){
Student stu1 = {1001,"nanzhi",18,100,
{2007,1,1 }};
Student stu2 = {1002,"test",18,100,
{2007,1,1 }};
printStudentInfo(stu1);
printStudentInfo(stu2);
return 0;
}
结构体指针
#include<stdio.h>
typedef struct Birthday
{
int year;
int month;
int day;
}Birthday;
typedef struct Student
{
int id;
char *name;
int age;
float score;
Birthday Birthday;
}Student;
void printStudentInfo(Student *pStu) {
printf("学号:%d\t姓名:%s\t年龄:%d\t成绩:%.2f\t生日:%d-%d-%d\n",pStu->id,pStu->name,pStu->age,pStu->score,
pStu->Birthday.year,pStu->Birthday.month,pStu->Birthday.day);
//pStu->id 与 (*pStu).id效果等同
}
int main(){
Student stu1 = {1001,"nanzhi",18,100,
{2007,1,1 }};
Student stu2 = {1002,"test",18,100,
{2007,1,1 }};
Student *pStu =&stu1;
printStudentInfo(pStu); // printStudentInfo(&stu1);
pStu = &stu2;
printStudentInfo(pStu);
return 0;
}
结构体的内存对齐
以常见的 GCC / MSVC 默认规则 为例:
- 第一个成员从偏移量0开始放。
- 后续成员按其类型大小(或指定对齐值)的整数倍对齐。
- 整个结构体的总大小要是最大成员对齐数的整数倍
递归
阶乘表示
#include<stdio.h>
int fact(int n) {
if (n == 0){
return 1;
} else {
return n * fact(n - 1); //返回n的阶乘
}
}
int main()
{
int n;
printf("输入一个正整数:");
scanf("%d", &n);
if (n < 0) {
printf("错误:阶乘只能计算非负整数\n");
} else {
printf("%d的阶乘为%d\n", n, fact(n));
}
return 0;
}
文件操作
- 需要在头文件中引入输入输出标准库文件
<stdio.h>和标准库函数<stdlib.h>
文件打开函数 fopen
FILE *fp;
fp = fopen("test.txt", "r");
FILE *fopen(const char *filename, const char *mode);
| 模式 | 说明 |
|---|---|
"r" | 以只读方式打开文件,文件必须存在 |
"w" | 以写入方式打开文件,如果文件存在会清空,不存在则创建 |
"a" | 以追加方式打开文件,写入内容会添加到末尾 |
"rb" | 二进制读 |
"wb" | 二进制写 |
"ab" | 二进制追加 |
"r+" | 读写,文件必须存在 |
"w+" | 读写,文件存在会清空,不存在则创建 |
"a+" | 读写,追加模式 |
文件读写函数
| 函数 | 功能 |
|---|---|
fgetc(fp) | 从文件读取一个字符,返回 ASCII 或 EOF |
fputc(c, fp) | 向文件写入一个字符 |
fgets(str, n, fp) | 从文件读取一行,最多 n-1 个字符 |
fputs(str, fp) | 向文件写入字符串(不自动加 \n) |
fprintf(fp, format, ...) | 向文件写格式化内容 |
fscanf(fp, format, ...) | 从文件按格式读取内容 |
FILE *fp = fopen("file.txt", "r");
char ch = fgetc(fp); // 读一个字符
fclose(fp);