C 语言基础知识点整理
C 语言基础知识点整理
tags
C/C++
未整理
date
Jan 12, 2020
source
status
未整理
1. 开始1.1 include头文件包含1.2 main函数1.3 注释1.4 大括号,程序体和代码块1.5 声明1.6 printf函数1.7 return语句1.8 system系统调用1.9 C语言编译过程、GCC参数简介1.9.1 C语言编译过程1.9.2 -E预编译1.9.3 -S汇编1.9.4 -c编译1.9.5 链接1.10 操作系统结构简述1.10.1 用户模式1.10.2 内核模式1.11 32位与64位系统的区别1.11.1 CPU内部结构与寄存器1.11.2 RISC与CISC的CPU架构1.11.3 SPARC、x86与ARM1.12 汇编语言1.12.1 I386汇编简介1.12.2 Visual Studio反汇编2.数据类型2.1 常量2.1.1 #define2.1.2 const2.1.3 字符串常量2.2 二进制数、位、字节与字2.3 八进制2.4 十六进制2.5 原码2.6 反码2.7 补码2.8 sizeof关键字2.9 int类型2.9.1 int常量、变量2.9.2 printf输出int值2.9.3 printf输出八进制和十六进制2.9.4 short、long、long long、unsigned int2.9.5 整数溢出2.9.6 大端对齐与小端对齐2.10 char类型2.10.1 char常量、变量2.10.2 printf输出char2.10.3 char的转义符2.10.4 char和unsigned char2.11 float、double、long double浮点类型2.11.1 浮点常量、变量2.11.2 printf输出浮点数2.12 类型限定2.12.1 const2.12.2 volatile2.12.3 register3. 字符串格式化输入与输出3.1 字符串在计算机内部的存储方式3.2 printf函数、putchar函数3.3 scanf函数与getchar函数4. 运算符表达式语句4.1 基本运算符4.1.1 =4.1.2 +4.1.3 –4.1.4 *4.1.5 /4.1.6 %4.1.7 +=4.1.8 -=4.1.9 *=4.1.10 /=4.1.11 %=4.1.12 ++4.1.13 –4.1.14 逗号运算符4.1.15 运算符优先级4.2 复合语句4.3 空语句4.4 类型转换5.1 关系运算符5.1.1 <5.1.2 <=5.1.3 >5.1.4 >=5.1.5 ==5.1.6 !=5.2 关系运算符优先级5.3 逻辑运算符5.3.1 &&5.3.2 ||5.3.3 !5.4 if5.5 if else5.6 if else if5.7 switch、break与default5.8 三目运算符5.9 goto语句与标号6.1 while6.2 continue6.3 break6.4 do while6.5 for6.6 循环嵌套7.1 一维数组定义与使用7.2 数组在内存的存储方式7.3 一维数组初始化7.4 二维数组定义与使用7.5 二维数组初始化8. 字符串与字符数组8.1 字符数组定义8.2 字符数组初始化8.3 随机数产生函数rand与srand8.4 用scanf输入字符串8.5 字符串的结束标志8.6 字符串处理函数8.7.1 gets8.7.2 fgets函数8.7.3 puts函数8.7.4 fputs函数8.7.5 strlen字符串长度8.7.6 strcat字符串追加8.7.7 strncat字符串有限追加8.7.8 strcmp字符串比较8.7.9 strncmp字符串有限比较8.7.10 strcpy字符串拷贝8.7.11 strncpy字符串有限拷贝8.7.12 sprintf格式化字符串8.7.13 Sscanf函数8.7.14 strchr查找字符8.7.15 strstr查找子串8.7.16 strtok分割字符串8.7.17 atoi转化为int8.7.18 atof转化为float8.7.19 atol转化为long9.函数9.1 函数的原型和调用9.2 函数的形参与实参9.3 函数的返回类型与返回值9.4 main函数、exit函数与函数的return语句9.5 多个源代码文件程序的编译9.5.1 头文件的使用9.5.2 #include与#define的意义9.5.3 #ifndef与#endif9.6 函数的递归9.6.1 递归的过程分析9.6.2 递归的优点9.6.3 递归的缺点10.1 指针的概念10.2 指针变量的定义10.3 &取地址运算符10.4 无类型指针10.5 NULL10.6 空指针与野指针10.7 指针的兼容性10.8 指向常量的指针与指针常量10.9 指针与数组的关系10.10 指针运算10.11 通过指针使用数组元素10.12 指针数组10.13 指向指针的指针(二级指针)10.14 指向二维数组的指针10.15 指针变量做为函数的参数10.16 一维数组名作为函数参数10.17 二维数组名作为函数参数10.18 const关键字保护数组内容10.19 指针做为函数的返回值10.20 指向函数的指针10.21 把指向函数的指针做为函数的参数10.22 memset、memcpy和memmove函数10.23 指针小结11.字符指针与字符串11.1 指针和字符串11.2 通过指针访问字符串数组11.3 函数的参数为char *11.4 指针数组做为main函数的形参12.内存管理12.1 作用域12.1.1 auto自动变量12.1.2 register寄存器变量12.1.3 代码块作用域的静态变量12.1.4 代码块作用域外的静态变量12.1.5 全局变量12.1.6 外部变量与extern关键字12.1.7 全局函数和静态函数12.2 内存四区12.2.1 代码区12.2.2 静态区12.2.3 栈区12.2.4 堆区12.3 堆的分配和释放12.3.1 malloc12.3.2 free12.3.3 calloc12.3.4 realloc13. 结构体、联合体、枚举与typedef13.1 结构体13.1.1 定义结构体struct和初始化13.1.2 访问结构体成员13.1.3 结构体的内存对齐模式13.1.4 指定结构体元素的位字段13.1.5 结构数组13.1.6 嵌套结构13.1.7 结构体的赋值13.1.8 指向结构体的指针13.1.9 指向结构体数组的指针13.1.10 结构中的数组成员和指针成员13.1.11 在堆中创建的结构体13.1.12 将结构作为函数参数13.1.13 指向结构的指针13.2 联合体13.3 枚举类型13.3.1 枚举定义13.3.2 默认值13.4 typedef13.5 通过typedef定义函数指针14.文件操作14.1 fopen14.2 二进制和文本模式的区别14.3 fclose14.4 getc和putc函数14.5 EOF与feof函数文件结尾14.6 fprintf、fscanf、fgets和fputs函数14.7 stat函数14.8 fread和fwrite函数14.9 fread与feof14.10 通过fwrite将结构保存到二进制文件中14.11 fseek函数14.12 ftell函数14.13 fflush函数14.14 remove函数14.15 rename函数References

1. 开始

1.1 include头文件包含

include是要告诉编译器,包含一个头文件。在C语言当中,任何库函数调用都需要提前包含头文件。
  • #include <头文件>,代表让C语言编译器去系统目录下寻找相关的头文件
  • #include "头文件",代表让C语言编译器去用户当前目录下寻找相关头文件
如果是使用了一个C语言库函数需要的头文件,那么一定是#include <>。如果使用了一个自定义的h文件,那么一定是#include ""

1.2 main函数

main函数是C语言中的主函数,一个C语言的程序必须有一个主函数,也只能有一个主函数。

1.3 注释

//,单行注释,代表注释,就是一个文字说明,没有实质的意义,单行注释是C++语言的注释方法。
/* */,多行注释,多行注释是标准C语言的注释方法。

1.4 大括号,程序体和代码块

C语言所有的函数的代码都是在{}里包着的。

1.5 声明

int a;
声明一个变量名字叫a,变量的名称是可以自定义的,但要遵循一些要求:
  • 可以使用大小写字母,下划线,数字,但第一个字母必须是字母或者下划线
  • 字母区分大小写
  • 不能用C语言的关键字做为变量名称
每一行语句,必须是分号;结尾。

1.6 printf函数

printf()是向标准输出设备输出字符串的。
  • 如果要输出一个字符串:printf("hello world");
  • 如果要输出一个整数:printf("%d", 整数);
  • printf("\n");会输出一个回车换行

1.7 return语句

一个函数遇到return语句就终止了,return是C语言中的关键字。

1.8 system系统调用

system库函数的功能是执行操作系统的命令或者运行指定的程序,system库函数的调用需要#include <stdlib.h>

1.9 C语言编译过程、GCC参数简介

1.9.1 C语言编译过程

notion image
img

1.9.2 -E预编译

gcc –E –o a.e a.c
此命令为预编译a.c文件,生成的目标文件名为a.e
预编译主要是进行宏展开等步骤,比如将include包含的头文件内容替换到C文件中中,同时将代码中没用的注释部分删除。
我们需要注意,-o本质上是一个重命名选项。无论有没有-o选项,最后都会执行链接的步骤。 当不使用-o选项时,执行命令gcc a.c,生成的是默认的a.out文件。这个名字不方便使用,所以一般我们用-o重命名一下,比如使用命令gcc a.c -o a,也可以生成可执行的a文件。

1.9.3 -S汇编

gcc -S a.c
  • S就是将a.c文件中的C语言转化为汇编语言,生成a.s

1.9.4 -c编译

gcc -c a.c
将代码编译为二进制的机器指令。

1.9.5 链接

gcc没有任何参数,代表就是链接。

1.10 操作系统结构简述

notion image
img

1.10.1 用户模式

应用程序都是运行在用户区域。

1.10.2 内核模式

操作系统的内核,设备驱动程序,这些都是在内核模式下运行的。

1.11 32位与64位系统的区别

1.11.1 CPU内部结构与寄存器

notion image
img

1.11.2 RISC与CISC的CPU架构

  • RISC精简指令集
  • CISC复杂指令集,一般来讲x86构架的CPU都是复杂指令的,AMD、intel就是x86构架的,linux是基于x86的操作系统

1.11.3 SPARC、x86与ARM

Sun公司开发的CPU,是基于SPARC,其实就是一款RISC的CPU。

1.12 汇编语言

1.12.1 I386汇编简介

Mov eax, 10
Add eax, 10
Sub eax, 20
Call printf

1.12.2 Visual Studio反汇编

先f9设置一个断点,F5,用调试方式运行代码。

2.数据类型

2.1 常量

常量就是在程序运行中不可变化的量,常量在定义的时候必须给一个初值。

2.1.1 #define

定义一个宏常量。

2.1.2 const

定义一个const常量。

2.1.3 字符串常量

"hello world"
对于#define类型的常量,C语言的习惯是常量名称为大写,但对于普通const常量以及变量,一般为小写结合大写的方式。

2.2 二进制数、位、字节与字

我们习惯于十进制的数:10,12等。
  • 一个位只能表示0,或者1两种状态,简称bit,一个位是一个bit
  • 一个字节为8个二进制,称为8位,简称BYTE,8个比特是一个字节
  • 一个字为2个字节,简称WORD
  • 两个字为双字,简称DWORD

2.3 八进制

0666
八进制为以8为基数的数制系统,C语言当中用0来开头表示这是一个八进制数。

2.4 十六进制

0x16A2D
十六进制值为以16为基数的数制系统,C语言中用0x来开头表示这是一个十六进制数。
  • 十进制转化八进制:用十进制数作为被除数,8作为除数,取商数和余数,直到商数为0的时候,将余数倒过来就是转化后的结果
  • 十进制转化十六进制,用十进制数作为被除数,16作为除数,取商数和余数,直到商数为0的时候,将余数倒过来就是转化后的结果
十进制
十六进制
二进制
0
0
1
1
2
10
3
11
4
100
5
101
6
110
7
111
8
1000
9
1001
10
1010
11
1011
12
1100
13
1101
14
1110
15
1111

2.5 原码

将最高位做为符号位(0代表正,1代表负),其余各位代表数值本身的绝对值。
  • +7的原码是0000 0111
  • 7的原码是1000 0111
  • +0的原码是0000 0000
  • 0的原码是1000 0000

2.6 反码

一个数如果值为正,那么反码和原码相同。
一个数如果为负,那么符号位为1,其他各位与原码相反。
  • 7的反码0000 0111
  • 7的反码1111 1000
  • 0的反码1111 1111

2.7 补码

原码和反码都不利于计算机的运算,如:原码表示的7和-7相加,还需要判断符号位,所以有了补码。
  • 正数:原码,反码补码都相同
  • 负数:最高位为1,其余各位原码取反,最后对整个数 + 1
7的补码:
  • 1000 0111(原码)
  • 1 1111 1000(反码)
  • 1111 1001(补码)
+0的补码为0000 0000,-0的补码也是0000 0000
补码符号位不动,其他位求反,最后整个数 + 1,得到原码
用补码进行运算,减法可以通过加法实现:
如7-6=1:
  1. 7的补码和-6的补码相加:0000 0111 + 1111 1010 = 1 0000 0001
  1. 进位舍弃后,剩下的0000 0001就是1的补码
如-7+6=-1:
  1. 7的补码和6的补码相加:1111 1001 + 0000 0110 = 1111 1111
11111111是-1的补码。

2.8 sizeof关键字

sizeof a;
sizeof(a);
sizeof是C语言关键字,功能是求指定数据类型在内存中的大小,单位:字节

2.9 int类型

2.9.1 int常量、变量

int类型存储的是32位的一个二进制整数,在内存当中占据4个字节的空间。

2.9.2 printf输出int值

printf("%d", 1);printf("%u", -1);
%d输出一个有符号的10进制整数,%u代表输出一个无符号的十进制整数。

2.9.3 printf输出八进制和十六进制

printf("%x", 16842);        // 0x41caprintf("%X", 16842);        // 0x41CAprintf("%o", 16842);        // 040712
%x代表输出16进制数,%X用大写字母方式输出16进制数,%o代表输出八进制数。

2.9.4 short、long、long long、unsigned int

  • short意思为短整数,在32位系统下是2个字节,16个比特
  • long意思为长整数,在32位的系统下,long都是4个字节的,在64位系统下,windows还是4个字节,unix下成了8个字节
  • int不管是32位系统下,还是64位系统下,不论是windows还是unix都是4个字节的
  • long long是64位,也就是8个字节大小的整数,对于32位操作系统,CPU寄存器是32位,所以计算long long类型的数据,效率很低

2.9.5 整数溢出

计算一个整数的时候超过整数能够容纳的最大单位后,整数会溢出,溢出的结果是高位舍弃。当一个小的整数赋值给大的整数,符号位不会丢失,会继承。

2.9.6 大端对齐与小端对齐

对于ARM,Intel这种x86构架的复杂指令集CPU,整数在内存中是倒着存放的,低地址放低位,高地址放高位,小端对齐。但对于unix服务器的CPU,更多是采用大端对齐的方式存放整数。
notion image
img

2.10 char类型

2.10.1 char常量、变量

char c;
定义一个char变量。
'a'
定义一个char的常量。
char的本质就是一个整数,一个只有1个字节大小的整数。

2.10.2 printf输出char

printf("%c", c);
%c意思是输出一个字符,而不是一个整数。

2.10.3 char的转义符

  • \a警报
  • \b退格
  • \n换行
  • \r回车
  • \t制表符
  • \\斜杠
  • \'单引号
  • \"双引号
  • \?问号

2.10.4 char和unsigned char

char取值范围为-128到127,unsigned char为0-255。

2.11 float、double、long double浮点类型

2.11.1 浮点常量、变量

float在32位系统下是4个字节,double在32位系统下是8个字节。
小数的计算效率很低,避免使用,除非明确的要计算一个小数。

2.11.2 printf输出浮点数

printf("%f", a);
%f是输出一个double
printf("%lf", a);
%lf输出一个long double

2.12 类型限定

2.12.1 const

const是代表一个不能改变值的常量。

2.12.2 volatile

volatile代表变量是一个可能被CPU指令之外的地方改变的,编译器就不会针对这个变量去优化目标代码。
notion image
img

2.12.3 register

register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度,而不是将其储存在内存里面。但register只是建议型的指令,而不是命令型的指令,不一定保证能成功将其保存在CPU的寄存器中。

3. 字符串格式化输入与输出

3.1 字符串在计算机内部的存储方式

字符串是内存中一段连续的char空间,以'\0'结尾。
notion image
img
  • ''是C语言表达单字符的方式
  • ""是C语言表达字符串的方式

3.2 printf函数、putchar函数

putchar是向控制台输出一个字符:
putchar('a');
printf格式字符:
字符
对应数据类型
含义
int
接受整数值并将它表示为有符号的十进制整数
Short int
短整数
Unsigned short int
无符号短整数
unsigned int
无符号8进制整数
unsigned int
无符号10进制整数
unsigned int
无符号16进制整数,x对应的是abcdef,X对应的是ABCDEF
double
科学计数法表示的数,此处「e」的大小写代表在输出时用的「e」的大小写
char
字符型。可以把输入的数字按照ASCII码相应转换为对应的字符
char 或wchar_t
字符串。输出字符串中的字符直至字符串中的空字符(字符串以'\0'结尾,这个'\0'即空字符)
void *
以16进制形式输出指针
%
输出一个百分号
printf附加格式:
字符
含义
附加在d、u、x、o前面,表示长整数
左对齐
数据最小宽度
将输出的前面补上0,直到占满指定列宽为止(不可以搭配「-」使用)
宽度至少为n位,不够以空格填充。

3.3 scanf函数与getchar函数

scanf通过键盘读取用户输入,放入变量中,记得参数一定是变量的地址(取地址符号:&
int a = 0;scanf("%d", &a);
getchar得到一个用户键盘输入的字符:
getchar();

4. 运算符表达式语句

4.1 基本运算符

4.1.1 =

赋值操作。
左值:表示可以被更改的数据对象
右值:能赋给左值的量
数据对象:泛指数据在内存的存储区域

4.1.2 +

加法运算。

4.1.3 –

减法运算。

4.1.4 *

乘法运算。

4.1.5 /

除法运算。

4.1.6 %

求模取余运算。

4.1.7 +=

加等于操作,进行加法运算后再进行赋值操作。

4.1.8 -=

减等于操作,进行减法运算后再进行赋值操作。

4.1.9 *=

乘等于操作,进行乘法运算后再进行赋值操作。

4.1.10 /=

除等于操作,进行除法运算后再进行赋值操作。

4.1.11 %=

取余等于操作,进行取余运算后再进行赋值操作。

4.1.12 ++

自身+1,自增操作:
  • i++:先计算表达式的值,然后再++
  • ++i:先++,再计算表达式的值

4.1.13 –

自身-1,自减操作:
  • i--:先计算表达式的值,然后再-
  • -i:先-,再计算表达式的值

4.1.14 逗号运算符

int a = 2;int b = 3;int c = 4;int d = 5;int i = (a = b, c + d);
逗号表达式先求逗号左边的值,然后求右边的值,整个语句的值是逗号右边的值。

4.1.15 运算符优先级

优先级
运算符
结合性
1
++(后缀)、--(后缀)、()(调用函数)、{}(语句块)、.->
从左到右
2
++(前缀)、--(前缀)、+(前缀)、-(前缀)、!(前缀)、~(前缀)、sizeof*(取指针值)、&(取地址)、(type)(强制类型转换)
从右到左
3
*/%
从左到右
4
+-
从左到右
5
<<>>
从左到右
6
<><=>=
从左到右
7
==!=
从左到右
8
&
从左到右
9
^
从左到右
10
|
从左到右
11
&&
从左到右
12
||
从左到右
13
?
从右到左
14
=*=%=+=-=<<=>>=&=|=^=
从右到左
15
,(逗号运算符)
从左到右

4.2 复合语句

{}代码块包裹。

4.3 空语句

只有一个;号的语句就是空语句,空语句在C语言里面和合法的,并且是在某些场合必用的。

4.4 类型转换

double f = (double)3 / 2;
()为强制类型转换运算符。

5.1 关系运算符

5.1.1 <

小于。

5.1.2 <=

小于等于。

5.1.3 >

大于。

5.1.4 >=

大于等于。

5.1.5 ==

等于(判断相等)。

5.1.6 !=

不等于(判断不相等)。

5.2 关系运算符优先级

前四种相同,后两种相同,前四种优先级高于后两种。

5.3 逻辑运算符

5.3.1 &&

逻辑与运算符,当运算符左右都是真的时候,那么整个表达式的结果为真。只要左右有一个值为假,那么整个表达式的结果为假。
true && true;   // turetrue && false;  // falsefalse && true;  // falsefalse && false; // false

5.3.2 ||

逻辑或运算符,当运算符左右只要有一个值是真的时候,那么整个表达式的结果为真。除非左右两个值都是假,那么整个表达式的结果为假。
true || true;   // turetrue || false;  // turefalse || true;  // turefalse || false; // false

5.3.3 !

取反(非)运算符,当值为真的时候,表达式为假;当值为假的时候,表达式为真。
!true;  // false!false; // true

5.4 if

单分支:
if (条件){    // 复合语句}
只有当条件是真的时候,复合语句才能被执行,如果条件为假的时候,复合语句不执行。

5.5 if else

双分支:
if (条件){    // 复合语句1}else{    // 复合语句2}
如果条件为真,那么执行复合语句1,否则执行复合语句2。

5.6 if else if

多重if
if (条件1){    // 复合语句1}else if (条件2){    // 复合语句2}else if (条件3){    // 复合语句3}else{    // 复合语句4}
当有多个else的时候,else总是和上方最近的那个if语句配对。

5.7 switch、break与default

多重选择:
switch (i){    case 0:        printf("i = 0\n");        break;  // 跳出switch的复合语句块    case 1:        printf("i = 1\n");        break;    case 2:        printf("i = 2\n");        break;    case 3:        printf("i = 3\n");        break;    case 4:        printf("i = 4\n");        break;    default:    // 如果有所条件都不满足,那么执行default语句        printf("error\n");}
什么时候用if,什么时候用switch?
当条件很复杂,一个条件中有&&||!存在,那么用if语句。如果条件很简单,但分支很多,那么适合用switch

5.8 三目运算符

一个求绝对值的例子:
int i = -8;int x = (i < 0) ? -i : i;
先求?左边的条件,如果条件为真,那么等于:左边的值,否则等于:右边的值。
一个求最大值的例子:
int c = (a > b) ? a : b;

5.9 goto语句与标号

无条件跳转gotogoto语句是汇编时代的跳转方式,不建议使用goto语句,这会使你的程序可读性变的很差。

6.1 while

while(条件){    // 循环体}
如果条件为真,会循环执行循环体内的代码;如果条件为假,则循环结束跳出循环。
while(1)是一种执行死循环的写法。

6.2 continue

循环遇到continue语句,不再执行continue下面代码,而是直接返回到循环起始语句处继续执行循环。

6.3 break

循环遇到break语句,立刻终止循环,跳出循环体。

6.4 do while

do {    // 复合语句}while (条件);
对于do while来讲,循环的复合语句至少可以被执行一次,对于while来讲,有可能复合语句一次执行机会都没有。

6.5 for

for(int i=0; i<10; i++){    // 循环体}
for语句可以更方便的指定循环次数。

6.6 循环嵌套

int i,j;for(i = 9; i > 0; i--){    for(j = 9; j > 0; j--)    {        printf("%d\t", i * j);    }    printf("\n");}

7.1 一维数组定义与使用

int array[10];  // 定义一个一维数组,名字叫array,一共有10个元素,每个元素都是int类型的array[0] = 20;array[1] = 30;array[9] = 80;//array[10] = 100; // 错误,没有 array[10]这个元素

7.2 数组在内存的存储方式

数组在内存中就是一段连续的空间,每个元素的类型是一样的。

7.3 一维数组初始化

// 定义数组的同时为数组的成员初始化值int array[10] = { 100, 1, 5, 3, 4, 5, 6, 7, 8, 0 };// 将数组的前三个元素赋值,其余元素置为0int array[10] = { 3, 7, 9 };// 将数组所有的元素都置为0int array[10] = { 0 };// 循环更新数组内的元素值int i;for (i = 0; i < 10; i++){    array[i] = 0; //通过循环遍历数组的每个元素,将元素的值置为0}

7.4 二维数组定义与使用

int array[2][3];    // 定义了一个二维数组,有两个array[3]

7.5 二维数组初始化

int a[3][4] = {    { 1, 2, 3, 4 },    { 5, 6, 7, 8 },    { 9, 10, 11, 12 }};

8. 字符串与字符数组

8.1 字符数组定义

char array[100];

8.2 字符数组初始化

char array[100] = {'a', 'b', 'c','d'};char array[100] = "abcd";char array[100] = { 0 };char array[] = "abcd";

8.3 随机数产生函数rand与srand

需要包含头文件stdlib.hrand()是伪随机数产生器,每次调用rand()产生的随机数是一样的。如果调用rand()之前先调用srand()就出现任意的随机数。只要能保证每次调用srand()函数的时候,参数的值是不同的,那么rand()函数就会产生不同的随机数。
#include <time.h>#include <stdlib.h>int t = (int)time(NULL);srand(t);for (int i = 0; i < 10; i++){    printf("%d\n", rand());}

8.4 用scanf输入字符串

char s[10] = { 0 };/*    "%s"的作用就是输入一个字符串的,    scanf是以回车键作为输入完成标示的,    但回车键本身并不会作为字符串的一部分    如果scanf参数中的数组长度小于用户在键盘输入的长度,    那么scanf就会缓冲区溢出,导致程序崩溃*/scanf("%s", s);int i;for (i = 0; i < 10; i++){    printf("%d\n", s[i]);}printf("----------------------------------\n");printf("%s\n", s);return 0;

8.5 字符串的结束标志

scanf将回车,空格都认为是字符串输入结束标志。

8.6 字符串处理函数

8.7.1 gets

char s[100] = { 0 };/*    gets认为回车的输入结束标示,    空格不是输入结束标示,    所以用gets这个函数就可以实现输入带空格的字符串    gets和scanf一样存在缓冲区溢出的问题*/gets(s);int i;for (i = 0; i < 10; i++){    printf("%d\n", s[i]);}printf("----------------------------------\n");printf("%s\n", s);
gets不能用类似%s或者%d之类的字符转义,只能接受字符串的输入。

8.7.2 fgets函数

gets函数不检查预留缓冲区是否能够容纳用户实际输入的数据。多出来的字符会导致内存溢出,fgets函数改进了这个问题。
由于fgets函数是为读取文件设计的,所以读取键盘时没有gets那么方便。
char s[100] = { 0 };fgets(s, sizeof(s), stdin);

8.7.3 puts函数

puts函数打印字符串,与printf不同,puts会在最后自动添加一个'\n'
char s[] = "hello world";puts(s);

8.7.4 fputs函数

fputsputs的文件操作版本。
char s[] = "hello world";fputs(s, stdout);

8.7.5 strlen字符串长度

size_t strlen(const char * _Str);
返回不包含字符串结尾'\0'的字符串长度。
char s[100] = "hello world";int len = strlen(s);  // 得到字符串长度,返回一个字符串中有效字符的数量(不包含字符串结尾的0)printf("len = %d\n", len);return 0;

8.7.6 strcat字符串追加

size_t strcat(char * _Str1, const char * _Str2);
将参数_Str2追加到_Str1后尾。
char s[1024] = "hello world";int len = strlen(s);  // 得到字符串长度,返回一个字符串中有效字符的数量(不包含字符串结尾的0)printf("len = %d\n", len);char s1[100] = "abc123456789";strcat(s, s1);   // 将两个字符串合并,结果放入第一个参数里面,strcat也存在缓冲区溢出的问题printf("%s\n", s);

8.7.7 strncat字符串有限追加

size_t strncat(char * _Str1, const char * _Str2, size_t len);

8.7.8 strcmp字符串比较

int strcmp(const char * _Str1, const char * _Str2);
比较两个字符串是否相等,相等返回0,不等返回非0。

8.7.9 strncmp字符串有限比较

8.7.10 strcpy字符串拷贝

char *strcpy(char * _Str1, const char * _Str2);
将参数_Str2拷贝到参数_Str1中。

8.7.11 strncpy字符串有限拷贝

8.7.12 sprintf格式化字符串

printf函数功能类似,printf函数将格式化结果输出到屏幕,sprintf将格式化结果输出到字符串。

8.7.13 Sscanf函数

Sscanf类似于scanf函数,scanf从键盘读取用户输入,scanf从指定格式化字符串读取输入。

8.7.14 strchr查找字符

char * strchr(char * _Str, int _Ch);
在参数_str中查找参数_Ch指定字符,找到返回字符_Ch_Str中所在位置,没有找到返回NULL

8.7.15 strstr查找子串

char * strstr(char * _Str, const char * _SubStr)
在参数_str中查找参数_SubStr指定子串,找到返回子串在_Str中所在位置,没有找到返回NULL

8.7.16 strtok分割字符串

字符在第一次调用时strtok()必需给予参数s字符串,往后的调用则将参数s设置成NULL每次调用成功则返回指向被分割出片段的指针。
char buf[] = "abc@defg@igk";char *p = strtok(buf, "@");while (p){    printf("%s\n", p);    p = strtok(NULL, "@");}

8.7.17 atoi转化为int

需要包含头文件stdlib.h

8.7.18 atof转化为float

8.7.19 atol转化为long

9.函数

9.1 函数的原型和调用

在使用函数前必须定义或者声明函数
double circle(double r);int main(){    double length = circle(10);    printf("length = %f\n", length);    return 0;}double circle(double r){    return 2 * 3.14 * r;}

9.2 函数的形参与实参

在调用函数的时候,函数大多数都有参数,主调函数和被调用函数之间需要传递数据。
在定义函数时函数名后面括弧中的变量名称为「形式参数」,简称形参。在调用函数时,函数名后面括号中的变量或表达式称为「实际参数」,简称实参
  • 形参在未出现函数调用时,他们并不占用内存单元,只有在发生函数调用的时候形参才被分配内存,函数调用完成后,形参所占的内存被释放
  • 实参可以是变量,常量或者表达式
  • 在定义函数时,一定要指定形参的数据类型
  • 形参与实参的数据类型一定要可兼容
  • 在C语言中,实参与形参的数据传递是「值传递」,即单向传递,只由实参传递给形参,而不能由形参传递给实参
如果函数的参数是个数组,那么是可以通过形参修改实参的值的。

9.3 函数的返回类型与返回值

  • 函数的返回值通过函数中的return获得,如果函数的返回值为void可以不需要return语句
  • 函数return语句中的返回值数据类型应该与函数定义时相同
  • 如果函数中没有return语句,那么函数将返回一个不确定的值

9.4 main函数、exit函数与函数的return语句

int test1(){    printf("111111\n");    //return 0;    exit(0); // 在子函数中调用exit同样代表程序终止,但在子函数中调用return只是子函数终止,程序正常执行    printf("222222\n");}int main(){    test1();    printf("AAAAAA\n");    exit(100);  // exit是C语言的库函数,调用exit的结果就是程序终止    return 100; // 在main函数中调用exit与调用return是一样的    printf("CCCCCCC\n");    return 0;   // main函数return代表程序终止    printf("BBBBBB\n");}

9.5 多个源代码文件程序的编译

9.5.1 头文件的使用

如果把main函数放在第一个文件中,而把自定义函数放在第二个文件中,那么就需要在第一个文件中声明函数原型。
如果把函数原型包含在一个头文件里,那么就不必每次使用函数的时候都声明其原型了。
把函数声明放入头文件是很好的习惯。

9.5.2 #include与#define的意义

#include就是简单的文件内容替换
#define就是简单的文本替换而已

9.5.3 #ifndef与#endif

#ifndef的意思就是条件预编译,如果#ifndef后面的条件成立,那么就预编译从#ifndef开始到#endif之间的代码,否则不会去预编译这段代码。

9.6 函数的递归

函数可以调用自己,这就叫函数的递归。
void recurse(int i){    if (i > 0)    {        recurse(i - 1);    }    printf("i = %d\n", i);}int main(){    recurse(10);    return 0;}

9.6.1 递归的过程分析

void up_down(int  n){    printf("in %d, location %p\n", n, &n);    if (n < 4)        up_down((n + 1));    printf("out %d, location %p\n", n, &n);}int main(){    up_down(1);    return 0;}
notion image
img
notion image
img
notion image
img

9.6.2 递归的优点

递归给某些编程问题提供了最简单的方法。

9.6.3 递归的缺点

一个有缺陷的递归会很快耗尽计算机的资源,递归的程序难以理解和维护。

10.1 指针的概念

指针变量也是一个变量,指针存放的内容是一个地址,该地址指向一块内存空间。

10.2 指针变量的定义

可以定义一个指向一个变量的指针变量。
int *p;     // 表示定义一个指针变量。*p;         // 代表指针所指内存的实际数据// 切记,指针变量只能存放地址,不能将一个int型变量直接赋值给一个指针。int *p = 100;

10.3 &取地址运算符

&可以取得一个变量在内存当中的地址。

10.4 无类型指针

定义一个指针变量,但不指定它指向具体哪种数据类型。可以通过强制转化将void *转化为其他类型指针,也可以用(void *)将其他类型指针强制转化为void类型指针。
void *p

10.5 NULL

NULL在C语言中的定义为(void *)0

10.6 空指针与野指针

指向NULL的指针叫空指针,没有具体指向任何变量地址的指针叫野指针
notion image
img

10.7 指针的兼容性

指针之间赋值比普通数据类型赋值检查更为严格,例如:不可以把一个double *赋值给int *
原则上一定是相同类型的指针指向相同类型的变量地址,不能用一种类型的指针指向另一种类型的变量地址。
notion image
img

10.8 指向常量的指针与指针常量

const char *p;  // 定义一个指向常量的指针
char *const p;  // 定义一个指针常量,一旦初始化之后其内容不可改变

10.9 指针与数组的关系

一个变量有地址,一个数组包含若干个元素,每个元素在内存中都有地址。
int a[10];int *p = a;
比较p&a[0]的地址是否相同。

10.10 指针运算

指针运算不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数做为倍数的运算。
char *p;p++;        // 移动了sizeof(char)这么多的字节数int *p1;p1++;       // 移动了sizeof(int)这么多的字节数
  • 赋值:int *p = &a;
  • 求值:int i = *p;
  • 取指针地址:int **pp = &p;
  • 将一个整数加(减)给指针:p + 3; p – 3;
  • 增加(减少)指针值:p++p--
  • 求差值:p1 – p2,通常用于同一个数组内求两个元素之间的距离
  • 比较:p1 == p2,通常用来比较两个指针是否指向同一个位置

10.11 通过指针使用数组元素

p + 1代表&a[1],也可以直接使用p[1]表示a[5]
p + 5代表&a[5]
p++

10.12 指针数组

int *p[5];

10.13 指向指针的指针(二级指针)

指针就是一个变量,既然是变量就也存在内存地址,所以可以定义一个指向指针的指针。
int i = 10;int *p1 = &i;int **p2 = &p1;printf("%d\n", **p2);
以此类推可以定义三级甚至多级指针。
C语言允许定义多级指针,但是指针级数过多会增加代码的复杂性,考试的时候可能会考多级指针,但实际编程的时候最多用到三级,但三级指针也不常用,一级和二级指针是大量使用的。
notion image
img
notion image
img

10.14 指向二维数组的指针

代码
含义
int buf[3][5]
二维数组名称,buf代表数组首地址
int (*a)[5]
定义一个指向int [5]类型的指针变量a
a[0], *(a + 0), *a
0行,0列元素地址
a + 1
第1行首地址
a[1], *(a + 1)
第1行,0列元素地址
a[1] + 2, *(a + 1) + 2, &a[1][2]
第1行,2列元素地址
*(a[1] + 2), *(*(a + 1) + 2), a[1][2]
第1行,2列元素的值

10.15 指针变量做为函数的参数

函数的参数可以是指针类型,它的作用是将一个变量的地址传送给另一个函数。
通过函数的指针参数可以间接的实现形参修改实参的值
notion image
img

10.16 一维数组名作为函数参数

当数组名作为函数参数时,C语言将数组名解释为指针:
int func(int array[10]);

10.17 二维数组名作为函数参数

二维数组做函数参数时可以不指定第一个下标:
int func(int array[][10]);
将二维数组做为函数的参数用例不是特别多见。

10.18 const关键字保护数组内容

如果讲一个数组做为函数的形参传递,那么数组内容可以在被调用函数内部修改,有时候不希望这样的事情发生,所以要对形参采用const参数:
func(const int array[])

10.19 指针做为函数的返回值

notion image
img

10.20 指向函数的指针

指针可以指向变量,数组,也可以指向一个函数。一个函数在编译的时候会分配一个入口地址这个入口地址就是函数的指针函数名称就代表函数的入口地址
函数指针的定义方式:
int (*p)(int);  // 定义了一个指向int func(int n)类型函数地址的指针
  • 定义函数指针变量的形式为:函数返回类型(*指针变量名称)(参数列表)
  • 函数可以通过函数指针调用
  • int(*p)()代表指向一个函数,但不是固定哪一个函数
void man(){    printf("抽烟\n");    printf("喝酒\n");    printf("打牌\n");}void woman(){    printf("化妆\n");    printf("逛街\n");    printf("网购\n");}int main(){    void(*p)();    int i = 0;    scanf("%d", &i);    if (i == 0)        p = man;    else        p = woman;    p();    return 0;}
在回调函数和运行期动态绑定的时候大量的用到了指向函数的指针。

10.21 把指向函数的指针做为函数的参数

将函数指针做为另一个函数的参数称为回调函数
int max(int a, int b){    if (a > b)        return a;    else        return b;}int add(int a, int b){    return a + b;}void func(int(*p)(int, int), int a, int b){    int res = p(a, b);    printf("%d\n", res);}int main(){    int  i = 0;    scanf("%d", &i);    if (i == 0)        func(max, 10, 20);    else        func(add, 10, 20);    return 0;}

10.22 memset、memcpy和memmove函数

这三个函数分别实现内存设置内存拷贝内存移动
使用memcpy()的时候,一定要确保内存没有重叠区域
notion image
img
notion image
img
notion image
img

10.23 指针小结

定义
说明
int i
定义整型变量
int *p
定义一个指向int的指针变量
int a[10]
定义一个int数组
int *p[10]
定义一个指针数组,其中每个数组元素指向一个int型变量的地址
int (*p)[10]
定义一个数组指针,指向int [10]类型的指针变量
int func()
定义一个函数,返回值为int
int *func()
定义一个函数,返回值为int *
int (*p)()
定义一个指向函数的指针,函数的原型为无参数,返回值为int
int **p
定义一个指向int的指针的指针,二级指针

11.字符指针与字符串

11.1 指针和字符串

在C语言当中,大多数字符串操作其实就是指针操作。
char s[] = "hello world";char *p = s;p[0] = 'a';

11.2 通过指针访问字符串数组

char buf[100] = "hello world";char *p = buf;//*(p + 5) = 'a';//p[5] = 'b';p += 5;*p = 'c';p[3] = ' ';printf("buf = %s\n", buf);

11.3 函数的参数为char *

/*    如果参数是个字符串,那么就不需要包含第二个参数了,    因为字符串是明确的以'\0'结尾的,    所以,在函数内部是有条件来做为循环终止依据的*/void print_str(char *s){    int i = 0;    while (s[i])    {        printf("%c", s[i++]);    }}

11.4 指针数组做为main函数的形参

int main(int argc, char *argv[]);
main函数是操作系统调用的,所以main函数的参数也是操作系统在调用时候自动填写的。
  • argc代表命令行参数的数量
  • argv代表命令行的具体参数,是char *类型的

12.内存管理

12.1 作用域

一个C语言变量的作用域可以是代码块作用域函数作用域或者文件作用域
代码块是{}之间的一段代码。出现在{}之外的变量,就是全局变量

12.1.1 auto自动变量

一般情况下代码块内部定义的变量都是自动变量。当然也可以显示的使用aotu关键字。

12.1.2 register寄存器变量

通常变量在内存当中,如果能把变量放到CPU的寄存器里面,代码执行效率会更高。
register int i;
register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度,而不是将其储存在内存里面。但register只是建议型的指令,而不是命令型的指令,不一定保证能成功将其保存在CPU的寄存器中。

12.1.3 代码块作用域的静态变量

静态变量是指内存位置在程序执行期间一直不改变的变量,一个代码块内部的静态变量只能被这个代码块内部访问。

12.1.4 代码块作用域外的静态变量

代码块之外的静态变量在程序执行期间一直存在,但只能被定义这个变量的文件访问。

12.1.5 全局变量

全局变量的存储方式和静态变量相同,但可以被多个文件访问。

12.1.6 外部变量与extern关键字

extern int i;

12.1.7 全局函数和静态函数

在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态。

12.2 内存四区

notion image
img

12.2.1 代码区

程序被操作系统加载到内存的时候,所有的可执行代码都加载到代码区,也叫代码段,这块内存是不可以在运行期间修改的。

12.2.2 静态区

所有的全局变量以及程序中的静态变量都存储到静态区,比较如下两段代码的区别:
int a = 0;int main(){    static int b = 0;    printf("%p, %p\n", &a, &b);    return 0;}
int a = 0;static int b = 0;int main(){    printf("%p, %p\n", &a, &b);    return 0;}

12.2.3 栈区

栈(stack)是一种先进后出的内存结构,所有的自动变量,函数的形参都是由编译器自动放出栈中,当一个自动变量超出其作用域时,自动从栈中弹出。
对于自动变量,什么时候入栈,什么时候出栈,是不需要程序控制的,由C语言编译器实现。栈不会很大,一般都是以kb为单位。
notion image
img
当栈空间以满,但还往栈内存压变量,这个就叫栈溢出
对于一个32位操作系统,最大管理管理4G内存,其中1G是给操作系统自己用的,剩下的3G都是给用户程序,一个用户程序理论上可以使用3G的内存空间。

12.2.4 堆区

堆(heap)和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。
堆是一个大容器,它的容量要远远大于栈,但是在C语言中,堆内存空间的申请和释放需要手动通过代码来完成。
notion image
img
notion image
img

12.3 堆的分配和释放

操作系统在管理内存的时候,最小单位不是字节,而是内存页
notion image
img

12.3.1 malloc

void * malloc(size_t _Size);
malloc()函数在堆中分配参数_Size指定大小的内存,单位:字节,函数返回void *指针。

12.3.2 free

void free(void *p);
free()负责在堆中释放malloc()分配的内存。参数pmalloc()返回的堆中的内存地址。

12.3.3 calloc

void * calloc(size_t _Count, size_t _Size);
calloc()malloc()类似,负责在堆中分配内存。
第一个参数是所需内存单元数量,第二个参数是每个内存单元的大小(单位:字节)calloc()自动将分配的内存置0
int *p = (int *)calloc(100, sizeof(int));   // 分配100个int

12.3.4 realloc

重新分配用malloc()或者calloc()函数在堆中分配内存空间的大小。
void * realloc(void *p, size_t _NewSize);
第一个参数p为之前用malloc()或者calloc()分配的内存地址,_NewSize为重新分配内存的大小,单位:字节。
  • 成功返回新分配的堆内存地址
  • 失败返回NULL
如果参数p等于NULL,那么realloc()malloc功能一致。

13. 结构体、联合体、枚举与typedef

13.1 结构体

13.1.1 定义结构体struct和初始化

struct man{    char name[100];    int age;};struct man m = { "tom", 12 };struct man m = { .name = "tom", .age = 12 };

13.1.2 访问结构体成员

通过.操作符访问结构体成员。

13.1.3 结构体的内存对齐模式

编译器在编译一个结构的时候采用内存对齐模式:
struct man{    char a;    int b;};
notion image
img

13.1.4 指定结构体元素的位字段

定义一个结构体的时候可以指定具体元素的位长。
struct test{    char a : 2;     // 指定元素为2位长,不是2个字节长};

13.1.5 结构数组

struct man m[10] = { { "tom", 12 }, { "marry", 10 }, { "jack", 9 } };

13.1.6 嵌套结构

一个结构的成员还可以是另一个结构类型:
struct names{    char first[100];    char last[100];};struct man{    struct names name;    int age;};struct man m = { { "wang", "wu" }, 20 };

13.1.7 结构体的赋值

struct name a = b;

13.1.8 指向结构体的指针

–>操作符。

13.1.9 指向结构体数组的指针

13.1.10 结构中的数组成员和指针成员

一个结构中可以有数组成员,也可以有指针成员,如果是指针成员结构体成员在初始化和赋值的时候就需要提前为指针成员分配内存。
struct man{    char name[100];    int age;};
struct man{    char *name;    int age;};

13.1.11 在堆中创建的结构体

如果结构体有指针类型成员,同时结构体在堆中创建,那么释放堆中的结构体之前需要提前释放结构体中的指针成员指向的内存。
struct man{    char *name;    int age;};struct man *s = malloc(sizeof(struct man) * 2);s[0].name = malloc(10 * sizeof(char));s[1].name = malloc(10 * sizeof(char));

13.1.12 将结构作为函数参数

  • 将结构作为函数参数
  • 将结构指针作为函数参数

13.1.13 指向结构的指针

在定义一个和结构有关的函数,到底是使用结构,还是结构的指针?
指针作为参数,只需要传递一个地址,所以代码效率高。当一个结构做为函数的参数时候,尽量使用指针,而不是使用结构变量。
// 一般来讲,不要把结构变量做为函数的参数传递void print_student(const struct student *s){    printf("name = %s, age = %d\n", s->name, s->age);}void set_student(struct student *s, const char *name, int age){    strcpy(s->name, name);    s->age = age;}

13.2 联合体

  • 联合体union是一个能在同一个存储空间存储不同类型数据的类型。
  • 联合体所占的内存长度等于其最长成员的长度,也有叫做共用体。
  • 联合体虽然可以有多个成员,但同一时间只能存放其中一种。
union variant{    int ivalue;    char cvalue;    double dvalue;};int main(){    union variant var;    var.cvalue = 12;    printf("%d\n", var.ivalue);    printf("%p, %p, %p\n", &(var.cvalue), &(var.ivalue), &(var.dvalue));    return 0;}

13.3 枚举类型

13.3.1 枚举定义

可以使用枚举(enumerated type)声明代表整数常量的符号名称,关键字enum创建一个新的枚举类型。
enum spectrum { red, yellow, green, blue, white, black };enum spectrum color;color = black;if (color != red)
实际上,enum常量是int类型的。

13.3.2 默认值

默认时,枚举列表中的常量被指定为0、1、2等:
enum spectrum { red, yellow, green, blue, white, black };printf("%d, %d\n", red, black);
可以指定枚举中具体元素的默认值:
enum spectrum { red = 10, yellow = 20, green, blue, white, black };printf("%d, %d\n", red, black);

13.4 typedef

typedef是一种高级数据特性,它能使某一类型创建自己的名字:
typedef unsigned char BYTE
  • #define不同,typedef仅限于数据类型,而不是能是表达式或具体的值
  • typedef是编译器处理的,而不是预编译指令
  • typedef#define更灵活
使用typedef可以增加程序的可移植性。

13.5 通过typedef定义函数指针

typedef const char *(*SUBSTR)(const char *, const char *);const char *getsubstr(const char *src, const char *str){    return strstr(src, str);}const char *func(const char *(*s)(const char *, const char *), const char *src, const char *str);const char *(*p[3])(const char *, const char *);

14.文件操作

14.1 fopen

  • r以只读方式打开文件,该文件必须存在
  • r+以可读写方式打开文件,该文件必须存在
  • rb+读写打开一个二进制文件,允许读写数据,文件必须存在
  • rw+读写打开一个文本文件,允许读和写
  • w打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件
  • w+打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件
  • a以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留(EOF符保留)
  • a+以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留(原来的EOF符不保留)

14.2 二进制和文本模式的区别

  • 在windows系统中,文本模式下,文件以"\r\n"代表换行。若以文本模式打开文件,并用fputs等函数写入换行符"\n"时,函数会自动在"\n"前面加上"\r"。即实际写入文件的是"\r\n" (CRLF模式)
  • 在类Unix/Linux系统中文本模式下,文件以"\n"代表换行。所以Linux系统中在文本模式和二进制模式下并无区别(LF模式)

14.3 fclose

使用fclose()关闭fopen()打开的文件。

14.4 getc和putc函数

int main(){    FILE *fp = fopen("a.txt", "r");    char c;    while ((c = getc(fp)) != EOF)    {        printf("%c", c);    }    fclose(fp);    return 0;}
int main(){    FILE *fp = fopen("a.txt", "w");    const char *s = "hello world";    int i;    for (i = 0; i < strlen(s); i++)    {        putc(s[i], fp);    }    fclose(fp);    return 0;}

14.5 EOF与feof函数文件结尾

程序怎么才能知道是否已经到达文件结尾了呢?EOF代表文件结尾。
如果已经是文件尾,feof()函数返回true

14.6 fprintf、fscanf、fgets和fputs函数

这些函数都是通过FILE *来对文件进行读写,都是针对文本文件的行读写函数。

14.7 stat函数

int stat(const char * _Filename, struct stat * _Stat)
#include <sys/stat.h>stat.st_size;   // 文件大小,单位:字节
函数的第一个参数代表文件名,第二个参数是struct stat结构。
该函数可以得到文件的属性,包括文件建立时间,文件大小等信息。

14.8 fread和fwrite函数

size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
注意:这个函数以二进制形式对文件进行操作,不局限于文本文件。
返回值:返回实际写入的数据块数目。

14.9 fread与feof

注意以下两段代码的区别:
while (!feof(p)){    fread(&buf, 1, sizeof(buf), p);}
while (fread(&buf, 1, sizeof(buf), p))

14.10 通过fwrite将结构保存到二进制文件中

14.11 fseek函数

int fseek(FILE * _File, long _Offset, int _Origin);
函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败则不改变stream指向的位置,函数返回一个非0值。
实验得出,超出文件末尾位置,还是返回0。往回偏移超出首位置,还是返回0,请小心使用。
第一个参数stream为文件指针;第二个参数offset为偏移量,正数表示正向偏移,负数表示负向偏移;第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CURSEEK_ENDSEEK_SET
  • SEEK_SET: 文件开头
  • SEEK_CUR: 当前位置
  • SEEK_END: 文件结尾
fseek(fp, 3, SEEK_SET);

14.12 ftell函数

函数ftell()用于得到文件位置指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。
long len = ftell(fp);

14.13 fflush函数

fflush()函数可以将缓冲区中任何未写入的数据写入文件中,成功返回0,失败返回EOF
int fflush(FILE * _File);
notion image
img

14.14 remove函数

remove()函数删除指定文件。
int remove(const char *_Filename);
参数Filename为指定的要删除的文件名,如果是Windows系统下文件名与路径可以用反斜杠\分隔,也可以用斜杠/分隔。而Linux/Unix系统下只支持用斜杠/分割,所以推荐都使用斜杠/来分割路径。

14.15 rename函数

rename()函数将指定文件改名。
int rename(const char *_OldFilename,const char *_NewFilename);
参数oleFilename为指定的要修改的文件名,参数newfilename为修改后的文件名。

References

Loading Comments...