C语言基础
约 8026 字大约 27 分钟
2025-11-25
首先了解C语言的固定写法
#include <stdio.h>
int main() {
// printf("Hello, World!\n"); // 具体内容实现
return 0;
}一、数据类型与变量定义
在了解固定写法之后,就要知道如何去实现我们想实现的内容,第一步就是认识数据类型和变量定义
#include <stdio.h>
int main() {
// 1. 字符型(存单个字符,用单引号)
char gender = 'M'; // 占用1字节,本质存ASCII码
printf("字符型:%c(ASCII值:%d)\n", gender, gender); // 输出:M(ASCII值:77)
// 2. 整型(存整数)
int age = 20; // 常用,4字节,支持正负
unsigned int score = 95; // 无符号,只能存非负
long long big_num = 1234567890123; // 存超大整数
printf("整型:%d,无符号整型:%u,超长整型:%lld\n", age, score, big_num); // 输出对应数值
// 3. 浮点型(存小数)
float pi1 = 3.14f; // 单精度,加f区分double,有效数字6-7位
double pi2 = 3.1415926535; // 双精度,精度更高,默认小数是double
printf("单精度浮点:%f,双精度浮点:%lf\n", pi1, pi2); // 输出小数
return 0;
}二、运算符使用
第二步就是了解如何对变量进行运算,此处需注意一个地方,在计算机中0通常与false等价,而1(或者其他整数)与true等价。ps:此处是0或1做判断条件用时的情况,运算仍然是0和1
#include <stdio.h>
int main() {
int a = 10, b = 3;
// 1. 算术运算符(+、-、*、/、%)
printf("a+b=%d\n", a + b); // 13
printf("a-b=%d\n", a - b); // 7
printf("a*b=%d\n", a * b); // 30
printf("a/b=%d\n", a / b); // 3(整数除法,舍弃小数)
printf("a%%b=%d\n", a % b); // 1(取余,只能用于整数)
// 2. 赋值运算符(=、+=、-=)
a += 2; // 等价于 a = a + 2 → a=12
b -= 1; // 等价于 b = b - 1 → b=2
printf("a+=2后:%d,b-=1后:%d\n", a, b); // 12,2
// 3. 比较运算符(>、<、==、!=、>=、<=)
printf("a>b?%d\n", a > b); // 1(真为1)
printf("a==b?%d\n", a == b); // 0(假为0)
// 4. 逻辑运算符(&&与、||或、!非)
int c = 5, d = 0;
printf("c>3 && d>0?%d\n", c>3 && d>0); // 0(一个假则假)
printf("c>3 || d>0?%d\n", c>3 || d>0); // 1(一个真则真)
printf("!d?%d\n", !d); // 1(d是0→假,非假为真)
return 0;
}三、判断分支(if-else、switch)
通过不同的条件触发不同的处理内容
此处要注意的点在于if-else的匹配情况,如果代码中是if{} if{} if{},那么3个if{}都会判断;如果代码中是if{}else if{} else{} if{}那么前面一组if-else只会判断后走一个分支,后面的if{}一定会判断。
if{}可以单独出现,不是一定要搭配else if{}或else{}
1. if-else 语句
#include <stdio.h>
int main() {
int score = 85;
// 单if
if (score >= 60) {
printf("成绩合格\n"); // 条件成立,执行这里
}
// if-else
if (score >= 90) {
printf("优秀\n"); // 条件不成立,不执行
} else if (score >= 80) {
printf("良好\n"); // 条件成立,执行这里
} else {
printf("一般\n"); // 前面都不成立才执行
}
return 0;
}2. switch 语句(适合多条件匹配)
可以这么认为if-else适合范围匹配,switch适合精准匹配
在实际运用中,往往会使用if-else,switch看情况使用
#include <stdio.h>
int main() {
char grade = 'B';
switch (grade) {
case 'A': // 匹配grade='A'
printf("90-100分\n");
break; // 跳出switch,必须加,否则往下执行
case 'B': // 匹配grade='B'
printf("80-89分\n"); // 执行这里
break;
case 'C':
printf("70-79分\n");
break;
default: // 所有case都不匹配时执行
printf("不及格\n");
}
return 0;
}四、循环(for、while、do-while)
循环可以认为是计算机的核心
补充i++和++i的区别
i++ 和 ++i 都是 “自增 1”,但核心区别是 “先使用变量值,再自增” 还是 “先自增,再使用变量值”—— 用 “先做事还是先拿结果” 的比喻就能轻松记住。
#include <stdio.h>
int main() {
// 示例1:单独使用(无区别)
int a = 5;
++a; // 先自增→a=6
printf("++a 单独使用后:a=%d\n", a); // 输出6
int b = 5;
b++; // 后自增→b=6
printf("b++ 单独使用后:b=%d\n", b); // 输出6
// 结论:单独一行写++i或i++,效果完全一样(都是变量+1)
// 示例2:参与赋值(核心区别!)
int c = 5;
int res1 = ++c; // 前置自增:先c+1(c=6),再把6赋值给res1
printf("res1=++c → res1=%d, c=%d\n", res1, c); // 输出res1=6, c=6
int d = 5;
int res2 = d++; // 后置自增:先把d当前值5赋值给res2,再d+1(d=6)
printf("res2=d++ → res2=%d, d=%d\n", res2, d); // 输出res2=5, d=6
// 示例3:参与运算(更能体现差异)
int x = 3, y = 3;
int sum1 = ++x + 2; // 先x+1=4,再4+2=6
int sum2 = y++ + 2; // 先3+2=5,再y+1=4
printf("++x+2 = %d, x=%d\n", sum1, x); // 输出6, x=4
printf("y+++2 = %d, y=%d\n", sum2, y); // 输出5, y=4
// 示例4:循环中使用(常用场景)
printf("\nfor循环中++i:");
for (int i=0; i<3; ++i) { // 前置自增:先i+1,再判断条件
printf("%d ", i); // 输出1 2 3?不!看注释↓
// 实际执行流程:
// 第1次:i=0→判断0<3成立→执行循环体(打印0)→i++(i=1)
// 第2次:i=1→判断1<3成立→执行循环体(打印1)→i++(i=2)
// 第3次:i=2→判断2<3成立→执行循环体(打印2)→i++(i=3)
// 第4次:i=3→判断3<3不成立→循环结束
}
// 输出:0 1 2(循环中++i和i++效果一样,因为循环体执行后才会自增)
printf("\nfor循环中i++:");
for (int j=0; j<3; j++) { // 后置自增:先判断条件,再执行循环体,最后j++
printf("%d ", j);
}
// 输出:0 1 2(和++i结果相同)
return 0;
}1. for 循环(适合已知循环次数)
像 “设定次数的重复任务”,比如 “绕操场跑 10 圈”
#include <stdio.h>
int main() {
// 循环10次:i从0到9(初始化→判断条件→执行→更新变量)
for (int i = 0; i < 10; i++) { // 1. 起点:i=0;2. 条件:i<10(没到10圈);4. 更新:i++(跑完1圈+1)
printf("for循环第%d次\n", i + 1); // 3. 执行:输出1到10(跑第n圈)
}
return 0;
}2. while 循环(适合未知循环次数,满足某些条件时结束)
像 “不确定次数的重复任务”,比如 “直到下雨才停止散步”
#include <stdio.h>
int main() {
int i = 1;
// 先判断条件(是否下雨),成立才执行(继续散步)
while (i <= 5) { // 条件:i≤5(没到5次)
printf("while循环第%d次\n", i); // 执行:输出1到5
i++; // 必须更新变量(否则一直“不下雨”,死循环)
}
return 0;
}3. do-while 循环(至少执行一次,循环后判断)
像 “先做后判断”,比如 “先吃一口饭,再看要不要继续吃”
#include <stdio.h>
int main() {
int i = 1;
// 先执行1次(吃一口),再判断条件(要不要继续吃)
do {
printf("do-while循环第%d次\n", i); // 输出1到5
i++;
} while (i <= 5); // 条件成立,继续执行
// 即使条件一开始不成立,也会执行1次
int j = 6;
do {
printf("do-while至少执行1次:%d\n", j); // 会输出6
} while (j <= 5);
return 0;
}五、函数(封装代码,复用)
函数是重要概念,简单来说他将可能会重复的代码从主程序中抽离出来,每当设计相关操作时调用函数执行,而不是通过复制粘贴实现。可以使代码更简洁,效率更高。
封装代码,像 “工具盒”,复用不重复写
固定写法中的int main(){return 0}其实就是一个名为main函数,不传入参数,返回int类型的值
#include <stdio.h>
// 函数定义:求两个数的和(工具:加法器)
// 返回值类型 函数名(参数列表):像“工具名称+输入参数+输出结果”
int add(int x, int y) { // x、y是形参(工具的“输入接口”)
return x + y; // 返回计算结果(工具的“输出”)
}
// 无返回值函数(void):像“工具只做事,不返回结果”(比如打印机)
void print_hello() {
printf("Hello, 函数!\n"); // 只打印,不返回值
}
int main() {
print_hello(); // 函数调用:用“打印机”,无参数无返回值
int a = 5, b = 3;
int result = add(a, b); // 调用“加法器”,a、b是实参(实际输入的数)
printf("%d + %d = %d\n", a, b, result); // 输出:5+3=8(工具输出结果)
return 0;
}函数需要定义在main函数之前,但也可以通过在main函数前先声明的方式实现
#include <stdio.h>
int add(int x, int y);
void print_hello();
int main() {
print_hello(); // 函数调用:用“打印机”,无参数无返回值
int a = 5, b = 3;
int result = add(a, b); // 调用“加法器”,a、b是实参(实际输入的数)
printf("%d + %d = %d\n", a, b, result); // 输出:5+3=8(工具输出结果)
return 0;
}
int add(int x, int y) { // x、y是形参(工具的“输入接口”)
return x + y; // 返回计算结果(工具的“输出”)
}
// 无返回值函数(void):像“工具只做事,不返回结果”(比如打印机)
void print_hello() {
printf("Hello, 函数!\n"); // 只打印,不返回值
}六、指针(指向变量的内存地址,解决 “跨范围访问” 核心问题)
1. 指针的背景:内存是 “编号的储物格”
程序运行时,所有变量都会存放在内存中 —— 内存就像一排有唯一编号的储物格(编号 = 内存地址),每个变量根据数据类型占用 1 个或多个连续储物格。
举个生动例子:
假设内存是 10 个连续储物格,编号 0~9(实际内存地址是十六进制,比如 0x7ffee4b7e7ac)
定义
int a=10; char b='A'; double c=3.14;a是 int 型(4 字节),占用储物格 0~3,地址记为&a=0b是 char 型(1 字节),占用储物格 4,地址&b=4c是 double 型(8 字节),占用储物格 5~12(超出假设的 10 格,仅为示意)
变量名是给程序员看的 “别名”,计算机底层只认 “储物格编号(地址)”
2. 为什么需要指针?解决 “函数调用不能修改外部变量” 的痛点
C 语言中函数调用有个默认规则:普通参数传递是 “值拷贝”—— 函数内部改的是拷贝版,外部原变量不变。
比如想写一个函数交换两个变量的值,直接传变量会失败:
#include <stdio.h>
// 尝试交换a和b,但实际是“值拷贝”
void swap_fail(int x, int y) {
int temp = x;
x = y; // 这里改的是x(a的拷贝),不是原变量a
y = temp; // 改的是y(b的拷贝),不是原变量b
}
int main() {
int a = 5, b = 10;
swap_fail(a, b); // 传的是a和b的“副本”
printf("a=%d, b=%d\n", a, b); // 输出a=5, b=10(原变量没变化!)
return 0;
}问题核心:函数内部不知道外部变量的 “储物格编号(地址)”,只能操作拷贝来的值。
而指针的核心作用就是:传递变量的 “地址”,让函数能直接找到外部变量的 “储物格”,修改原变量的值—— 这就是指针存在的核心意义。
3. 指针的定义与使用(像 “储物格编号记录本”)
建议先看下文的 [4](#4.scanf 要用 & a 而不是 a?) 和 总结 部分,最后回看本部分
ps!!! int *p实际上是int* p,变量是p,不是*p
#include <stdio.h>
// 用指针交换:传递地址,直接修改原变量
void swap_success(int *x, int *y) { // x、y是指针,存a、b的地址
int temp = *x; // *x:通过地址找到a的储物格,取里面的值(5)
*x = *y; // 把b的值(10)放进a的储物格
*y = temp; // 把temp的值(5)放进b的储物格
}
int main() {
int num = 10; // 变量num,占用某个储物格(地址假设为0x1234)
int *p = # // 定义指针p(“编号记录本”),&num是“取地址”:抄下num的储物格编号
// 指针的3种核心用法
printf("num的值:%d\n", num); // 直接用变量名访问:5(直接喊别名找储物格)
printf("num的地址:%p\n", &num); // 取地址:输出0x1234(看储物格编号)
printf("指针p存的地址:%p\n", p); // 指针存地址:和&num一样(记录本上的编号)
printf("指针p指向的值:%d\n", *p); // 解引用:通过编号找储物格,取里面的值(10)
// 用指针修改原变量:像“按编号找到储物格,换里面的东西”
*p = 20; // 等价于num=20(直接修改储物格内容)
printf("修改后num的值:%d\n", num); // 输出20
// 用指针解决函数修改外部变量的问题
int a=5, b=10;
swap_success(&a, &b); // 传a和b的地址(储物格编号)
printf("交换后a=%d, b=%d\n", a, b); // 输出a=10, b=5(原变量真的变了!)
return 0;
}4.scanf 要用 & a 而不是 a?
scanf的作用是 “把用户输入的值,放进变量 a 的储物格”—— 所以scanf必须知道 a 的 “储物格编号(地址)”,才能准确把值放进去。
- 用
&a:&是取地址符,传递的是 a 的 “储物格编号”,scanf能直接找到 a 的位置,把输入值存进去(正确)。 - 用
a:传递的是 a 的 “当前值”(比如 5),scanf拿到的是 “5” 这个数字,根本不知道要放进哪个储物格(错误,甚至会导致程序崩溃)。
#include <stdio.h>
int main() {
int a;
printf("请输入一个整数:");
scanf("%d", &a); // 正确:传a的地址,scanf知道把值存到哪
// scanf("%d", a); // 错误:传a的值(此时a是随机垃圾值),scanf找不到存储位置
printf("你输入的是:%d\n", a); // 正确输出输入值
return 0;
}总结指针核心
- 本质:存 “内存地址” 的变量(像储物格编号记录本)
- 作用:解决 “函数不能修改外部变量” 的问题,让代码能跨范围操作数据
- 关键符号:
&(取地址)、*(解引用,通过地址访问变量)
// 简单记忆
int *p;int a; //定义指针变量
p就是地址--相当于&a,*p就是具体变量值--相当于a
所以要改变 指针指向的变量的值 是用 *p=10;而不是p=10ps!!! int *p实际上是int* p,变量是p,不是*p
#include <stdio.h>
int main() {
int a = 5; // 变量a:储物格A,内容=5,地址&a=0x1234(假设)
int *p = &a; // 指针p:编号本,记录的地址=0x1234(p的值=&a)
// 1. 改变“p指向的变量a的值”(目标:把储物格A的内容改成10)
*p = 10; // 正确!通过编号本找到储物格A,修改里面的内容
printf("a=%d, *p=%d\n", a, *p); // 输出a=10, *p=10(储物格内容变了)
printf("p=%p, &a=%p\n", p, &a); // 输出p=0x1234, &a=0x1234(编号本记录的地址没改)
// 2. 试图改变“指针变量p本身的值”(目标:让编号本记录新的地址)
int b = 20; // 新变量b:储物格B,内容=20,地址&b=0x5678(假设)
p = &b; // 正确!把b的地址(0x5678)赋值给p,编号本现在记录B的编号
printf("p=%p, &b=%p\n", p, &b); // 输出p=0x5678, &b=0x5678(p的地址值变了)
printf("*p=%d, b=%d\n", *p, b); // 输出*p=20, b=20(现在指向B的内容)
// 3. 错误示例:p=10(不要这么写!)
// p=10; // 把“数字10”当成地址赋值给p,系统不允许访问地址10,程序会崩溃
return 0;
}指针进阶(指针变量、变量指针、指针函数、函数指针)
| 概念 | 精准定义 | 语法关键区分 | 核心特征 / 语法格式 |
|---|---|---|---|
| 指针变量 | 本质是 “变量”,专门用于存储内存地址(可指向普通变量、数组、函数等) | 无括号,如int* p; | 语法:类型* 变量名;(如int* p;) |
| 变量指针 | 本质是 “指针”,专门指向普通变量(而非数组、函数等)的地址 | 无括号,如int* p = &a; | 是 “指针变量” 的子集(强调指向 “变量”),语法同指针变量 |
| 指针函数 | 本质是 “函数”,返回值类型为指针(内存地址) | 无括号(*属于返回值类型),如int* func(); | 语法:返回值类型* 函数名(参数列表);(如int* func(int a);) |
| 函数指针 | 本质是 “指针”,专门指向函数的内存地址(通过地址可调用对应函数) | 有括号(*属于指针名),如int (*p)(); | 语法:返回值类型 (*指针名)(参数类型列表);(如int (*p)(int a);) |
1. 指针变量 vs 变量指针(同一概念的两种说法,像 “水杯” 和 “装水的杯子”)
#include <stdio.h>
int main() {
// 指针变量:本质是“变量”,只是存的是“地址”(像“门牌号记录本”)
int num = 10;
int *p = # // p是“指针变量”,指向int型变量num
// 变量指针:本质是“指针”,指向一个“变量”(像“指向房子的门牌号”)
// 上面的p也是“变量指针”(指向变量num),和“指针变量”是同一个东西的不同描述
printf("指针变量p存的地址:%p\n", p); // 变量num的地址
printf("变量指针p指向的值:%d\n", *p); // 变量num的值10
return 0;
}总结:指针变量 = 变量指针,都是 “存地址的变量”,只是叫法角度不同(前者强调 “变量属性”,后者强调 “指向对象”)。
2. 指针函数(返回值是指针的函数,像 “工具做完事,返回一个门牌号”)
#include <stdio.h>
// 指针函数:返回int*类型(int型变量的地址)
int* find_max(int arr[], int len) {
int *max_ptr = &arr[0]; // 指针指向数组第一个元素
for (int i = 1; i < len; i++) {
if (arr[i] > *max_ptr) { // 找到更大的元素
max_ptr = &arr[i]; // 指针指向这个更大元素的地址
}
}
return max_ptr; // 返回“最大值所在的门牌号”
}
int main() {
int scores[] = {85, 92, 78, 95, 88};
int len = 5;
// 调用指针函数,得到最大值的地址
int *max_addr = find_max(scores, len);
printf("数组中的最大值:%d\n", *max_addr); // 95(通过地址找到最大值)
return 0;
}语法格式:返回值类型* 函数名(参数列表),核心是 “返回一个地址”。
3. 函数指针(指向函数的指针,像 “工具的说明书地址”,通过地址调用工具)
#include <stdio.h>
// 普通函数1:加法
int add(int a, int b) {
return a + b;
}
// 普通函数2:减法
int sub(int a, int b) {
return a - b;
}
int main() {
// 定义函数指针:指向“参数为两个int,返回值为int”的函数
// 格式:返回值类型 (*指针名)(参数类型1, 参数类型2)
int (*func_ptr)(int, int);
// 函数名本质是“函数的地址”(像“工具的说明书地址”)
func_ptr = add; // 让指针指向add函数
int sum = func_ptr(5, 3); // 通过指针调用add函数,等价于add(5,3)
printf("5+3=%d\n", sum); // 8
func_ptr = sub; // 让指针指向sub函数(切换工具)
int diff = func_ptr(5, 3); // 通过指针调用sub函数
printf("5-3=%d\n", diff); // 2
return 0;
}生动比喻:函数是 “工具”,函数地址是 “工具说明书的存放地址”,函数指针是 “记录说明书地址的本子”—— 拿着本子找到说明书,就能用对应的工具。语法要点:(*指针名)的括号不能少,否则会变成 “指针函数”(优先级问题)。
七、结构体(自定义复合类型)
结构体就是自己定义的数据类型
#include <stdio.h>
// 1. 定义结构体类型(相当于创建一个新的"数据模板")
struct Student {
char name[20]; // 姓名(字符串)
int age; // 年龄
float score; // 成绩
};
int main() {
// 2. 定义结构体变量并赋值
struct Student stu1 = {"张三", 18, 92.5};
// 3. 访问结构体成员(用.运算符)
printf("姓名:%s\n", stu1.name);
printf("年龄:%d\n", stu1.age);
printf("成绩:%.1f\n", stu1.score);
// 4. 用指针访问结构体成员(用->运算符)
struct Student *p = &stu1;
printf("\n通过指针访问:\n");
printf("姓名:%s\n", p->name); // 等价于 (*p).name
printf("年龄:%d\n", p->age);
printf("成绩:%.1f\n", p->score);
return 0;
}八、数组(同一类型数据的 “连续收纳盒”)
在c语言中,数组下标是从0开始的
#include <stdio.h>
int main() {
// 1. 定义数组:像“一排相同的收纳盒”,装同一类型的数据
// 格式:类型 数组名[长度] = {元素1, 元素2, ...};
int scores[5] = {85, 92, 78, 95, 88}; // 5个int型收纳盒,分别装5个成绩
char fruits[4] = {'苹', '果', '香', '蕉'}; // 4个char型收纳盒,装字符
// 2. 访问数组元素:用“索引”(收纳盒编号,从0开始!)
printf("第1个成绩:%d\n", scores[0]); // 85(编号0是第一个盒子)
printf("第3个成绩:%d\n", scores[2]); // 78(编号2是第三个盒子)
printf("第2个水果:%c\n", fruits[1]); // 果(编号1是第二个盒子)
// 3. 遍历数组:用循环“逐个打开收纳盒”
printf("\n所有成绩:");
for (int i = 0; i < 5; i++) { // i从0到4(5个盒子)
printf("%d ", scores[i]); // 依次输出每个盒子里的成绩
}
printf("\n");
// 4. 数组的地址特性:数组名本质是“第一个元素的地址”(第一个收纳盒的门牌号)
printf("数组名的地址:%p\n", scores); // 等价于&scores[0]
printf("第一个元素的地址:%p\n", &scores[0]); // 和数组名地址相同
return 0;
}九、字符串(字符数组的 “特殊形式”)
#include <stdio.h>
#include <string.h> // 字符串专用函数库(必须包含)
int main() {
// 1. 定义字符串:两种方式(本质是“以'\0'结尾的字符数组”,'\0'是“结束标记”)
char name1[] = "张三"; // 自动加'\0',像“珠子串完自动加个收尾”
char name2[10] = "李四"; // 指定长度,剩余空间自动补'\0'
// 2. 输出字符串:用%s(直接打印整个“珠子串”)
printf("姓名1:%s\n", name1); // 输出:张三
printf("姓名2:%s\n", name2); // 输出:李四
// 3. 字符串常用函数(像“字符串专用工具”)
// strlen:求字符串长度(统计“珠子数量”,不算'\0')
printf("name1的长度:%zu\n", strlen(name1)); // 2(“张三”是2个字符)
// strcpy:字符串复制(把一个串复制到另一个串)
char dest[20];
strcpy(dest, "Hello 字符串!"); // 把右边的串复制到dest
printf("复制后:%s\n", dest); // 输出:Hello 字符串!
// strcat:字符串拼接(把两个串连起来)
strcat(dest, " 你好呀~"); // 给dest后面加字符串
printf("拼接后:%s\n", dest); // 输出:Hello 字符串! 你好呀~
// strcmp:字符串比较(比较两个串是否相同,返回0则相同)
char str1[] = "abc", str2[] = "abc";
printf("str1和str2是否相同?%d\n", strcmp(str1, str2)); // 0(相同)
return 0;
}相关字符串操作的处理函数,加深对字符串的操作理解
strcpy(字符串复制)
#include <stdio.h>
// 手动实现strcpy:把src复制到dest(dest必须有足够空间)
char* my_strcpy(char *dest, const char *src) {
char *dest_start = dest; // 保存dest的起始地址(最后要返回)
// 遍历src,逐个复制字符到dest,直到src的'\0'
while ((*dest = *src) != '\0') { // 先赋值,再判断是否是'\0'
dest++; // dest指针向后移一位
src++; // src指针向后移一位
}
return dest_start; // 返回dest的起始地址(方便链式调用)
}
int main() {
char dest[20]; // 目标数组:必须预留足够空间(至少能装下src+'\0')
char src1[] = "Hello World";
char src2[] = "C语言真有趣";
// 复制src1到dest
my_strcpy(dest, src1);
printf("复制后dest:%s\n", dest); // 输出Hello World
// 复制src2到dest(覆盖原有内容)
my_strcpy(dest, src2);
printf("覆盖后dest:%s\n", dest); // 输出C语言真有趣
return 0;
}strcat(字符串拼接)
#include <stdio.h>
// 先复用my_strlen(求长度)
int my_strlen(const char *str) {
int len = 0;
while (str[len] != '\0') len++;
return len;
}
// 手动实现strcat:把src拼接到dest末尾
char* my_strcat(char *dest, const char *src) {
char *dest_start = dest;
// 1. 找到dest的末尾('\0'的位置)
dest += my_strlen(dest); // dest指针移到'\0'处
// 2. 把src的字符逐个复制到dest末尾(和strcpy逻辑一样)
while ((*dest = *src) != '\0') {
dest++;
src++;
}
return dest_start;
}
int main() {
char dest[50] = "我喜欢"; // dest初始有内容
char src1[] = "编程";
char src2[] = ",也喜欢C语言!";
// 拼接src1到dest:"我喜欢" + "编程" → "我喜欢编程"
my_strcat(dest, src1);
printf("拼接1次:%s\n", dest);
// 再拼接src2:"我喜欢编程" + ",也喜欢C语言!" → 最终结果
my_strcat(dest, src2);
printf("拼接2次:%s\n", dest);
return 0;
}strcmp(字符串比较)
手动实现strcmp:比较str1和str2,返回值规则: 0→相等;正数→str1大;负数→str2大
#include <stdio.h>
// 手动实现strcmp:比较str1和str2,返回值规则:
// 0→相等;正数→str1大;负数→str2大
int my_strcmp(const char *str1, const char *str2) {
// 逐字符对比,直到遇到不同字符或'\0'
while (*str1 != '\0' && *str2 != '\0') {
if (*str1 != *str2) {
return *str1 - *str2; // 不同则返回ASCII差值
}
str1++; // 指针向后移
str2++;
}
// 若前面字符都相同,比较是否同时到'\0'(比如"abc"和"abcd")
return *str1 - *str2;
}
int main() {
char str1[] = "abc";
char str2[] = "abc";
char str3[] = "abd";
char str4[] = "ab";
char str5[] = "ABC"; // 大写A的ASCII=65,小写a=97
printf("\"abc\" vs \"abc\":%d\n", my_strcmp(str1, str2)); // 0(相等)
printf("\"abc\" vs \"abd\":%d\n", my_strcmp(str1, str3)); // -1(c=99 < d=100)
printf("\"abc\" vs \"ab\":%d\n", my_strcmp(str1, str4)); // 99(str4先到'\0',str1剩'c'=99)
printf("\"abc\" vs \"ABC\":%d\n", my_strcmp(str1, str5)); // 32(a=97 - A=65=32)
return 0;
}十、输入
#include <stdio.h>
#include <string.h>
// 函数声明(用于“函数相关”输入场景)
void inputAndPrintInt(int *num); // 接收整型输入并打印
void inputAndPrintStruct(struct Student *s); // 接收结构体输入并打印
// 结构体定义(用于“结构体相关”输入场景)
struct Student {
char name[20]; // 姓名
int age; // 年龄
float score; // 成绩
};
int main() {
// ======================================
// 1. 整型相关输入(int、多组整型、无符号整型)
// ======================================
printf("===== 1. 整型相关输入 =====\n");
int a, b;
unsigned int u_num; // 无符号整型
// 单个/多个整型输入
printf("输入2个整数(空格分隔):");
scanf("%d %d", &a, &b);
printf("输出:a=%d, b=%d, a+b=%d\n", a, b, a+b);
// 无符号整型输入
printf("输入无符号整数(非负):");
scanf("%u", &u_num);
printf("输出:无符号整数=%u\n", u_num);
// 多组整型输入(Ctrl+Z/C 结束)
printf("输入多组整数(空格分隔,Ctrl+Z/C 结束):");
while (scanf("%d %d", &a, &b) == 2) {
printf("当前组:%d * %d = %d\n", a, b, a*b);
}
getchar(); // 吸收缓冲区换行符
printf("\n");
// ======================================
// 2. 浮点型相关输入(float、double、多组浮点)
// ======================================
printf("===== 2. 浮点型相关输入 =====\n");
float f;
double db1, db2; // 双精度浮点型
// float 输入(格式符 %f)
printf("输入单精度浮点数(float):");
scanf("%f", &f);
printf("输出:float=%.2f\n", f);
// double 输入(格式符 %lf,输出可用 %f/%lf)
printf("输入2个双精度浮点数(double,空格分隔):");
scanf("%lf %lf", &db1, &db2);
printf("输出:double1=%.4f, double2=%.4f, 平均值=%.4f\n", db1, db2, (db1+db2)/2);
// 多组双精度输入
printf("输入多组双精度数(空格分隔,Ctrl+Z/C 结束):");
while (scanf("%lf %lf", &db1, &db2) == 2) {
printf("当前组:%.2f / %.2f = %.2f\n", db1, db2, db1/db2);
}
getchar(); // 吸收换行符
printf("\n");
// ======================================
// 3. 字符相关输入(单个字符、连续字符)
// ======================================
printf("===== 3. 字符相关输入 =====\n");
char ch1, ch2;
// 单个字符输入(getchar(),可读取空格/换行)
printf("输入单个字符(含空格):");
ch1 = getchar();
printf("输出:%c(ASCII码=%d)\n", ch1, ch1);
getchar(); // 吸收上一次的换行符
// 多个字符输入(scanf %c,需注意缓冲区)
printf("输入2个字符(无分隔符):");
scanf("%c%c", &ch1, &ch2); // 连续输入,无空格
printf("输出:ch1=%c, ch2=%c\n", ch1, ch2);
getchar(); // 吸收换行符
printf("\n");
// ======================================
// 4. 数组相关输入(整型数组、浮点型数组)
// ======================================
printf("===== 4. 数组相关输入 =====\n");
int int_arr[5]; // 整型数组(5个元素)
float float_arr[3];// 浮点型数组(3个元素)
int i;
// 整型数组输入(循环+scanf)
printf("输入5个整数(空格分隔):");
for (i = 0; i < 5; i++) {
scanf("%d", &int_arr[i]); // 数组元素需传地址
}
printf("整型数组输出:");
for (i = 0; i < 5; i++) {
printf("%d ", int_arr[i]);
}
printf("\n");
// 浮点型数组输入
printf("输入3个浮点数(空格分隔):");
for (i = 0; i < 3; i++) {
scanf("%f", &float_arr[i]);
}
printf("浮点型数组输出:");
for (i = 0; i < 3; i++) {
printf("%.2f ", float_arr[i]);
}
printf("\n\n");
// ======================================
// 5. 字符串相关输入(无空格、含空格、字符串数组)
// ======================================
printf("===== 5. 字符串相关输入 =====\n");
char str1[20]; // 单个字符串(无空格)
char str2[30]; // 单个字符串(含空格)
char str_arr[3][20];// 字符串数组(3个字符串)
// 无空格字符串输入(scanf %s)
printf("输入无空格字符串:");
scanf("%s", str1); // 字符串数组名无需&(本身是地址)
printf("输出:%s\n", str1);
getchar(); // 吸收换行符
// 含空格字符串输入(fgets,推荐)
printf("输入含空格字符串:");
fgets(str2, sizeof(str2), stdin);
str2[strcspn(str2, "\n")] = '\0'; // 去除换行符
printf("输出:%s\n", str2);
// 字符串数组输入(循环+fgets)
printf("输入3个含空格字符串(每行1个):\n");
for (i = 0; i < 3; i++) {
printf("第%d个:", i+1);
fgets(str_arr[i], sizeof(str_arr[i]), stdin);
str_arr[i][strcspn(str_arr[i], "\n")] = '\0';
}
printf("字符串数组输出:\n");
for (i = 0; i < 3; i++) {
printf("str_arr[%d] = %s\n", i, str_arr[i]);
}
printf("\n");
// ======================================
// 6. 指针相关输入(通过指针间接输入变量/数组)
// ======================================
printf("===== 6. 指针相关输入 =====\n");
int num = 0;
int arr[4];
int *p_num = # // 指向整型变量的指针
int *p_arr = arr; // 指向数组的指针(数组名=首元素地址)
// 指针指向变量:通过指针输入变量值
printf("通过指针输入整数:");
scanf("%d", p_num); // 指针本身存地址,无需&
printf("输出:num=%d(指针访问:*p_num=%d)\n", num, *p_num);
// 指针指向数组:通过指针输入数组元素
printf("通过指针输入4个整数(空格分隔):");
for (i = 0; i < 4; i++) {
scanf("%d", p_arr + i); // 指针偏移访问数组元素
}
printf("数组输出(指针遍历):");
for (i = 0; i < 4; i++) {
printf("%d ", *(p_arr + i));
}
getchar(); // 吸收换行符
printf("\n\n");
// ======================================
// 7. 函数相关输入(通过函数参数输入数据)
// ======================================
printf("===== 7. 函数相关输入 =====\n");
int func_num;
// 调用函数:通过指针参数传入数据(函数内完成输入)
inputAndPrintInt(&func_num); // 传变量地址给函数
printf("主函数中:func_num=%d\n", func_num);
printf("\n");
// ======================================
// 8. 结构体相关输入(单个结构体、结构体数组)
// ======================================
printf("===== 8. 结构体相关输入 =====\n");
struct Student stu1; // 单个结构体
struct Student stu_arr[2]; // 结构体数组(2个学生)
// 单个结构体输入
printf("输入学生信息(姓名 年龄 成绩):");
scanf("%s %d %f", stu1.name, &stu1.age, &stu1.score); // 字符数组无需&
printf("单个结构体输出:\n姓名:%s,年龄:%d,成绩:%.2f\n",
stu1.name, stu1.age, stu1.score);
getchar(); // 吸收换行符
// 结构体数组输入
printf("输入2个学生信息(每行:姓名 年龄 成绩):\n");
for (i = 0; i < 2; i++) {
printf("第%d个学生:", i+1);
fgets(stu_arr[i].name, sizeof(stu_arr[i].name), stdin);
stu_arr[i].name[strcspn(stu_arr[i].name, "\n")] = '\0'; // 读取姓名(含空格)
scanf("%d %f", &stu_arr[i].age, &stu_arr[i].score);
getchar(); // 吸收换行符
}
printf("结构体数组输出:\n");
for (i = 0; i < 2; i++) {
printf("学生%d:姓名=%s,年龄=%d,成绩=%.2f\n",
i+1, stu_arr[i].name, stu_arr[i].age, stu_arr[i].score);
}
return 0;
}
// 函数实现:接收整型输入并打印(通过指针修改主函数变量)
void inputAndPrintInt(int *num) {
printf("函数内输入整数:");
scanf("%d", num); // num是指针,存主函数变量地址
printf("函数内输出:%d\n", *num);
}