Hi,大家好,我是半亩花海。本文主要参考浙大翁恺老师的C语言讲解以及其他博主的C语言学习笔记,进而梳理C语言的基础知识,为后续系统性学习数据结构和其他语言等知识夯实一定的基础。(其他博主学习笔记的链接包括:C语言入门基础知识【完整版】_c语言基础知识入门-优快云博客和C语言基础知识入门大全-优快云博客以及C语言初阶——手把手教零基础/新手入门(万字心得笔记)_c语言入门自学零基础-优快云博客)
目录
一、数据类型和运算表达式
C语言中二进制数、八进制数和十六进制数的表示:
- 二进制:二进制由 0 和 1 两个数字组成,使用时必须以 0b 或 0B(不区分大小写)开头。例如:0b101(十进制的 5)、0B111(十进制的 7)。(注意:标准的C语言并不支持二进制写法,有些编译器自己进行了扩展,才会支持二进制数字)
- 八进制:八进制由 0~7 八个数字组成,使用时必须以数字 0 开头。例如:021(十进制的 17)、01010(十进制的 520)。
- 十六进制:十六进制由数字 0~9、字母 A~F 或 a~f(不区分大小写)组成,使用时必须以0x或0X(不区分大小写)开头。例如:0X2A(十进制的43)、0xffff(十进制的65535)。
1. 数据类型
(1)基本数据类型
- 整型
- 字符型
- 实型(浮点型):单精度(float)和双精度(double)
类型 | 类型说明符 | 字节 |
---|---|---|
字符型 | char | 1 Byte |
基本整型 | int | 4 Byte |
短整型 | short int | 2 Byte |
长整型 | long int | 4 Byte |
无符号整型 | unsigned int | 4 Byte |
无符号长整型 | unsigned long | 4 Byte |
单精度实型 | float | 4 Byte |
双精度实型 | double | 8 Byte |
注意:
- short int 和 long int 是根据编译环境的不同,所取范围不同。
- C语言 int 的取值范围在于他占用的字节数 ,不同编译器规定不一样(16位占2字节,32位或64位占4字节)。
(2)构造数据类型
- 枚举类型
- 数组类型
- 结构体类型
- 共用体类型
(3)指针类型
(4)空类型
2. 常量
C语言中常量的定义有两种方式,假如我们要定义一个int类型、名为TEMP、值为1的常量:
- 预定义命令(#define 定义的标识符常量):
#define TEMP = 1
- const关键字(const 修饰的常变量):
const int TEMP = 1
3. 运算表达式
(1)算术运算表达式
- 加:+
- 减:-
- 乘:*
- 除:/
- 取余:%
- 自增:++
- 自减:--
注意:
a++和++a(a--和--a)有区别,解释如下:
表达式 | 运算 | 表达式的值 |
a++ | 给a加1 | a++为a原来的值 |
++a | 给a加1 | a++为a+1以后的值 |
a-- | 给a减1 | a--为a原来的值 |
--a | 给a减1 | a--为a-1以后的值 |
#include <stdio.h>
int main(){
int a = 10;
printf("a++=%d\n", a++); //a原来的值
printf("a=%d\n", a); //a+1后的值
printf("++a=%d\n", ++a); //上述a+1后的值
printf("a=%d\n", a); //与++a的值一样
return 0;
}
(2)关系运算表达式
- 等于:==
- 大于:>
- 大于等于:>=
- 小于:<
- 小于等于:<=
- 不等于:!=
(3)逻辑运算符
C语言中 0 表示假,1 表示真。
- 与:&&
- 或:||
- 非:!
(4)位运算符
- 位与:&(对每一位进行逻辑与运算,0表示假,1表示真:0011 & 1111 = 0011)
- 位或:|(对每一位进行逻辑或运算,0表示假,1表示真:0011 | 1111 =1111)
- 位非:~(对每一位进行逻辑非运算,0表示假,1表示真:~1111 =0000)
- 位异或:^(对每一位进行逻辑异或运算,0表示假,1表示真:0011 ^ 1111 =1100)
- 左移:<<(高位溢出丢弃,低位不足补0:01100100 << 2 = 10010000)
- 右移:>>
- 正数:高位补0,低位溢出舍去:01111111 >> 4 = 00000111
- 负数:高位补1,低位溢出舍去:11111111 >> 4 = 11111111
二、语句
1. 条件判断语句
(1)简单if语句
如果表达式的值为真,则执行其后的语句,否则不执行该语句。
if(表达式){
执行代码块;
}
(2)if-else语句
如果表达式的值为真,则执行代码块1,否则执行代码块2。
if(表达式){
执行代码块1;
}
else{
执行代码块2;
}
(3)多重if-else语句
if(表达式a){
执行代码块a;
}
...
else if(表达式b){
执行代码块b;
}
...
else{
执行代码块c;
}
(4)嵌套if-else语句
if(表达式a){
if(表达式b){
执行代码块;
}
else{
执行代码块;
}
}
else{
执行代码块;
}
(5)switch语句
如果表达式的值等于常量1,执行下面的语句1;如果没有通过上面的开关语句退出,就会执行下面的语句n+1。(default可以省略break,因为它本身就是最后执行,执行完就会退出开关语句)。
注意:
switch语句如果没有break会一直向下执行直到结束。
switch(表达式){
case 常量1:
语句1;
break;
case 常量2:
语句2;
break;
case 常量a:
语句a;
break;
default:
语句b;
break; //break可以忽略
}
2. 循环执行语句
(1)while循环
计算表达式的值,当值为真(非0)时, 则执行循环体代码块。
while(循环条件){
执行代码块;
}
举个例子:
int main(){
int n = 0;
while(n <= 5) //条件判断
{
printf("a=%d\n", n); //花括号内为语句块
n++; //计数器放最后
}
return 0;
}
注意:
- 一定要记着在循环体中改变循环变量的值,否则会出现死循环(无休止的执行)。
- 循环体如果包括有一个以上的语句,则必须用{}括起来,组成复合语句。
(2)do-while循环
它先执行循环中的执行代码块,然后再判断while中表达式是否为真,如果为真则继续循环;如果为假,则终止循环。因此,do-while循环至少要执行一次循环语句。
do{
执行代码块;
}while(循环条件);
举个例子:
int i = 0;
do{
i++;
}while(...);
注意:使用do-while结构语句时,while括号后必须有分号。
(3)for循环
for(循环变量赋初值;循环条件;循环变量增量){
执行代码块;
}
举个例子:
int main(){
int i = 0;
for(i = 0; i <= 5; i++){
printf("a=%d\n", i);
}
return 0;
}
3. 转向语句
(1)break语句
中断语句,一般用于循环结构中,作用是终止循环,当执行到break语句时,会立即退出循环。
(2)continue语句
continue语句一般用于循环结构中,作用是跳过当次循环,当循环语句执行到continue时,不会继续向下执行,会跳过当次循环,直接执行下一次循环。
(3)return语句
跳出函数语句,用于跳出函数并返回一个值。
三、函数
1. 函数的定义
函数是实现了某种功能的代码块。函数的定义方式主要分为无参函数和有参函数。
//无参函数
类型标识符 函数名(){
声明部分;
语句;
}
//有参函数
类型标识符 函数名(形参1,形参2,形参3...形参n){
声明部分;
语句;
}
示例:下面定义了两个函数,第一个 HelloWorld 是无参函数,功能是输出一个 "Hello World!" 字符串,第二个 FindMax 是有参函数,接收两个 int 类型的参数,返回两个数中最大的那个数。
//函数的调用必须先定义声明,否则编译不通过
void HelloWorld(){ //返回类型为void,表示不返回任何值。
printf("Hello World!");
}
int FindMax(int a, int b){
int max; //声明一个整数变量max,用于存储比较后的最大值。
max = a >= b ? a : b; //使用条件运算符(三元运算符)比较a和b大小并将大值赋给max。
return max; //返回类型为int,表示返回一个整数值。
}
int main(){
HelloWorld(); //调用HelloWorld函数,会打印输出"Hello World!"。
int a = 5; //声明一个整数变量a,并赋值为5。
int b = 10; //声明一个整数变量a,并赋值为10。
int c; //声明一个整数变量c,用于存储最大值。
c = FindMax(a, b); //调用FindMax函数,传入a和b作为参数,将返回值赋给变量c。
printf("\n最大数为:%d\n", c); //使用%d占位符表示整数
return 0; //返回0,表示程序正常运行结束。
}
注意:
1)void是一种特殊的数据类型,表示“无类型”或“空类型”。当一个函数的返回类型是void时,该函数执行完毕后不会返回任何结果,或者说返回值为空。
2)三元运算符的语法结构如下:condition ? expression1 : expression2;
语义如下:如果条件为真(即condition为真),则返回expression1的值;如果条件为假(即condition为假),则返回expression2的值。
2. 函数的参数
- 形参:形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。
- 实参:实参在主调函数中,是调用函数时传递的参数。
- 参数传递:函数的参数由主调函数的实参传递给被调函数的形参,因此实参与形参的顺序、类型必须保持一致。
3. 全局变量与局部变量
(1)全局变量
全局变量也称为外部变量,它是在函数外部定义的变量。
在全局变量定义之前的函数中使用全局变量,需要使用关键字 extern
做全局变量说明,声明某个变量是全局变量,然后才能使用
int a = 5; //此处a为全局变量
int main(void){
int extern a; //全局变量说明,声明a是一个全局变量,此处在a定义之后,可以省略该说明
printf("%d", a); //输出结果为5
}
(2)局部变量
局部变量也称为内部变量。局部变量是函数内部定义的变量,作用域仅限于函数内部,局部变量只能在函数内部使用,函数外部无法访问。
int main(void){
int a = 5; //这是一个局部变量,a的作用域范围是main函数内,在函数外无法使用
print("%d", a);
a++;
}
print("%d", a); //全局作用域内找不到变量a,编译不通过
4. 静态变量与寄存器变量
(1)静态变量
静态变量是在函数调用结束后不消失而保留原值的变量,如果在一个函数调用结束后,希望它保留某个变量的值,就把这个变量用static
关键字声明为静态变量。
//定义一个自增函数,初始化局部静态变量a=0,每调用一次,a自增1
int Add(){
static int a = 0;
a++;
return a;
}
int main(){
print("%d", Add()); //输出结果为1
print("%d", Add()); //输出结果为2
return 0;
}
(2)寄存器变量
寄存器变量是放在CPU寄存器中的变量,CPU寄存器可以理解为CPU的内存空间,就像是电脑的内存一样,在寄存器中运算速度非常快,使用 register
关键字声明。
注意:
- 只有局部自动变量(非静态变量)和形参可以作为寄存器变量,局部静态变量不能定义为寄存器变量。
- 一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量。
#include "stdio.h"
//这是一个计算n的阶乘的函数,将局部变量i和f声明为寄存器变量
int fac(int n){
register int i, f = 1;
for (i = 1; i <= n; i++){
f = f * i;
}
return f;
}
int main(){
int i;
for (i = 0; i <= 5; i++){
printf("%d!=%d\n", i, fac(i));
}
return 0;
}
5. 预处理命令
(1)文件包含
文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件(头文件)。
文件包含的形式为:#include "文件名"
或 #include <文件名>
C语言常见头文件请详见附录1
。
(2)宏定义
C语言可以使用 #define
定义宏(类似常量),程序在编译处理时会把源程序中所有的宏名替换成宏定义的结果。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。
- 无参宏定义:
#define 标识符 字符串
(“字符串”可以是常数、表达式、格式串等)
#include <stdio.h>
#define PI 3.1415926
#define M (a+a)
int main(void){
double a = 1.0;
double b;
b = 2*M + PI; //等同于2*(a+a) + 3.1415926
printf("%f", b);
return 0;
}
- 带参宏定义:
#define 宏名(形参1,形参2,形参3,...形参n) 字符串
(“字符串”可以是常数、表达式、格式串等)
#include <stdio.h>
#define S(x,y) x*y //S表示矩形面积,x,y分别表示长宽
int main(void){
double a = 3.0,b = 4.0;
double s;
s = S(a,b); //等同于a*b
printf("%f", s);
return 0;
}
6. 常用函数
C语言中常用的函数有很多,以下是一些常见的函数:
- printf():用于输出数据到标准输出设备(通常是控制台),输出完结果后不会自动换行。
- scanf():用于从标准输入设备(通常是键盘)读取数据。
- putchar():用于输出字符,每次只能输出一个字符,若想输出多个字符则需使用多个putchar()函数,输出完结果后不会自动换行。
- getchar():用于获取字符,getchar()函数没有参数,且只能接收一个字符,若想输入多个字符则需使用多个getchar()函数。
- malloc():用于动态分配内存。
- free():用于释放动态分配的内存。
- strcpy():用于复制字符串。
- strlen():用于获取字符串的长度。
- strcat():用于将一个字符串追加到另一个字符串的末尾。
- strcmp():用于比较两个字符串。
- fopen():用于打开文件。
- fclose():用于关闭文件。
- fprintf():用于将数据输出到文件。
- fscanf():用于从文件中读取数据。
- abs():用于计算整数的绝对值。
- pow():用于计算一个数的指数幂。
- sqrt():用于计算一个数的平方根。
这些是C语言中常用的函数,还有很多其他函数可以根据具体需求使用。
例如,scanf() 和 printf() 函数如下所示。
# include <stdio.h> int main(void){ int a, b; printf("请输入两个整数:"); scanf("%d %d", &a, &b); //&a, &b表示变量a, b的地址,&是取地址符 printf("%d + %d = %d\n", a, b, a + b); return 0; }
结果如下:
再例如,putchar() 和 getchar() 函数如下所示。
#include <stdio.h> int main() { char a = 'A', b = 'W', c = 'M'; int d = 97, e = 99, f = 101; putchar(a); //输出字符常量 putchar(b); putchar(c); putchar(d); //输出整型常量,通过ASCII码值将int类型的数值转换为字符 putchar(e); putchar(f); putchar('\n'); //putchar()函数不能自动换行 putchar('\101'); //输出字符A putchar('\''); //输出单撇号,括号中的 \' 是转义字符,代表单撇号', putchar('\015'); //八进制数15等于十进制数13,13的ASCII码值表示的是回车 }
结果如下:
#include <stdio.h> int main() { char a, b, c; a = getchar(); b = getchar(); c = getchar(); putchar(a); putchar(b); putchar(c); printf("\n"); }
结果如下:
注意:
在 printf 中,所有的“非输出控制符”都要原样输出。而在 scanf 中,所有的“非输入控制符”都要原样输入。如下面的结果图所示,在输入的时候,“a=”和“b=”必须要原样输入;按代码所示,%d和%d之间也需输入一个空格,且不能使用逗号或其他符号代替,否则报错。
另外,在 scanf 函数中%d后面也没有必要加\n,因为在 scanf 中\n不起换行的作用;在 printf 函数中 \n 能起到换行的作用,否则就会将输出体现在同一行里。
四、指针
指针是指存储单元的地址,例如定义一个变量int a = 1;
指向变量a的指针就是a在内存中的地址。
1. 变量的指针和指针变量
(1)变量的指针
变量的指针:就是变量的内存地址。
(2)指针变量
- 指针变量的定义:类型说明符* 变量名
指针变量:存放变量地址的变量。(例如:有个变量a,变量a的地址是p,p就是变量a的指针。现在我们再假设一个变量b,然后把p赋值给变量b,那么变量b就是一个指针变量。)
int* a; //定义一个int类型的指针变量,指向的变量类型也必须是int
char* b; //定义一个char类型的指针变量,指向的变量类型也必须是char
double* c; //定义一个double类型的指针变量,指向的变量类型也必须是double
- 指针的操作
1)&:取地址运算符
&变量名:
表示取变量的地址(指针)。int a = 123; int* p = &a; //取变量a的地址赋值给指针变量p
2) *:指针运算符
*指针变量:
表示取指向的变量的值。int a = 123; int* p = &a; //取变量a的地址赋值给指针变量p printf("%d",*p); //输出123,*p表示取a的值
2. 数组的指针和指针数组
(1)数组的指针
一个数组是由连续的一块内存单元组成的。数组的指针是指数组的这块连续内存单元的首地址,即数组中第一个元素的地址。
int array[] = {1,2,3,4,5,6};
int* pA = array; //数组名就是数组的指针
int* pB = &array[0]; //数组的第一个元素的地址就是数组的指针
//指针pA和指针pB是相等的
(2)指针数组
一个数组的元素值为指针则是指针数组。
定义方式:类型说明符* 数组名[数组长度](跟普通数组定义方式相同,唯一区别是*)
int main(){
int a=1,b=2,c=3,d=4,e=5;
int* Int[5] = {&a,&b,&c,&d,&e}; // 这是一个整型指针数组
// 字符串在C语言中是字符数组,所以一个字符串相当于一个字符数组,字符串本身就等于字符数组的指针(首地址)
const char* String[] = {"Test1","Test2","Test3","Test4","Test5"}; // 这是一个字符型的指针数组
for (int i = 0; i < 5; ++i){
printf("%p\n",String[i]); // 这里输出的就是每个字符串的指针
}
return 0;
3. 字符串的指针
- C语言中是没有字符串类型的,C语言中的字符串都是用字符数组进行存储。
- 字符串的指针就是字符数组的指针,也就是字符数组的首地址。
C语言字符串的两种定义形式:
- 数组形式:char string[] = {'H','e','l','l','o','\0'}; 或 char string[] = "Hello";
- 指针形式:char* string = "Hello";(等价于{'H','e','l','l','o','\0'})
4. 函数的指针&指针型函数
(1)函数的指针
在C语言中一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址(即函数指针)。
- 函数指针的定义:类型说明符 (*指针变量名)(实参类型);
int (*p)(); //定义一个函数指针p
int Function(){
printf("test");
}
p = Function; //将Function函数的入口地址赋值给函数指针变量p
注意:函数指针的定义区别于变量指针。
- 函数指针的调用:(*指针变量名) (实参表);
int FindMax(int a, int b){
return a > b ? a : b;
}
int main(){
int (*p)(int, int) = FindMax;
int max = p(5,10);
printf("%d",max);
return 0;
}
(2)指针型函数
函数类型(即函数返回值的类型)是指针的函数就是指针型函数。
指针型函数的定义:
类型说明符* 函数名(参数){
执行语句;
return 对应类型的指针;
}
例如:下面定义了指针型函数,作用是随机生成一个数组,返回数组的指针。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int* GetNumber(){
static int array[10];
srand((unsigned)time(NULL));
for (int i = 0; i < 10; ++i){
array[i] = rand();
printf("%d\n",array[i]);
}
return array;
}
int main(){
int* p = GetNumber();
printf("==================\n");
for (int i = 0; i < 10; ++i){
printf("%d\n",p[i]);
}
5. 指向指针的指针
指向指针的指针,根据字面意思举个例子:假如有个变量a,变量a的指针用p1表示,将p1赋值给一个变量b,变量b的指针用p2表示,现在将p2赋值给一个变量c,变量c就是指向指针的指针。
int a = 2333;
int* b = &a;
int** c = &b;
要访问指向指针的指针的值,要使用**。如上面的指针c,访问方式为**c。
五、结构体和公用体
1. 结构体
结构体是由多个不同数据类型的成员组成的数据类型。结构体中的每个成员可以有不同的数据类型和命名。使用结构体可以将多个不同数据类型的信息组合成一个单一的逻辑单元(实现面向对象编程),从而方便地进行操作。
(1)结构体的定义
- 定义结构体关键字:
struct
- 定义形式:
struct 结构名{成员数据};
//下面定义了一个名为Person的结构体
//Person包含有一个人的姓名、年龄、性别、身高、住址信息
struct Person{
char* name;
int age;
char sex;
double height;
char address[200];
};
(2)结构体的用法
- 结构体成员变量的表示方法:
结构名.变量名
或(*结构指针).变量名/(*结构指针)->变量名
- 结构体变量的赋值:直接给成员变量赋值,注意数组类型不能直接赋值。
#include <stdio.h>
#include <string.h>
// 下面定义了一个名为Person的结构体,Person包含有一个人的姓名、年龄、性别、身高、住址信息
struct Person{
char* name;
int age;
char sex;
float height;
char address[200];
};
int main(){
struct Person man;
struct Person woman;
struct Person* pW = &woman;
man.name = "小明"; // 结构体变量赋值
man.sex = 'M';
man.age = 18;
man.height = 1.78f;
strcpy(man.address,"四川省成都市");
(*pW).name = "小红"; // 结构体变量赋值
(*pW).sex = 'W';
pW->age = 19;
pW->height = 1.68f;
strcpy(pW->address,"四川省绵阳市"); // 数组类型不能直接赋值
printf("姓名:%s\n年龄:%d\n性别:%c\n身高:%.2fm\n地址:%s\n",man.name,man.age,man.sex,man.height,man.address);
printf("姓名:%s\n年龄:%d\n性别:%c\n身高:%.2fm\n地址:%s\n",woman.name,woman.age,woman.sex,(*pW).height,pW->address);
return 0;
}
2. 共用体
共用体是一种特殊的结构体,其所有成员共享相同的内存空间。共用体中的每个成员可以有不同的数据类型,但是它们共享相同的内存空间,因此只能同时存在一个成员的值。
共用体的主要用途是在不同的数据类型之间进行类型转换或节省内存空间。
(1)共用体的定义
- 定义结构体关键字:
union
- 定义形式:
union 共用体名{成员数据};
(2)共用体的用法
#include <stdio.h>
#include <string.h>
union data{
int i;
float f;
char* s;
char c;
};
int main(){
union data temp; // 定义一个共用体temp
temp.i = 10;
printf("temp = %d\n",temp.i);
printf("data中i的内存地址:%p\n",&temp.i);
printf("data中f的内存地址:%p\n",&temp.f);
printf("data中s的内存地址:%p\n",&temp.s);
printf("data中c的内存地址:%p\n",&temp.c);
// 可以看出共用体的所有成员指向的是同一块内存空间
printf("==================================\n");
temp.s = "测试";
printf("temp = %s\n",temp.s);
printf("data中i的内存地址:%p\n",&temp.i);
printf("data中f的内存地址:%p\n",&temp.f);
printf("data中s的内存地址:%p\n",&temp.s);
printf("data中c的内存地址:%p\n",&temp.c);
printf("==================================\n");
temp.f = 3.14159f;
printf("temp = %f\n",temp.f);
printf("data中i的内存地址:%p\n",&temp.i);
printf("data中f的内存地址:%p\n",&temp.f);
printf("data中s的内存地址:%p\n",&temp.s);
printf("data中c的内存地址:%p\n",&temp.c);
printf("==================================\n");
return 0;
}
通过上面的例子,如果把temp看做一个没有定义类型的变量,那么他就是个可变类型的变量。
六、动态内存分配
C语言常用的内存管理函数有四个:malloc
、calloc
、realloc
、free
。其中申请空间的函数是malloc
、calloc
;重新调整空间大小的函数是realloc
;释放空间的函数是free。
以 malloc() 函数为例
malloc 只开辟空间(开辟的是用户所需求的字节数大小的空间),且不进行类型检查,只是在使用的时候进行类型的强制转换(malloc() 函数返回的实际是一个无类型指针,必须在其前面加上指针类型强制转换才可以使用)。
malloc() 函数使用形式: 指针自身 = (指针类型 *)malloc(sizeof(指针类型) * 数据数量)
例如:分配一块空间存储指定个数的数字,并对数字求和。
#include <stdio.h> #include <stdlib.h> int main() { int* ptr; //定义一个指针变量 int n, sum = 0; //初始化元素个数与元素总和 printf("输入要保存的元素个数: "); scanf("%d", &n); ptr = (int*) malloc(n * sizeof(int)); //申请一片连续的存储空间,强制转换为int类型指针 if(ptr == NULL) { printf("内存空间分配失败!\n"); exit(1); } printf("输入保存的元素:\n"); for(int i = 0; i < n; i++) { scanf("%d", &ptr[i]); sum += ptr[i]; } printf("所有元素累加总和为:%d\n", sum); free(ptr); //释放内存空间ptr return 0; }
附录1:C语言常见头文件
C语言中常用的头文件包括但不限于以下几个:
<stdio.h>
:提供了输入输出函数(Sandard Input and Output Header,标准输入输出头文件),如printf
,scanf
,fopen
等。<stdlib.h>
:提供了常用的实用函数(Standard Library Header,标准库头文件),如内存分配函数malloc
,free
, 随机数生成函数rand
等。<string.h>
:提供了字符串处理函数,如strlen
,strcpy
,strcat
等。<math.h>
:提供了数学函数库,如三角函数sin
,cos
,sqrt
等。<time.h>
:提供了与时间有关的函数,如time
,用于获取当前时间。<ctype.h>
:提供了字符处理函数,如tolower
,toupper
, 测试字符类别的函数isalpha
等。<stdbool.h>
:提供了布尔类型bool
,true
,false
。<limits.h>
:提供了有关实现常量的信息,如INT_MAX
,INT_MIN
等。
使用这些头文件通常只需在程序的开始部分加上相应的包含指令即可,例如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <ctype.h>
#include <stdbool.h>
#include <limits.h>
附录2:文件操作
文件是一个有序数据集,数据集的名称叫文件名。文件分为两种,一种是普通文件,比如txt文件、C语言的源程序文件、头文件等等存在于磁盘上的;另一种是设备文件,比如鼠标、键盘、显示器等等外部设备,都认为是一个文件。
1. 文件指针
C语言使用一个指针变量指向一个文件,通过操作指针来操作文件。
文件指针的定义:FILE *变量名;(FILE 实际上是系统定义的一个结构体,该结构体中含有文件名、文件状态、文件当前位置等信息)
注意:
文件指针和文件位置指针需要区别开:文件指针指向的是整个文件,而文件位置指针指的是文件中所处位置的指针(头部、当前位置、末尾等)。
2. 操作文件的函数
(1)打开与关闭
ANSIC 规定用 fopen 来打开文件,用 fclose 来关闭文件:
- fopen:打开一个文件,成功则返回文件的指针,失败则返回空指针
NULL
- fclose:关闭一个文件,成功则返回0,失败则返回非0
函数原型如下:
FILE * fopen ( const char * filename, const char * mode ); //打开文件,fopen("文件名", "打开模式")
int fclose ( FILE * stream ); //关闭文件
- path:文件路径。
-
mode:打开的模式。mode主要由以下6个字符组合而成:
r:可读(文件位置指针在文件头部,文件必须存在)
w:可写(文件位置指针在文件头部,文件存在则清空内容,不存在就创建)
a:追加写入(文件位置指针在文件尾部,文件必须存在)
b:二进制方式打开
+:可读写
t:文本模式(默认,可省略) -
mode的常用模式:
选项 说明 r 只读打开一个文本文件,只允许读数据 w 只写打开一个文本文件,只允许写数据 a 追加写入打开一个文本文件,在文件末尾写数据 rb 以二进制方式打开一个文件,只允许读数据 wb 以二进制方式打开一个文件,只允许写数据
(2)文件读写
文件结束符:EOF
文件写入的函数需要以写或者读写模式打开文件,文件读取的函数需要以读或者读取的模式打开文件。读取或写入操作之后,位置指针都会向后移动到读取或写入位置的末尾。
函数名 | 功能 | 适用性 |
fgetc() | 字符输入函数 | 所有输入流 |
fputc() | 字符输出函数 | 所有输出流 |
fgets() | 文本行输入函数 | 所有输入流 |
fputs() | 文本行输出函数 | 所有输出流 |
fscanf() | 格式化输入函数 | 所有输入流 |
fprintf() | 格式化输出函数 | 所有输出流 |
fread() | 二进制输入 | 文件 |
fwrite() | 二进制输出 | 文件 |
1)fgetc:从文件读取一个字符
- 函数原型:int fgetc(FILE *file);
- file:目标文件的指针
- 返回值:返回 int 类型的 ASCII 码,位置指针向后移动一个字节
- 使用方法:fgetc(文件指针);
2)fputc:向文件中写入一个字符
- 函数原型:int fputc(int c, FILE *file);
- c:要写入的字符(char 或者 int 类型 ASCII 码)
- file:目标文件的指针
- 返回值:成功返回写入的字符,位置指针向后移动一个字节;失败返回 EOF
- 使用方法:fputc('a', 文件指针);
3)fgets:从文件读取一个字符串到字符数组中
- 函数原型:char* fgets(char *Buffer, int MaxCount, FILE *file );
- Buffer:字符数组的指针
- MaxCount:最大读取字符数
- file:目标文件的指针
- 说明:
- MaxCount是一个正整数,表示从文件中读出的字符串不超过 MaxCount-1个字符。在读入的最后一个字符后加上串结束标志 \0。
- 在读出MaxCount-1个字符之前,如遇到了换行符或EOF,则读出结束。
- 返回值:字符数组的首地址
- 使用方法:fgets(数组首地址, 字符串最大长度, 文件指针);
4)fputs:将一个字符串写入到文件中,不包含’\0’
- 函数原型:int fputs(const char *str, FILE *file);
- str:要写入的字符数组(字符串)的指针
- file:目标文件的指针
- 返回值:成功返回非负整数;失败返回 EOF(符号常量,其值为-1)
- 使用方法:fputs(字符串, 文件指针);
5)fread:从文件中读取一组固定大小的数据到内存空间
- 函数原型:size_t fread(void *Buffer, size_t size, size_t count, FILE *file);
- Buffer:内存空间首地址(用来存放数据的内存空间指针)
- size:数据块的大小
- count:数据块的数量
- file:目标文件的指针
- 返回值:返回成功读取的对象个数(若出现错误或到达文件末尾,则可能小于count)
- 使用方法:fread(内存空间地址, 数据块大小, 数据块数量, 文件指针);
6)fwrite:写入一组固定大小的数据到文件中
- 函数原型:size_t fwrite(const void *Buffer, size_t size, size_t count, FILE *file);
- Buffer:要存入的数据的首地址
- size:数据块的大小
- count:数据块的数量
- file:目标文件的指针
- 返回值:返回成功写入的对象个数(若出现错误或到达文件末尾,则可能小于 count)
- 使用方法:fwrite(数据地址, 数据块大小, 数据块数量, 文件指针);
7)fscanf:从文件中获取指定格式的数据,跟scanf类似,输入对象换成了普通文件
- 函数原型:int fscanf(FILE *file, const char *str, [arg...]);
- file:目标文件的指针
- str:格式化字符串
- [arg…]:一个或多个接收数据的地址
- 说明:fscanf 遇到空格和换行时结束
- 返回值:成功返回读入参数的个数,失败返回 EOF
- 使用方法:fscanf(文件指针, 格式化字符串, 目标地址);
8)fprintf:格式化输出数据到文件,跟printf类似,输出对象换成了普通文件
- 函数原型:int fprint(FILE *file, const char *str, [arg...]);
- file:目标文件的指针
- str:格式化字符串
- [arg…]:一个或多个数据
- 说明:fprintf 会根据参数 str 字符串来转换并格式化数据,然后将结果输出到参数file指定的文件中,直到出现字符串结束(\0)为止。
- 返回值:成功返回输出数据的个数,失败返回 EOF
- 使用方法:fprintf(文件指针, 格式化字符串, 目标数据);
(3)文件操作实例
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE *fp = fopen("test.txt", "w+"); // 以读写模式打开一个文件
if (fp == NULL) {
printf("文件打开失败!");
exit(1);
}
fputc('a', fp); // 向文件写入一个字符'a'
rewind(fp); // 将文件位置指针放到文件头部,因为我们刚刚向文件写入了一个字符'a',所以现在文件位置指针指向的文件尾部
char ch = (char)fgetc(fp); // 从文件读取一个字符,现在文件中只有一个'a',读取的字符就是'a'
printf("%c\n",ch);
printf("结束位置:%d\n", feof(fp)); // 看看位置指针是不是在结束位置
fclose(fp); //关闭文件
return 0;
}