4206 字
11 分钟
C语言学习笔记
2025-12-24

C语言学习笔记#

该笔记持续更新,仅更新部分,如有错误请在评论区指正,谢谢🙏

首次更新2025-08-31,最后一次更新2025-12-29

常量和变量#

常量是在程序运行过程中 值不能改变 的数据;变量是在程序运行过程中 值可以改变 的存储空间。

  • 初始化变量<类型名称> <变量名称> = <初始值>
  • 类型名称:告诉编译器,这个变量可以存储什么类型的数据。
    • 例如:int 表示整数,float 表示单精度浮点数,char 表示字符等。
  • 变量名称:是程序中引用这个数据的“名字”,方便我们后续使用。
  • 定义变量的过程会为它 分配内存空间

📌 示例:

int age = 18; // 定义(声明)变量 age 并赋值 18

数据类型#

整数数据类型

整数数据类型大小(字节)*取值范围(典型 32 位系统)说明
short / short int2-32,768 ~ 32,767短整型
unsigned short20 ~ 65,535无符号短整型
int4-2,147,483,648 ~ 2,147,483,647默认整型
unsigned int40 ~ 4,294,967,295无符号整型
long / long int4(Win) / 8(Linux 64 位)取决于系统长整型
unsigned long同上同上无符号长整型
long long8约 ±9×10¹⁸长长整型
unsigned long long80 ~ 1.8×10¹⁹无符号长长整型
  • short int =shortint可忽略
  • 使用unsigned int 类型赋值时,输出时的整数占位符需要改为%u
  • 使用long 类型赋值时,初始值后需要加L ,输出时的整数占位符需要改为%ld
  • 使用long long 类型赋值时,初始值后需要加LL ,输出时的整数占位符需要改为%lld

小数数据类型(浮点型)

小数数据类型大小(字节)有效数字位数范围(约)说明
float46~7 位3.4E-38 ~ 3.4E38单精度浮点数
double815~16 位1.7E-308 ~ 1.7E308双精度浮点数
long double10/12/16(取决于编译器)>16 位范围更大扩展精度浮点数

💡 浮点数的输入和输出

类型scanfprintf
float%f%f
%e
(科学计数法)
double%lf%f
%e
(科学计数==0、
】)

浮点数的范围与精度

  • printf输出inf表示超过范围的浮点数:+∞
  • printf输出nan表示不存在的浮点数

5.56E+16-5.56E+16

在科学计数法里,E(或者 e)表示 “×10 的多少次幂”。

符号可以是-或+也可以省略(表示+)
%.2lf表示保留两位小数(以此类推)
使用float 类型赋值时,初始值后需要加Ff

字符类型

类型大小(字节)取值范围说明
char1-128 ~ 127(signed)或 0 ~ 255(unsigned)存储单个字符(ASCII 码)
signed char1-128 ~ 127有符号字符
unsigned char10 ~ 255无符号字符
/

1 字节(byte) = 8 位(bit)

定义一个长度为 4char 数组,名为 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 码\x41A
\ooo八进制 ASCII 码\101A

类型转换#

  • 隐式类型转换(自动转换):低精度 → 高精度
  • 显式类型转换(强制类型转换 ):
(新类型) 表达式
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,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循环执行流程:
    1. 执行初始化表达式。
    2. 判断条件表达式,为真 → 执行循环体。
    3. 执行更新表达式。
    4. 重复步骤 2~3,直到条件为假。

🔹 小技巧:

  • for 的三个部分可以省略,但分号不能省,例如 for(;;)死循环
  • for 循环常用来遍历数组、执行计数任务等。
  • 如果有固定次数,用for;必须执行一次,用do-while;其他用while

循环控制#

使用continentbreak来控制循环

素数的判断和输出#

  • 素数的判断
#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);

作用域规则#

局部变量#

  • 在某个函数或块的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的,即便在<font style="color:rgb(51, 51, 51);background-color:rgb(250, 252, 253);">main</font>函数内也为局部变量
#include <stdio.h>
int main ()
{
/* 局部变量声明 */
int a, b;
int c;
/* 实际初始化 */
a = 10;
b = 20;
c = a + b;
printf ("value of a = %d, b = %d and c = %d\n", a, b, c);
return 0;
}

局部变量和全局变量的名称可以相同,但是在函数内,如果两个名字相同,会使用局部变量值,全局变量不会被使用。

全局变量#

  • 全局变量是定义在函数外部,通常是在程序的顶部。全局变量在整个程序生命周期内都是有效的
#include <stdio.h>
/* 全局变量声明 */
int g = 20;
int main ()
{
/* 局部变量声明 */
int g = 10;
printf ("value of g = %d\n", g);
return 0;
}

初始化局部变量和全局变量#

  • 全局变量(以及 static 变量)会被系统自动初始化;局部变量不会,被初始化前使用是未定义行为。
数据类型初始化默认值
int0
char'\0'
float0
double0
pointerNULL

枚举#

每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增

enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};

数组#

一维数组#

定义数组(数组就是 一组相同类型的变量,在内存中连续存放,每个元素通过 下标(索引) 来访问

数据类型 数组名[元素个数];
  • 遍历数组 = 把数组里的每一个元素 从头到尾依次访问一次

读入一组整数,求平均数,然后输出比平均数大的数:

#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;
}

数组元素的计算

int a[5];
printf("%zu\n", sizeof(a) / sizeof(a[0])); // 输出 5

二维数组#

二维数组可以看作是 “数组的数组”

  • 一维数组:一条线 → 存放一组元素
  • 二维数组:一个表格(矩阵) → 存放“行 × 列”多个元素

例如:

int a[3][4];

这表示:

  • a 是一个二维数组
  • 它有 3 行,每行有 4 列
  • 一共存储 3 × 4 = 12int 元素

可以把它理解成一个 3 行 4 列的表格

指针#

指针变量#

指针就是一个变量,但它存储的不是普通的数值,而是 内存地址

&i 表示变量在内存中的位置(也就是内存地址)。

#include<stdio.h>
int main(){
int a =10;
int *p = &a;
printf("%d\n",*p);
return 0;
}
变量名存储内容
a10
&a0x7ffeefb4
p0x7ffeefb4(a 的地址(16进制)
*P10

要输出 指针(地址),应该用 %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 的作用:表示 不能修改

  1. 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; // ✅ 可以改变指针指向
  1. int *const p; 常量指针(const pointer)(指针 p 本身不能改(指向固定),但是能通过它修改指向的值。)
int a = 10, b = 20;
int *const p = &a;
*p = 30; // ✅ 可以改 a 的值
p = &b; // ❌ 错误,p 的指向不能改
  1. 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;
}

字符与字符串#

单字符的输入输出#

  1. **getchar**
  • 标准输入(通常是键盘) 读取 一个字符
  • 返回值是该字符的 ASCII 码(**int**** 类型),如果到达文件末尾或出错,返回 **EOF**(通常是 -1)**
#include <stdio.h>
int main() {
char c;
printf("请输入一个字符:");
c = getchar(); // 读取一个字符
printf("你输入的是:");
putchar(c); // 输出该字符
printf("\n");
return 0;
}
  1. **putchar**
  • 标准输出(通常是屏幕) 输出 一个字符
  • 返回输出的字符,如果出错返回 **EOF**
  1. 结合
#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() 每次取 缓冲区的一个字符 → 返回给 cputchar(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;
}

其他:

占位符类型用途
%luunsigned long输出无符号长整型
%zusize_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 默认规则 为例:

  1. 第一个成员从偏移量0开始放。
  2. 后续成员按其类型大小(或指定对齐值)的整数倍对齐。
  3. 整个结构体的总大小要是最大成员对齐数的整数倍

递归#

阶乘表示#

#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);
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

C语言学习笔记
https://blog.litkg.com/posts/c语言学习笔记/
作者
南栀
发布于
2025-12-24
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录