注:本文是基于Linux操作系统下运行的程序
文章目录
- C语言学习第一天
- C语言学习第二天
- C语言学习第三天
- C语言学习第四天
- C语言学习第五天
- C语言学习第六天
- C语言学习第七天
- C语言学习第八天
- 一、文本文件读写
- 1. int fgetc(FILE *stream);
- 2. int fputc(int c, FILE *stream);
- 3. char *fgets(char *s, int size, FILE *stream);
- 4.int fputs(const char *s, FILE *stream);
- 5. 在文件内部,有一个专门处理光标位置的指针,文件偏移指针
- 6.int fseek(FILE *stream, long offset, int whence);
- 7. 函数原型:long ftell(FILE *stream);
- 8.void rewind(FILE *stream);
- 9.int feof(FILE *stream);
- 10. size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- 11.size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
- 13. int fprintf(FILE *stream, const char *format, ...); (自学) 例如:fprintf(fp,"%d %d",a,b);
C语言学习第一天
一、写第一个c语言
1.创建c语言流程
a.打开终端,创建.c文件
b编辑.c文件
vi +文件名.c ; gedit +文件名.c
c.写c语言代码
d. 编译 gcc xxx.c -o 可执行程序名字
e. ./可执行程序名 默认是a.out
2.解释c语言代码
#include <stdio.h> 预处理:头文件-包含函数定义。
//第一个c语言程序 注释–给人看,编译器当做空白,多行注释 /xx/
int main() 入口函数(main函数)–程序执行的入口
{
int i = 0; 定义变量----定义一个i变量
printf(“hello huqing\n”); 打印–屏幕上照常输出
return 0; 返回值: 返回给main的外部
}
二、常量
1.常量:不可以改变的量,不可以修改的量 (建筑物高度,地球直径)
一般时候,常量是给变量赋值使用。
2.整形常量: -1 , 0 ,100 所有的值都是整数
表示方式:10进制-满10进1, 16进制满16进1,用0到9+ABCDEF组成,加前缀0x或者H后缀。2进制常量只有1和0,8进制1到7表示,一般用0的前缀
3.浮点型常量:表示小数 -1.2, 1.0, 12.56 ,在内存中存储方式特殊。有误差。浮点型和0比较 float x =0; 不可以 x == 0
4.字符型常量: ‘a’, ‘1’, ‘\0’, ascii表
5.指针常量:指向某一个位置的常量(以后重点)
6.字符串常量: 多个字符用双引号串一起 “abc”
三、进制计算
1.进制关系
十进制: 用0到9表示,满10进1,9+1=10,99+1=100 ,100-1 =99
二进制:用0和1表示:满2进1,借1当2. 10 -1 = 1; 1 + 1 = 10
十六进制:用0到9和ABCDEF表示,满16进1, 9 +1=0XA F+1=0X10
八进制:用0到7表示,满8进1,借1当8 ;7+1=10;7-1=6 ;77+1=100
2.二进制计算
移位运算:算数左移 < ,把左边第一个删除,右侧添加0
10010010 < 1 = 00100100
10010010 < 8 = 0
10010010 < 3 = 10010000
算数右移: > ,把右侧删除一个,左侧添加0
10010010 > 3 = 00010010
与: & ,相同位上都是1则结果为1,否则为0. (有0则0)
10010 & 1011 00010010
& 00001011
------------------
00000010
或: | ,相同位上有1则结果位1,否则为0,(有1则1)案例: 10010 | 1011 = 11011
取反:~,如果是1则取反的结果位0,如果是0则取反的结果位1,案例 ~100110 = 011001
按位异或: ^ ,同1则1,同0则0,不同则0;案例:110^101 = 100
3.进制转换
十六进制和二进制转换
2进制转16进制:二进制的4个位,表示16进制中的1位。 案例:1000 1001 = 0x89
案例: 11011 --> 0001 1011 =0x1B
16进制转2进制:一个16进制位表示4个2进制位 案例: 0xA0B = 1010 0000 1011
十六进制和10进制转换:
10进制 0~9 10 11 12 13 14 15 16
16进制 0~9 A B C D E F 0X10
8进制和2进制的计算
2进制转8进制,3个2进制位转换成1个8进制位
011 011 010 --> 332
8进制转2进制,1个8进制位转换成3个二进制位
7024 = 111 000 010 100
10进制转换成2进制,把10进制短除法2后直到商0后得到的余数从下往上写就是2进制
2进制转10进制,用1 2 4 8 16 1101011 = 64+32 + 8+ 2+1 = 107
四、变量:可以变的量,可以修改的量(64为系统)
1、整形是 int ,
表示整数如-20, 0, 1。 总共是4字节可以用sizeof(int)打印,表示的数值范围是-21亿~+21亿
短整形 short ,表示短整数。 总共是2字节16位,表示的数值范围是65535
长整形 long, 表示长整数。 总共是8字节。对long类型赋值,需要在常量后加L ,long x = 10L;
2. 浮点型: float ,
单精度,表示6位小数,表示小数类型,1.2,-2.3, 0.0,总共多少位,给float类型复制需要加f。
double,双精度,表示12位小数,总共多少位
3.字符类型: char ,
表示字符如‘a’,‘b’, 在内存中占1个字节。范围是0~255,sizeof(char) = 1;
ascii:是把字符和整形对应的一个表 ‘0’—48 ,‘A’—65,‘Z’–90, ‘a’—97,‘z’—122, ’ '—32 , ‘\0’—0
转义字符: 和原本意思不一样了。 \n —换行 \t----tab \—一个\ %%—表示一个% " ----表示双引号
4.字符串类型:
没有对应变类型,“aa”,char* p = “asdfgasdg”;
5.每个类型都分为有符号和无符号
正数是无符号,负数是有符号,如果只想表示正数则用无符号。关键字 unsigned
例如:unsigned int x = 123;
补充: 1字节 = 8位
1KB = 1024B
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1EB = 1024TB
五、输入和输出 加上#include<stdio.h>
1.在屏幕上输出,printf
格式: printf("照常输出部分 占位符 ",变量);
格式: printf("照常输出 %d %c\n",变量1,变量2);
printf("int = %10d \nfloat = %10.5f\n",x,z);
占位符: %d --------打印int整形的值 ,%10d 总共显示10位,不够的部分用空格替换,右对齐
%o(字母o) 打印8进制
%x------------打印16进制
%hd --------打印short的值
%ld ---------打印long的值
%u ---------打印无符号数据的值
%c --------- 打印字符类型
%f --------- 单浮点型,%5.3f总共5位,小数点占3位。
%lf %le--------- 双精度浮点型
%s ----------字符串
%p ------------指针
2.输入 scanf
格式: scanf("占位符",&变量);
scanf("占位符,占位符",&变量1,&变量2);如果是逗号,则输入的时候也要用逗号隔开
scanf("占位符 占位符",&变量1,&变量2);如果是空格,输入的时候也是空格隔开
练习:输入年龄和姓名(姓名用一个字符表示),把输入的内容打印出来。(代码day01/04test.c)
五、运算符
1.算数运算符
+ 加法 计算两个数的和,例如 int a = 10 ;int b = 20; a + b ,其中a和b的值不变,a+b得到的是a和b的结果。如果a修改需要赋值
- 减法 同上
*乘法 同上
/ 除法 整除,所有的小数部分丢弃。 12/21 = 0 12 / 4 = 3; 12/5=2
% 取模,余数,得到相除后的余数 12%21=12 ,12%4 = 0; 12%5=2 ,值1%值2把结果限定在值2范围内,123%5=3
2.赋值运算符
格式: 左值= 右值; 把右值给到左值
复合赋值运算符:+= ,*= ,-=,/= ; %=; |= ; &=; >>=;<<= (算数运算符,二进制运算符)
例如: int x = 10; int y = 20; y *=x+2; 等价于 y = y*(x+2);是240
注意: 左值不可以是常量 ,只可以是变量。不可以有其他的运算。
3.关系运算符
关系运算符的结果只有两个,真或者假,非0为真,0表示假。
> 如果左值大于右值,结果为真,否则为假
>= 如果左值大于或者等于右值,结果为真
< 如果左值小于右值结果为真
<= 如果左值小于等于右值,结果为真
== 如果左值和右值相同则结果为真,否则为假
!= 如果左值和右值不相同则结果为真
注意:一般情况用于判断语句,比如说if语句中。
在比较运算符==时候,需要把常量放前面,以防出现赋值运算符
关系运算符连用是不正确的,例如:10 < time < 15
4.逻辑运算符:(双目)
&& : 表达式左值和右值都为真则结果为真,否则为假 。&表示按位与
|| :表达式左值或者右值有一个为真则结果为真。|表示安位或。
! :逻辑非,如果表达式为真则结果为假, !(2>6)
注意:&&有截取功能,如果第一个表达式(左值)为假,右侧的值不计算。
|| 也有截取功能,如果第一个表达式为真,则第二个表达式不会计算。
案例1:请输入你的年龄,和金钱。如果年龄小于18岁,并且钱大于10000,则是高富帅,否则为屌丝。(代码day01/gaofushuai.c)
练习2:输入三个数,请判断哪个值最大,打印出最大值。
5.自增自减运算符 (单目)
前加加:++x; 表示x= x+1;是先自己加1后再做其他事情
后加加:x++; 表示x = x+1;是先计算完其他操作再自己加1(暂时理解遇到分号加1)
前减减:--x:
后减减:x--:表示x = x-1;是先计算完其他操作再自己减1(暂时理解遇
到分号减一)
6.三目运算符 (运算符有三个值)
格式: 表达式1 ? 表达式2:表达式3
执行:如果表达1为真则执行表达式2,如果表达式1为假则执行表达式3.
三目运算符可以代替简单的if语句使用。并且三目运算符可以嵌套使用。
7.运算符的优先级
优先级:先计算的运算符优先级高,否则优先级低
优先级顺序:括号–>单目–>算术运算符–>移位运算符–>关系运算符–>位运算符–>逻辑运算符–>三目运算符–>赋值运算符–>逗号运算符。
六、分支语句
1.if分支语句
if格式:
if(条件判断)
{
语句1
}
else
{
语句2
}
2.执行:如果条件1满足则执行语句1,不满足执行语句2;
3.格式2:
if(条件判断1){
语句1;
}
else if(条件判断2) {
语句2;
}
else {
语句3;
}
执行: 先判断条件1是否满足,如果满足则执行语句1后结束if语句,如果条件1不满足继续判断条件2,如果条件2满足则执行语句2,否则执行语句3;
注意:有if不一定有else if和else 但有else必定有if ,有else if也必定有if。
练习: 判断某一个数是不是回文:12321 22222,32123 (day01/10same.c)
getc putc, gets puts getchar putchar sprintf sscanf的使用(了解)
C语言学习第二天
一、if嵌套
格式:
if(条件1)
{
if(条件3)
{
语句4;
}
else
{
语句5;
}
}
else if(条件2)
{
语句2;
}
else
{
语句3;
}
执行:先判断条件1是否满足继续判断条件3,如果条件3也满足则执行语句4后结束,如果条件3不满足则执行语句5结束,
如果条件1都不满足则继续判断条件2,如果条件2满足则执行语句2,如果条件2也不满足则执行语句3
2.开关语句:switch
格式: switch(表达式)
{
case 常量1:表达式1;break;
case 常量2:表达式2;break;
case 常量3:表达式3;break;
case 常量4:表达式4;break;
…
default: 表达式5; break;
}
执行: 先计算表达式的值,和case后的常量比较,当常量的值和表达式的值相同时,则执行后面的表达式,如果所有的case都不匹配,则执行defaul后的语句
注意:case后的常量不可以相同。
一般情况下 在每个case后都会加上break;
default不是必须有,可写可不写。但一定是最后一个匹配。
case常量后是冒号:
如果在case后需要定义变量,则要加上大括号使用。
练习: 输入成绩,用switch打印等级(day02/03sorce.c)
3.goto语句
作用:跳转到某一个位置,需要提前把位置设置好,遇到goto加标签则直接跳转到标签位置。
格式: goto 标志;
案例: DANNY: //设置标志
goto DANNY; //跳转到标志
练习: 使用goto计算1到100的偶数和(day01/05even.c)
二、循环语句
1.while循环语句
格式:while(条件)
{
循环语句;
}
2.流程:首先判断条件是否满足,如果满足执行循环语句,然后继续判断,满足则一直执行,知道条件不满足为止;
3.练习: 打印1到100的和(day02/06while.c);
4.练习:输入两个数,用while循环打印出两个数中间的所有整数(包含两个数)()
5.练习:打印斐波那契数列。
注意:while后不可以加分号
6.continue 和break
continue:退出当次循环,遇到continue后的代码不执行,继续执行下一次循环。
break:退出当前循环,遇到break则退出循环;
7.死循环(无限循环)
定义:当循环条件永远为真则循环不会结束,这种循环叫无限循环。无限循环不结束则后面的代码无法执行。
比如说 while(1){} while(1);
三、循环嵌套:
1.定义:在循环中加入另外一个循环
2.格式: while(条件1)
{
语句1;
while(条件2)
{
语句2
}
语句3;
}
3.执行:先判断条件1是否满足,如果满足执行语句1,后继续判断条件2,如果条件2满足则执行语句2后继续判断条件2,
直到条件2不满足才会退出内部循环,然后执行语句3,再执行外部循环的条件1,如果满足继续刚才的流程,直到条件1不满足则退出循环。
注意:在循环嵌套中,如果遇到break,则会退出当前循环,break在哪个括号中则退出当前括号的循环。
4. do…while循环
格式:do
{
循环体;
}while(条件);
执行:先执行循环体,在判断条件是否满足,如果满足则继续执行循环体,如果不满足则退出循环。不管是否满足条件,至少会执行一次循环体。
随机值:电脑随机产生一个值,是随意的。
随机值产生步骤: 添加头文件 #include <stdlib.h>,需要用srand做种子,然后rand产生随机值。
案例:猜数小游戏,随机产生值,然后和输入的值比较,给出大小提示,直到用户猜对为止。(day02/11guess.c)
注意:在while后要加分号。
5.for循环
格式: for(语句1;条件2;语句3)
{
循环语句4;
}
注意:语句1一般为 定义变量语句
语句2一般为判断条件语句
语句3一般为自增自减语句
执行:先执行语句1,然后判断条件2是否满足,如果满足 则执行循环语句4,继续执行语句3后再判断条件2是否满足,
如果条件2还满足则继续执行循环语句4,直到条件不满足为止。
6. for循环嵌套
格式:for(语句1;语句2;语句3)
{
for(语句4;语句5;语句6)
{
循环体7;
}
}
执行:先初始化语句1,判断条件2是否满足,进入到内循环中,执行语句4,判断语句5是否满足,如果满足则执行循环体7,
再执行语句6后判断语句5是否满足,如果满足则一直执行循环体7中内容,如果不满足则执行语句3,在判断语句2是否满足。
练习:输入一个值,然后打印输入值的行数的三角形(day02/14triangle.c )
*
* *
* * *
练习: 九九乘法表
四、数组:
1.定义:相同数据类型的连续的集合。
2.格式:数据类型 数组名[数组元素个数] = {多个值};
例如: int arr[6]={1,2,3,4,5,6};
3.元素:数组的每个值是他的元素,用数组名和下标表示例如arr[2]
下标:数组的下标是从0开始到数组元素个数-1.不可以越界使用
4.数组的初始化:
int arr[4] = {1,2,3,4}; 全部初始化
int arr[4] = {1,2} 部分初始化,未初始化部分默认为0
int arr[4] = {0} 全部初始化成0;
int arr[ ] = {1,2,3,4} 缺省初始化,会根据初始化个数自动确定元素个数
练习:请打印斐波拉契数列:1 1 2 3 5 8 (20项)
5.补充知识: (头文件unistd.h)
sleep() 代码延时, (秒)
system(命令) 调用外部程序执行
exit() 结束程序 ,结束进程
注意:在Windows下是没有这个头文件的。
C语言学习第三天
一、一维数组
1.定义:数据类型 数组名[数组元素个数] = {1,2,3};
2.变长数组(伪):数组元素个数可以改变的数组叫变长数组
实现: int a ; scanf("%d",&a); int arr[a]; 数组大小会随着输入的内容进行修改
3.练习:随机产生十个学生的学号(100以内),用户输入学号后判断在数组中是否存在,如果存在则打印出位置。(day03/02findStudent.c)
二、二维数组
1.二维数组:在一维数组中插入一维数组,每个二维数组中的元素都是一维数组。
2.格式: 数据类型 数组名[行数][列数] = {值};
例子: int arr[3][5] = {1,2,3};
3.理解: (int ARR[3]) arr[3] = {1,2,3}; 只可以这么理解
int arr[3][3] = {1,2,3}; //3行3列
4.下标: 数组元素中,行下标= 行数 -1
列下标 = 列数 -1
5.初始化方式:
int arr[3][2] = {1,2,3,4,5,6}; 全部初始化
int arr[3][2] = {1,3}; 部分初始化,没初始化的默认是0
int arr[3][2] = {{1},{3,2},{4,1}} 第一行1和0,第二行3,2,第三行4,1分组初始化
int arr[ ][2] = {1,2,3,4,5,6,7} ; 缺省初始化,默认是4行,第四行是7和0
6. 三维数组:int arr[2][3][4] ;
5.补充:fflush(stdout); //清空输出缓冲stdout. 其中stdin是输入缓冲
练习: 有5个学生,每个学生都有语数外的成绩,请输入学号和科目(0~2),打印出学生的成绩。(成绩随机生成)(day03/04findDoubleArr.c)
三、函数
1.函数:对某一部分重复代码进行封装,方便使用,增加代码的模块化。
2.格式:返回值类型 函数名(参数)
{
函数体;
}
3.注意: 如果没有返回值用void代替,如果没有参数可以空着也可以用void代替
函数内部不可以定义函数,必须放全局。
如果不写返回值类型也不写void则默认是int返回值。
4.参数:调用者给被调用者传递值
格式:返回值类型 函数名(数据类型 变量名,数据类型 变量名){ }
实参:实际参数,在内存中存在。比如main函数中的变量
形参: 形式参数,函数调用前没有内存存储形参,只是形式。
所有形参都是实参的拷贝。
练习:请写一个函数,交换两个变量的值,然后再main中打印交换后的值,在函数中打印交换后的值。(day03/07change.c)
注意:形参改变,不会影响实参的值,他们在不同的内存空间。
5.局部变量和全局变量
局部变量:定义在局部,在某一个括号中的变量(小括号,大括号)
全局变量: 定义在全局,没有定义在某一个括号中
6.作用域和生命周期
作用域: 变量可以使用的范围,局部变量一般在某一个括号中,空间角度
生命周期:变量从在内存中存在到内存中释放的时间。时间角度
7.局部变量和全局变量的区别
生命周期的区别:
全局变量的生命周期:从程序运行到整个程序结束
局部变量的生命周期:从定义开始到括号结束。
作用域的区别:
全局变量:在整个项目中都可以使用,不限于当前文件
局部变量:从定义开始到大括号结束。
8.静态变量和常量
常量:不可以修改的量。用const修饰。 const int x = 123; 则x=234错误。
静态变量:在变量前加static。
全局静态变量:全局静态变量不会修改生命周期,但把作用域限定在本文件中。
局部静态变量:局部静态变量作用域不变,生命周期从定义开始到整个程序结束吗,局部静态变量只会初始化一次。
注意:在同一个作用域内不可以有相同的变量名
在不同的作用域内可以有相同变量,如果全局变量和局部变量名相同时,优先使用局部变量,就近原则。
全局变量只有一份,如果某个位置修改了全局变量的值,其他地方全部修改。一般情况下避免使用全局变量。
9.返回值
作用:函数执行完代码后,把结果反馈给调用者。
格式: int fun(int x){ return xx;}
注意:如果有返回值类型,就必须加return关键字;
return只可以返回一个值,例如 return x,y;是错误的。
函数遇到return 则结束,后面的代码不会执行。
如果返回值是void,可以使用return;结束函数。
函数在使用前需要申明,如果没有申明会隐式声明成int返回值和int参数
练习:请在main函数中输入一个行数和列数,通过调用函数返回杨辉三角对应的值。(day03/08return.c)
10.main函数的参数
格式: int main(int argc,char* argv[ ]);
参数: argc是参数的个数,argv是参数的内容。char* 表示字符串,[ ]字符串数组。
补充:
#include <stdlib.h>
int atoi(const char *nptr); 把字符串转换成int
long atol(const char *nptr); 把字符串转换成long
long long atoll(const char *nptr); 把字符串转换成longlong
如果需要把某一个类型转换成其他类型,直接在前面加上转换后的类型加小括号 int x = 10; float y = (float)x;这个叫强制类型转换。
在32位系统中,long是4字节,longlong是8字节,在64位系统中,long是8字节,longlong也是8字节。
五、字符串:
1.字符串:字符串是由双引号括起来的,以\0结束的一串字符
2. 字符串常量: “abc” , 每个字符串后默认会添加'\0', ascii是0
3.表示方式:
char arr[10] = {'a','b','c'}; 部分初始化后面的部分是0,而\0的ascii是0
char * arr = "abcasg"; 指针表示方式。
char arr[] = "abcde";
4.字符串的长度包含\0,字符串的有效长度不包含\0,%s打印字符串。
2.字符串函数:
1>字符串不可以使用==比较,strcmp
#include <string.h>
int strcmp(const char *s1, const char *s2);
作用: 比较两个字符串是否相同
参数:s1 第一个字符串
s2 第二个字符串
返回值:如果相同返回0,如果第一个大返回正数,第一个小返回负数
返回值是第一个字符串中不相同的字母ascii值减去第二个字符串不同的字母的sacii。
2> 计算字符串的长度 strlen
size_t strlen(const char *s);
作用:得到字符串的有效长度,不含\0
参数: 需要计算长度的字符串
返回值:返回字符串的有效长度
3>字符串的拷贝 strcpy
char* strcpy(char *dest, const char *src);
作用: 把stc中的内容拷贝到dest中
参数: stc 源字符串
dest是目的字符串,dest只可以用数组的表示方式
dest必须有足够的空间存放stc的内容,比如说stc是“acd”,则dest中数组必须有4个元素。
返回值:拷贝后的dest的首地址,dest字符串。
C语言学习第四天
一、字符串:
1.字符串比较函数 strcmp
int strcmp(const char *s1, const char *s2);
作 用: 比较s1和s2字符串是否相同
参 数:s1是需要比较的第一个字符串
s2是需要比较的第二个字符串
返 回: 0 如果两个字符串相同
<0 第一个字符串小于第二个字符串
>0 第一个字符串大于第二个字符串
比较的是第一个不相同字符的ascii的值的差。
2.得到字符串长度 strlen
函数原型: size_t strlen(const char *s);
作用: 得到字符串的有效长度
参数: 需要获取长度的字符串内容
返回: s字符串的有效长度
3.字符串追加 strcat
函数原型:char *strcat(char *dest, const char *src);
作用:把src字符串拼接到dest字符串后面
参数: src源字符串,需要拼接到dest后的字符串
dest目的字符串,需要存放src字符串。
返回:返回拼接后的字符串内容。
4.字符串的拼接 sprintf
函数原型:int sprintf(const char *format, ...); 比如:int arr[10],a,b; sprintf(arr,"%d %d",a,b);
作用:把可变长参数中的内容合并成一个字符串format
参数:第一个部分:字符串变量,
第二个部分: 双引号的内容,有占位符
第三个部分:各种变量,用来填充占位。
返回:拼接后的字符串长度。
5.字符串的输入
char *gets(char *s);
作用:获取一个字符串,以回车结束输入。
参数:输入后的内容保存的位置。
返回值:输入的字符串内容。
注意:会有危险警告,没法检测s的内存空间,避免使用。
int getchar(void);
作用:获取一个输入的字符
参数: 无参数
返回值:输入的字符的ascii值。
注意:输入函数是堵塞函数,没有输入则一直等待直到输入结束为止。
6.字符串的输出
int putchar(int c);
作用:输出一个字符
参数:需要输出的字符ascii值
返回:也是输出的ascii值
int puts(const char *s);
作用:输出一个字符串
参数: 需要输出的字符串内容
返回值:输出的字符串的字符个数,包含\0
7.字符串的拷贝
char *strcpy(char *dest, const char *src);
作用:拷贝src的内容到dest中
参数: src源字符串
dest 目的字符串
返回值:拷贝好后的字符串 dest字符串。
char *strncpy(char *dest, const char *src, size_t n);
作用:拷贝src中的前n个字符。
参数: src源字符串
dest 目的字符串
n 需要拷贝的字符个数
返回值:拷贝好后的字符串 dest字符串。
练习:输入gets两个字符串,通过strncmp比较前5个字符是否相同,如果相同则把第二个字符串的前3个字符拷贝到第一个字符串的后面。(day04/03strncat.c)
int strncmp(const char *s1, const char *s2, size_t n);
char *strncat(char *dest, const char *src, size_t n);
8.内存初始化函数
函数原型: void *memset(void *s, int c, size_t n);
参 数: s 是字符串的首地址,内存的首地址(数组名)
c 需要初始化的值,比如说是0
n 需要初始化的内存大小。
返回值:内存首地址
注意: memset按照字节进行初始化;
函数原型: void bzero(void *s, size_t n);
作用: 把s内存初始化成0
头文件: #include <strings.h>
参数:s 字符串--内存首地址
n 需要初始化的内存大小
返回值:无
memcpy;
sscanf;
二、函数高级
1.递归函数;
含义: 函数自己调用自己。
案例: int add(int x){ add(20); }
规律: 1> 找到特殊值,也就是函数出口
2> 找到公式,普通情况下的处理方式
三、结构体
1.结构体:不同数据类型的集合,属于自定义类型。
2.案例:用一个东西来存学生的信息,学号,姓名等,int age,int id
3.格式: struct 机构体名{数据类型 变量名1;数据类型 变量名2;… };
4.变量:struct 结构体名 变量名;
5.定义全局结构体变量
struct person{
int id;
int age;
char name[20];
}lzf;
lzf是一个struct person的变量。
typedef struct person{
int id;
int age;
char name[20];
}per;
per是一个struct person的类型,也叫做它的别名。
6.使用: 结构体变量名.成员变量;
lzf.id = 123;
7.初始化: 和数组的初始化方式一样。初始化的类型要和结构体定义类型匹配
struct person danny = {10,20,"abcd"};
8.改名: typedef个关键字,在结构体struct前加上typedef
9.结构体的对齐和补齐
因为结构体的成员变量类型不一样,所以cpu在找数据的时候会比较麻烦,为了使cpu访问速度提高,
以空间还时间的方式,给结构体中空余一部分空间,使之对齐,提高访问速度。
节省空间办法:
小类型放前面,大类型放后面(char --short ---int ---float--long---double--超过8的数组)
四、结构体数组
数组:相同数据类型的集合,
结构体数组:数组中的每个元素都是结构体类型、
例如: struct student arr[10];
arr[0].id = 123;
补充:数组名就是数组的首地址,在scanf的时候直接传入数组名就行不需要加&
C语言学习第五天
一、共用体:
共用体:多个变量公用一块内存空间
关键字: union 共用体名{
类型 变量名1;
类型 变量名2;
};
大小:共用体大小是最大类型的大小,如果有char int 则大小是4字节
在同一个时间点,共用体中的变量只有一个存在。
场景:在网络通信中,发送端发送数据后接受端不知道什么类型的数据,接收端通过共用体,一块内存不同的类型表示来接受数据。
定义: union 共用体名 变量名;
不可以直接用大括号初始化,因为只有一个变量有内存。
使用:共用体名.成员变量名 = 值;
二、枚举类型
枚举:用一个名字代替一个值,为了代码的可读性更好。
格式: enum 枚举名{枚举常量1,枚举常量2.....};
使用: 直接把枚举常量当做普通常量使用。
枚举值:枚举中的常量值默认从0开始,后面常量值自动加一处理
注意:可以在switch中使用,在case后当做常量使用。
枚举类型在64位系统占4字节,相当于一个int。
案例:
enum day{mon,tue=3,wen=2,thu,fri,sat,sun};
//从第一个赋值的元素开始加以
int main(){
printf("mon = %d\n",mon);//默认是0
printf("tue = %d\n",tue);//赋值3
printf("wen = %d\n",wen);//赋值2
printf("thu = %d\n",thu);//默认2+1
printf("fri = %d\n",fri);//默认2+1+1
printf("sizeof(enum day) = %d\n",sizeof(enum day));
return 0;
}
三、预处理指令
1.编译过程: 预处理,编译,汇编,链接。
2.预处理指令:以#开头的部分。不是c语言的语句,不需要加分号。
包含:头文件,宏定义,条件编译。
3.宏定义:用一个名字代替一个值
格式1:#define 宏名称 替换文本(值)
格式2:#define 宏名称 (定义一个标志)
格式3:#define 宏(参数) 表达式
4.注意:#define CJ(X,Y) ((X)*(Y)) //宏函数需要给每个参数加上括号,整体加上括号。以为宏函数只替换内容,不会自动加括号。
5.条件编译
满足某一个条件则编译一块代码,不满足则不编译。
格式1:#ifdef 宏
编译代码1
#else
编译代码2
#endif
编译:如果宏存在(#define 宏)则编译代码1,如果不存在则编译代码2
格式2:#if 条件
编译代码1
#else
编译代码2
#endif
编译: 如果条件为真则编译代码1如果条件为假编译代码2
格式3: ifndef 宏
编译代码1
#else
编译代码2
#endif
编译:如果宏不存在则编译代码1,如果存在则编译代码2.
四、头文件
作用:定义结构体,函数的申明,宏定义,文件的包含
#ifndef __FILE_H__
#define __FILE_H__
#include <stdio.h>
struct student{};
#define xxx 10
void fun();
#endif
后缀:头文件后缀是.h
使用:在include自定义的头文件时,使用“”而不用<>,使用系统库头文件时,使用<> 而不适用“”
区别: #include <xxx.h> 默认先从库文件目录查找,如果没找到再从PATH路径找,如果还没找到则报错。
而#include""先从当前目录开始查找,如果没找到继续查找库文件目录,如果没找到继续查找PATH路径,还没找到则报错。(系统库目录:/bin/include)
防止头文件重复包含
#ifndef _ _ XXXX_H_ _ 下划线之间不要空格,这里是格式原因
#define _ _ XXXX_H_ _
头文件内容;
#endif
或者 #pragma once
五.指针(所有以下内容都是基于64位系统)
1.指针:是指向地址的变量,保存的是地址
地址: 变量存储的位置编号,理解成门牌号。常量。
2.定义指针
数据类型* 指针变量名 = &变量;
int a= 10;
int* p = &a;
解引用: 得到指针所指向内存空间的内容
*p = 123; 则表示把a的值修改成123,printf{"a=%d",*p};
3.指针类型
指针的类型是所指向的内存空间类型后加*; 比如int a; int*p =&a;
指针所指向内存空间的类型:指针类型去掉星号
4.指针运算
指针可以做++ ; --; +常量 ; -,-常量; 地址+地址
地址相减:实际减的是个数,理解成相隔几个房间。
定义一个指针,指向double类型的变量,打印出指针的值。(%p)
C语言学习第六天
一、指针的定义
1.int* p = &a;
指向改变: p = &b;
指向内容改变:*p = 123;
2.指针运算
指针和指针相加是非法。
指针和指针相减是可以的;
double a[2] = {0};
double* p=&arr[0];
double*q = &arr[1]
q指向a[1], 计算q-p =1;表示相差几个double
指针加常量是可以的 p+1是可以的 p+2越界是错误的;
指针可以++ ,--
3.解引用
得到指针所指向的内存空间的值。
在指针前加*,
例如 int a = 10;
int* p = &a;
*p=110;
给指针赋值 指针指向发生改变。(非常重要)
4.野指针
野指针:不指向某一个固定位置的指针。要防止野指针出现
空指针:指向NULL的指针。当指针不知道指向哪里时,则赋值为NULL;
段错误:核心已转储,操作了一块不允许操作的内存控件,数组越界,操作了野指针,用空指针解引用。
5.常指针(面试题):只有const挨着指针的时候才是修饰指针,指针指向不可以改变。
指针常量:指针所指向的内存空间是不可以修改的
const int *p = &a; p=&b 允许;但*p= 22是错误的; b=22可以的;
常量指针:指针的指向不可以修改
int * const p = &a; p=&b不允许;但*p= 22可以;
const int* const p=&a; 指针的指向和所指向的内存空间都不允许修改
补充1:如果定义一个const变量,用指针指向变量后,可以通过解引用修改
const int a = 123;
int *p = &a;
*p=222; 允许;
补充2: 直接用指针给指针赋值是可以的
int a = 123;
int *p = &a;
int *q = p; 是可以的,可以用指针直接给指针赋值。
二、二级指针
1.二级指针:保存的是一级指针的地址;同样是8个字节。
一级指针:保存的是变量的地址。
2.格式:数据类型** 二级指针变量名 = &一级指针变量名;
案例: int a = 123;
int* p = &a;
int** pp= &p;
命名规则: pp_num; ppNum; PpNum;
3.使用案例:
int main(int argc,char *argv[])
{
int a = 123;
int b = 234;
int* p = &a;
int * q = &b;
printf("*p %d\n",*p);
int ** pp = &p;//二级指针不可以是野指针
*pp = &b; //*pp等同于p等同于&b,改变了p的指向,pp=&&a 错误
printf("*p =%d,**pp =%d\n",*p,**pp);
pp = &q; //pp指向q,
printf("*p =%d,**pp =%d\n",*p,**pp);
return 0;
}
三:动态内存分配
总共分为:代码区,数据区,堆区,栈区
根据地址从小到大分布如下:
代码区:存放代码,不可以修改
数据区: 静态常量区 :静态变量和常量
全局变量区:已初始化的全局变量
bss段:未初始化的全局变量
堆区:动态内存申请,自己申请自己释放,先从低地址分配,再分配高地址。
栈区:存放局部变量,自动申请自动释放,先从高地址分配,再分配低地址空间,堆区和栈区无明显的分割线。
2. 动态内存申请
void *malloc(size_t size);
作用:在堆空间中申请内存。
参数: size 需要申请的内存大小
头文件: #include <stdlib.h>
返回值:申请到的内存空间的首地址。
int* p = (int*)malloc(8);
注意:malloc申请的内存空间没有初始化时,存放的是乱值。
malloc的返回值需要强制类型转换才能使用。
malloc申请的内存空间,用完后需要释放。
案例:用malloc申请一块内存,存放3个学生的信息,学生(id,age);代码如下:
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student
{
int id;
int age;
}stu;
int main(int argc,char *argv[])
{
//连续内存空间可以使用下标操作
// 结构体数组 stu arr[3]; p=&arr[0];
stu* p = (stu*)malloc(3*sizeof(stu));
if(NULL == p)
{
perror("malloc");//打印错误信息
exit(0); //结束程序
}
memset(p,0,sizeof(stu)*3);//malloc申请的空间是乱值。
for(int i = 0; i < 3;i++)
{
printf("请输入学生id和age\n");
scanf("%d %d",&p[i].id,&p[i].age);
}
for(int j = 0;j < 3;j++)
{
printf("第%d个学生的id是%d age是%d\n",j+1,p[j].id,p[j].age);
}
//相同数据类型连续的集合
int* pa = (int*)malloc(10*sizeof(int));
printf("%p,%p\n",pa+1,pa);//相差4字节
//如下会出现野指针
//int a = 123;
//pa = &a; malloc申请的内存pa不再指向,导致内存泄露
return 0;
}
备注: 对于连续的内存空间可以使用下标操作(重点)
int* p = (int*)malloc(5* sizeof(int));
p[0] = 123;
接受malloc返回值的变量指针,不允许被修改,不可以指向其他位置。如果修改会导致malloc申请的内存没有指针指向,内存泄漏。
void free(void *ptr);
作用:释放动态申请的内存空间,如果不释放内存泄漏
参数:malloc返回的指针
返回值: 无
void *calloc(size_t nmemb, size_t size);
作用:动态申请内存。申请的内存默认初始化成0.
参数:nmemb申请的内存块的个数
size 每一块内存的大小
返回值:申请的内存空间的首地址。
void *realloc(void *ptr, size_t size);
作用:重新申请内存空间。重新分配内存空间
参数: ptr 从ptr地址开始申请,一般malloc的返回值。
size 一共申请size大小。
返回值:重新申请的内存空间首地址。
注意:realloc申请的内存不一定是从ptr开始,当ptr开始的内存不足以有size个的时候,会先释放当前内存后,重新在另外一个区域申请。
四、指针和函数的关系
1.函数:存在代码区(不允许修改)
2.函数名: 就是函数的首地址。 void fun(int x);
3.练习: 请写一个函数,修改两个参数的值。形参和实参都需要修改。
void change(int *a ,int * b);
4.注意:不可以返回局部变量的地址。因为函数结束后局部变量就被释放了。
malloc申请的内存空间函数结束依然存在。只有free释放之后才不会存在。
学习fopen fread fwrite fgets fputs fgetc fputc fclose fseek rewind ftell 的使用。
C语言学习第七天
1.函数和指针的关系
int a = 123;
int b = 234;
int *pa = &a;
int * pb = &b;
请用一个函数,修改pa和pb的指向,实参和形参同时修改(day07/01fun.c))。
备注:在函数中,如果想修改形参一级指针指向,需要传入二级指针。
注意: 不可以返回局部变量的地址。()
2.malloc和函数的关系
案例:
int* funmalloc()
{
// int a = 123;
int* p =(int*)malloc(sizeof(int));
// p = &a;
*p = 123;
// return p;
}
注意: 在函数中动态申请的内存,需要在函数内部释放,要么需要返回首地址给调用者,否则会出现内存泄漏。
注意:
/* 错误 p所指向的内存空间不允许修改,*p指向一级指针,所以不可以修改一级指针指向(查看大门01fun.c)查看图片(函数和malloc)
void funconst( const int** p)
{
*p = (int*)malloc(sizeof(int));
}*/
3.函数指针和指针函数
指针函数:是返回值是指针的函数 int* fun(int a){ }
函数指针:是一个指针,指向某一个函数。
函数指针格式: int (fun)(int a); fun是指针,指向参数是int 返回值是int的函数,指针的类型 int () (int a); fun指向的类型 int p(int a);
使用:函数指针名(参数); 例如void (*pfun)(int a) = fun; pfun(10);
命别名: typedef int (pf)(int a,int b) ;其中pf是指针类型。
定义了一个函数指针类型为int ()(int ,int)
定义指针变量: 函数指针类型 指针变量= 函数名
注意:给指针变量赋值,需要匹配相同的指针类型,则返回返回值和函数的参数要和指针类型相匹配,否则是类型不匹配错误。
一般情况下 函数指针名都会用callback
当数组作为函数参数时,会隐式转换成指针使用。
回调函数:把函数指针当做函数参数,系统回调函数是系统自动调用函数。
案例如下:用一个函数计算两个数的加减乘除。
#include<stdio.h>
void fun(int a){printf("a = %d\n",a);}
//定义一个函数指针类型
typedef int (*pf)(int a,int b) ; 其中 pf是类型,不是变量名
//可以这么理解typedef int(*)(int a) pf; pf是别名,是类型,这个格式会语法错误。
void fun(int a){ printf("a = %d\n",a);}
int add(int a,int b){ printf("add = %d\n",a);}
int sub(int a,int b){ return a-b;}
int jisuan(int a,int b,pf function)//pf function = sub
{
return function(a,b);
//function只是一个变量,实参是什么则funciotn是什么。
}
int main(int argc,char *argv[]){
//pfun是指针名,是变量,不是类型
void (*pfun)(int a) = fun;
pfun(10);//实际调用的是fun函数,打印a=10;
pf pmyfun = add;//pmyfun指向add函数,
pmyfun(1,3);
printf("a-b=%d\n",jisuan(2,4,sub));
return 0;
}
二、数组和指针的关系
1.数组名: 数组名就是数组的首地址。数组名是const指针,不可以指向其他的位置,只可以指向数组的首地址。int arr[10] = {0},arr 和&arr[0]相同。arr++是错误的。
练习:定义一个数组,10个指向int类型的指针。int* arr[10];
2.指针数组: 是数组,在数组中存放 指针 int* arr[10];
3.数组指针:是指针,指向某一个数组。
意义: int (*arr)[10]; 一个指针,指向一个数组,数组大小是10个元素; int (*arr)[10] = &arr;
定义一个结构体数组指针。指向3个元素的数组。结构体(id ,age)(代码:day07/04arrPointTest.c)
4.指针数组指针:是一个指针,指向一个数组,数组中存放的是指针。
int* (*arr)[10]; stu* (*arr)[10] ;
自学: 定义一个数组指针,数组中存放的是10个元素的数组指针。
5.二维数组和指针关系
二维数组名是指针,指向第一行的第一个元素。
二维数组名加1实际加了一行元素
二维数组名[下标] 表示某一行的首地址。类型是一维数组,加一实际加了一行。
二维数组名[行标][列标];某一行行的某一个元素,加一实际加了一个元素的值。
案例:
#include<stdio.h>
typedef struct student
{
int id;
int age;
}stu;
int main(int argc,char *argv[])
{
//定义结构体数组
stu arr[3]={{1,2},{2,3},{3,4}};
//strcpy 给字符串赋值才用
//定义一个结构体数组指针。
stu (*parr)[3] = &arr;
//如下代码,理解方式如下
//parr = &arr; *parr = arr =&arr[0]
//**parr=arr[0];
(**parr).id = 123; //arr[0].id
(*parr)[1].id = 234;//arr[1].id
printf("parr = %p\nparr+1=%p\n",parr,parr+1);//加24,指针加一加的是一个类型大小。
stu doubleArr[2][3] = {0};
parr = doubleArr;
printf("parr = %p\n",parr);
printf("parr+1 = %p\n",parr+1);
printf("doubleArr = %p\n",doubleArr);
printf("doubleArr+1 = %p\n",doubleArr+1);
printf("doubleArr[0] = %p\n",&doubleArr[0]);
printf("doubleArr[0] +1 = %p\n",&doubleArr[0]+1);
printf("doubleArr[0][0]+1 = %p\n",&doubleArr[0][0]+1);
printf("doubleArr[1] = %p\n",&doubleArr[1]);
printf("doubleArr[1][0] = %p\n",&doubleArr[1][0]);
return 0;
}
三、指针和结构体关系
1.定义结构体 :struct 结构体名{ };
2.定义结构体指针:struct student* pa;
3.案例:
4.使用:通过结构体指针操作结构体中的成员变量,需要用到->
5.例子:
#include<stdio.h>
struct student
{
int id;
int age;
char name[10];
};
int main(int argc,char *argv[])
{
struct student danny;
struct student huqing;
struct student* pd = &danny;
danny.id = 123;
pd->age = 12;
//pd.id = 123; 错误
pd=&huaqing;
pd->id = 12;
printf("%d\n",huaqing.id);
printf("%d\n",pd->id);
return 0;
}
案例:定义一个结构体指针数组,有3个学生信息(int id;int age;char name[10];)请通过scanf输入的方式对结构体数组进行赋值。
#include<stdio.h>
#include <stdlib.h>
typedef struct student{ int id; int age; char name[10];}stu,*pstu;
//这么理解typedef struct student* pstu;
//这么理解typedef struct student stu;
int main(int argc,char *argv[]){
pstu pa[3] = {0};//等价于struct student* pstu
pa[0] = (pstu)malloc(sizeof(stu)*3);
printf("请输入学生信息(id,age,name)");
scanf("%d %d %s",&(pa[0]->id),&(pa[0]->age),pa[0]->name);
printf("%d\t%d\t%s\n",pa[0]->id,pa[0]->age,pa[0]->name);
return 0;
}
四、指针和字符串的关系
1.字符串的保存方式
字符串数组: char arr[10] = “abc”;
字符串指针: char * p = “bac”;
2.字符指针指向的是字符串的首地址,第一个字符的地址
3.相同的字符串常量在内存中只会存储一份,但凡有字母不一样则是不同的字符串。
五、文件操作
1. 文件存在磁盘中,是长期存储的。
2.使用:一般用于写日志文件,日志是程序运行中的各种信息,当程序出现bug时定位问题的位置。一般日志文件都是.log结束,和txt文件一样。
3.文件可以长期保存信息,比如passwd文件。
文件:存储在外部介质上的数据的集合,操作系统管理数据的最小单位。长期存储,不会因为断电而丢失,分为文本文件和二进制文件。
4.读文件:从磁盘上的文件中读取到计算机内存中
写文件:从计算机内存中的内容写入到文件中。
5.文件操作四步骤(文件FILE,和文件操作相关的函数是以f开头)
1> 打开文件:fopen
FILE *fopen(const char *path, const char *mode);
作用:打开文件或者创建文件
参数: path 需要打开或者创建的文件路径,默认是当前目录下,也是字符串
mode:打开或者创建的模式,是字符串
返回值:FILE的指针,文件指针,保存的是文件的基本信息(文件大小等)后续所有的函数操作都需要用到文件指针FILE*
模式如下:
r 只读的方式打开文件.文件指针在文件的开始位置,如果文件不存在则打开失败
r+ 以读写的方式打开文件,指针在文件开始的位置.如果文件不存在则打开失败
w 以写的方式打开文件,文件的长度初始化成0,所有内容会清空.如果文件不存在则创建.
w+ 读写的文件打开文件,如果文件存在则清空,不存在则创建
a 追加(只写)的方式打开文件,文件不存在则创建,光标在文件末尾;
a+ 读写的追加方式打开文件,光标在文件末尾,如果文件不存在则创建,存在不会清空。
读写文件:fgets fputs fgetc fputs fscanf fprintf fread fwrite
文件偏移:ftell fseek rewind
文件关闭:fclose
int fclose(FILE *stream);
作用:关闭文件
参数:stream是文件指针,是fopen的返回值。
C语言学习第八天
一、文本文件读写
1. int fgetc(FILE *stream);
作用:从文件中读取一个字符。(内存获取--从文件读出来)
参数:stream 是fopen的返回值。文件结构体。
返回值:如果成功获取到的字符的sacii值,如果失败返回EOF 就是-1;
请把danny.log文件的所有内容全部读取出来。
2. int fputc(int c, FILE *stream);
作用:写一个字符到文件中(内存中数据到文件中)
参数:c 是需要写入文件是字符ascii
stream 是fopen的返回值
返回值:如果成功返回写入的字符,失败返回EOF
补充: perror是打印错误信息,使用perror(字符串);
在文件中会有一个全局变量errno,记录的是最后一次的错误信息
错误信息对应一个字符串,perror会把字符串内容打印。
3. char *fgets(char *s, int size, FILE *stream);
作用: 从文件中读取一个字符串
参数:s 缓冲区,用来存放读取到的字符串。
size 大小,一次性读取的字符个数
stream :fopen的返回值
返回值:如果成功返回读取到的字符首地址,如果失败返回NULL
注意:实际读取到的字符是size-1个,会自动在后面加‘\0’;
4.int fputs(const char *s, FILE *stream);
作用:写入字符串到文件中
参数: s 需要写入的字符串首地址
stream:fopen的返回值
返回值:如果成功返回实际写入文件的字符个数,失败返回EOF
注意:写入的时候不会把字符串的'\0'写入到文件中
5. 在文件内部,有一个专门处理光标位置的指针,文件偏移指针
6.int fseek(FILE *stream, long offset, int whence);
功能:移动文件偏移指针到指定位置,
参数: offset:偏移多少字节,负数则往左偏移,整数往右偏移
whence: SEEK_SET 从文件头开始
SEEK_CUR 从当前位置开始
SEEK_END 从文件末尾开始
7. 函数原型:long ftell(FILE *stream);
功能:返回文件偏移指针位置
参数:fopen的返回值
返回值:光标所在位置,文件偏移指针位置。
8.void rewind(FILE *stream);
作用:把文件光标移动在文件头
参数:fopen返回值
返回值:无
练习:请写代码,把abc.log文件复制一份到abc-bak.log中(day08/copyfile.c)
9.int feof(FILE *stream);
作用:判断文件是否结束
参数: 文件结构体指针 fopen返回值
返回值:如果文件结束返回真,如果文件未结束返回假(0)
注意:错误信息保存在errno,可以用perror打印、
10. size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
作用:读二进制文件到内存中
参数: ptr 是缓冲区,用来存放读取的二进制内容,
size 每一块的大小
nmemb:读的块数,总共读取几块,总大小是size* nmemb
stream : 文件的结构体指针,fopen的返回值
返回值:实际读取到的块个数。小于等于nmemb的值。
11.size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
作用:写入二进制到文件
参数: ptr 是缓冲区,用来存放写入文件的内容,
size 写入每一块的大小
nmemb:写的块数,总共写入几块,总大小是size* nmemb
stream : 文件的结构体指针,fopen的返回值
返回值:实际写入文件的个数。小于等于nmemb的值。
### 12.int fscanf(FILE *stream, const char *format, ...); (自学内容)
作用:读取文件内容到内存中
参数:stream 文件指针 fopen返回值
format 格式化处理字符串
13. int fprintf(FILE *stream, const char *format, …); (自学) 例如:fprintf(fp,“%d %d”,a,b);
作用:写入内容到文件中
参数:stream 文件指针 fopen返回值
format 格式化处理字符串
练习: 在文件中写入3个结构体,结构体中有id age和name,请把读取到的所有name写入到另外一个文件中。把文化a中所有学生信息的名字提取出来放入文件 b中
未完结,持续更新中…