c语言基础知识

本文全面介绍了C语言基础知识,涵盖编译步骤、数据类型、运算符、程序流程结构、数组、函数、指针、内存管理、复合类型和文件操作等内容。详细讲解了各部分的概念、用法及示例,如数据类型的存储方式、运算符的优先级、文件的读写操作等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C语言基础知识

 1.1C语言初学知识

        c语言32 个关键字   

9种控制语句

34种运算符

入门级教学 第一个程序教学

#include <stdio.h>

int main()
{
    printf("hello world\n")
    return 0;
}

1.2 c程序的编译步骤

C代码编译成可执行程序经过4步:

  1. 预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法  

    gcc -E hello.c -o hello.i   生成.i文件

  2. 编译:检查语法,将预处理后文件编译生成汇编文件 

    gcc -S hello.i -o hello.s  生成.s文件

  3. 汇编:将汇编文件生成目标文件(二进制文件) 

    gcc -c hello.s -o hello.o 生成.o 文件

  4. 链接:C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去   gcc    hello.o -o hello

1.3 CPU内部结构与寄存器(了解)

1.3.164位和32位系统区别

  1. 寄存器是CPU内部最基本的存储单元
  2. CPU对外是通过总线(地址、控制、数据)来和外部设备交互的,总线的宽度是8位,同时CPU的寄存器也是8位,那么这个CPU就叫8位CPU
  3. 如果总线是32位,寄存器也是32位的,那么这个CPU就是32位CPU
  4. 有一种CPU内部的寄存器是32位的,但总线是16位,准32为CPU
  5. 所有的64位CPU兼容32位的指令,32位要兼容16位的指令,所以在64位的CPU上是可以识别32位的指令
  6. 在64位的CPU构架上运行了64位的软件操作系统,那么这个系统是64位
  7. 在64位的CPU构架上,运行了32位的软件操作系统,那么这个系统就是32位
  8. 64位的软件不能运行在32位的CPU之上

1.3.2  寄存器名字(了解)

8

16

32

64

A

AX

EAX

RAX

B

BX

EBX

RBX

C

CX

ECX

RCX

D

DX

EDX

RDX

1.3.3寄存器、缓存、内存三者关系

按与CPU远近来分,离得最近的是寄存器,然后缓存(CPU缓存),最后内存。

CPU计算时,先预先把要用的数据从硬盘读到内存,然后再把即将要用的数据读到寄存器。于是 CPU<--->寄存器<--->内存,这就是它们之间的信息交换。

那为什么有缓存呢?因为如果经常操作内存中的同一址地的数据,就会影响速度。于是就在寄存器与内存之间设置一个缓存。

因为从缓存提取的速度远高于内存。当然缓存的价格肯定远远高于内存,不然的话,机器里就没有内存的存在。

由此可以看出,从远近来看:CPU〈---〉寄存器〈---> 缓存 <---> 内存。

2.数据类型

2.1.1 关键字的类型区分

2.1.2 数据类型的作用:编译器预算对象(变量)分配的内存空间大小。

2.1.3常量

  1. 在程序运行过程中,其值不能被改变的量
  2. 常量一般出现在表达式或赋值语句中
  3. 常量 分为以下几种

    整型常量

    100,200,-100,0

    实型常量

    3.14 , 0.125,-3.123

    字符型常量

    ‘a’,‘b’,‘1’,‘\n’

    字符串常量

    “a”,“ab”,“12356”

2.1.4 变量

1)变量的

一般的情况下,把建立存储空间的声明称之为“定义”,而把不需要建立存储空间的声明称之为“声明”。

2.2.1整形

整型变量的定义和输出

  1. 在程序运行过程中,其值可以改变
  2. 变量在使用前必须先定义,定义变量前必须有相应的数据类型
  3. 标识符命名规则  
  4. 标识符不能是关键字
  5. 标识符只能由字母、数字、下划线组成
  6. 第一个字符必须为字母或下划线
  7. 标识符中字母区分大小写
  8. 声明变量不需要建立存储空间,如:extern int a;
  9. 定义变量需要建立存储空间,如:int b
  10. #include <stdio.h>
    
    int main()
    {
    	//extern 关键字只做声明,不能做任何定义,后面还会学习,这里先了解
    	//声明一个变量a,a在这里没有建立存储空间
    	extern int a;
    	a = 10;	//err, 没有空间,就不可以赋值
    
    	int b = 10;	//定义一个变量b,b的类型为int,b赋值为10
    
    	return 0;
    }
    

    从广义的角度来讲声明中包含着定义,即定义是声明的一个特例,所以并非所有的声明都是定义:

  11. int b 它既是声明,同时又是定义
  12. 对于 extern b来讲它只是声明不是定义

打印格式

含义

%d

输出一个有符号的10进制int类型

%o(字母o)

输出8进制的int类型

%x

输出16进制的int类型,字母以小写输出

%X

输出16进制的int类型,字母以大写输出

%u

输出一个10进制的无符号数

#include <stdio.h>

int main()
{
	int a = 123;	//定义变量a,以10进制方式赋值为123
	int b = 0567;	//定义变量b,以8进制方式赋值为0567
	int c = 0xabc;	//定义变量c,以16进制方式赋值为0xabc

	printf("a = %d\n", a);
	printf("8进制:b = %o\n", b);
	printf("10进制:b = %d\n", b);
	printf("16进制:c = %x\n", c);
	printf("16进制:c = %X\n", c);
	printf("10进制:c = %d\n", c);

	unsigned int d = 0xffffffff; //定义无符号int变量d,以16进制方式赋值
	printf("有符号方式打印:d = %d\n", d);
	printf("无符号方式打印:d = %u\n", d);
	return 0;
}

例2  

#include <stdio.h>

int main()
{
	int a;
	printf("请输入a的值:");

	//不要加“\n”
	scanf("%d", &a);

	printf("a = %d\n", a); //打印a的值

	return 0;
}

2.2.2 short、int、long、long long

数据类型

占用空间

short(短整型)

2字节

int(整型)

4字节

long(长整形)

Windows为4字节,Linux为4字节(32位),8字节(64位)

long long(长长整形)

8字节

注意:

  1. 需要注意的是,整型数据在内存中占的字节数与所选择的操作系统有关。虽然 C 语言标准中没有明确规定整型数据的长度,但 long 类型整数的长度不能短于 int 类型, short 类型整数的长度不能长于 int 类型。
  2. 当一个小的数据类型赋值给一个大的数据类型,不会出错,因为编译器会自动转化。但当一个大的类型赋值给一个小的数据类型,那么就可能丢失高位。

整型常量

所需类型

10

代表int类型

10l, 10L

代表long类型

10ll, 10LL

代表long long类型

10u, 10U

代表unsigned int类型

10ul, 10UL

代表unsigned long类型

10ull, 10ULL

代表unsigned long long类型

打印格式

含义

%hd

输出short类型

%d

输出int类型

%ld

输出long类型

%lld

输出long long类型

%hu

输出unsigned short类型

%u

输出unsigned int类型

%lu

输出unsigned long类型

%llu

输出unsigned long long类型

#include <stdio.h>

int main()
{
	short a = 10;
	int b = 10;
	long c = 10l; //或者10L
	long long d = 10ll; //或者10LL

	printf("sizeof(a) = %u\n", sizeof(a));
	printf("sizeof(b) = %u\n", sizeof(b));
	printf("sizeof(c) = %u\n", sizeof(c));
	printf("sizeof(c) = %u\n", sizeof(d));

	printf("short a = %hd\n", a);
	printf("int b = %d\n", b);
	printf("long c = %ld\n", c);
	printf("long long d = %lld\n", d);

	unsigned short a2 = 20u;
	unsigned int b2 = 20u;
	unsigned long c2= 20ul; 
	unsigned long long d2 = 20ull; 

	printf("unsigned short a = %hu\n", a2);
	printf("unsigned int b = %u\n", b2);
	printf("unsigned long c = %lu\n", c2);
	printf("unsigned long long d = %llu\n", d2);

	return 0;
}

2.2.3有符号数和无符号数区别

1) 有符号数

有符号数是最高位为符号位,0代表正数,1代表负数。

#include <stdio.h>

int main()
{
	signed int a = -1089474374; //定义有符号整型变量a
	printf("%X\n", a); //结果为 BF0FF0BA

	//B       F      0        F       F     0        B	      A
	//1011 1111 0000 1111 1111 0000 1011 1010

	return 0;
}

2) 无符号数

无符号数最高位不是符号位,而就是数的一部分,无符号数不可能是负数。

#include <stdio.h>

int main()
{
	unsigned int a = 3236958022; //定义无符号整型变量a
	printf("%X\n", a); //结果为 C0F00F46

	return 0;
}

3) 有符号和无符号整型取值范围

数据类型

占用空间

取值范围

short

2字节

-32768 到 32767 (-215 ~ 215-1)

int

4字节

-2147483648 到 2147483647 (-231 ~ 231-1)

long

4字节

-2147483648 到 2147483647 (-231 ~ 231-1)

unsigned short

2字节

0 到 65535 (0 ~ 216-1)

unsigned int

4字节

0 到 4294967295 (0 ~ 232-1)

unsigned long

4字节

0 到 4294967295 (0 ~ 232-1)

2.2.4sizeof关键字

  1. sizeof不是函数,所以不需要包含任何头文件,它的功能是计算一个数据类型的大小,单位为字节
  2. sizeof的返回值为size_t
  3. size_t类型在32位操作系统下是unsigned int,是一个无符号的整数

#include <stdio.h>

int main()
{
	int a;
	int b = sizeof(a);//sizeof得到指定值占用内存的大小,单位:字节
	printf("b = %d\n", b);

	size_t c = sizeof(a);
	printf("c = %u\n", c);//用无符号数的方式输出c的值

	return 0;
}

2.2.5 字符型:char

2.4.1 字符变量的定义和输出字符型变量用于存储一个单一字符,在 C 语言中用 char 表示,其中每个字符变量都会占用 1 个字节。在给字符型变量赋值时,需要用一对英文半角格式的单引号(' ')把字符括起来。

字符变量实际上并不是把该字符本身放到变量的内存单元中去,而是将该字符对应的 ASCII 编码放到变量的存储单元中。char的本质就是一个1字节大小的整型

#include <stdio.h>

int main()
{
	char ch = 'a';
	printf("sizeof(ch) = %u\n", sizeof(ch));

	printf("ch[%%c] = %c\n", ch); //打印字符
	printf("ch[%%d] = %d\n", ch); //打印‘a’ ASCII的值

	char A = 'A';
	char a = 'a';
	printf("a = %d\n", a);		//97
	printf("A = %d\n", A);	//65

	printf("A = %c\n", 'a' - 32); //小写a转大写A
	printf("a = %c\n", 'A' + 32); //大写A转小写a

	ch = ' ';
	printf("空字符:%d\n", ch); //空字符ASCII的值为32
	printf("A = %c\n", 'a' - ' '); //小写a转大写A
	printf("a = %c\n", 'A' + ' '); //大写A转小写a

	return 0;
}

例2

#include <stdio.h>

int main()
{
	char ch;
	printf("请输入ch的值:");

	//不要加“\n”
	scanf("%c", &ch);
	printf("ch = %c\n", ch); //打印ch的字符

	return 0;
}

2.2.6 ASCII对照表

ASCII

控制字符

ASCII

字符

ASCII

字符

ASCII

字符

0

NUT

32

(space)

64

@

96

1

SOH

33

!

65

A

97

a

2

STX

34

"

66

B

98

b

3

ETX

35

#

67

C

99

c

4

EOT

36

$

68

D

100

d

5

ENQ

37

%

69

E

101

e

6

ACK

38

&

70

F

102

f

7

BEL

39

,

71

G

103

g

8

BS

40

(

72

H

104

h

9

HT

41

)

73

I

105

i

10

LF

42

*

74

J

106

j

11

VT

43

+

75

K

107

k

12

FF

44

,

76

L

108

l

13

CR

45

-

77

M

109

m

14

SO

46

.

78

N

110

n

15

SI

47

/

79

O

111

o

16

DLE

48

0

80

P

112

p

17

DCI

49

1

81

Q

113

q

18

DC2

50

2

82

R

114

r

19

DC3

51

3

83

S

115

s

20

DC4

52

4

84

T

116

t

21

NAK

53

5

85

U

117

u

22

SYN

54

6

86

V

118

v

23

TB

55

7

87

W

119

w

24

CAN

56

8

88

X

120

x

25

EM

57

9

89

Y

121

y

26

SUB

58

:

90

Z

122

z

27

ESC

59

;

91

[

123

{

28

FS

60

<

92

/

124

|

29

GS

61

=

93

]

125

}

30

RS

62

>

94

^

126

`

31

US

63

?

95

_

127

DEL

ASCII 码大致由以下两部分组成:

  1. ASCII 非打印控制字符: ASCII 表上的数字 0-31 分配给了控制字符,用于控制像打印机等一些外围设备。
  2. ASCII 打印字符:数字 32-126 分配给了能在键盘上找到的字符,当查看或打印文档时就会出现。数字 127 代表 Del 命令。

2.2.7转义字符

转义字符

含义

ASCII码值(十进制)

\a

警报

007

\b

退格(BS) ,将当前位置移到前一列

008

\f

换页(FF),将当前位置移到下页开头

012

\n

换行(LF) ,将当前位置移到下一行开头

010

\r

回车(CR) ,将当前位置移到本行开头

013

\t

水平制表(HT) (跳到下一个TAB位置)

009

\v

垂直制表(VT)

011

\\

代表一个反斜线字符"\"

092

\'

代表一个单引号(撇号)字符

039

\"

代表一个双引号字符

034

\?

代表一个问号

063

\0

数字0

000

\ddd

8进制转义字符,d范围0~7

3位8进制

\xhh

16进制转义字符,h范围0~9,a~f,A~F

3位16进制

注意:红色字体标注的为不可打印字符。//\r切换到句首, \n为换行键

//\b为退格键, \n为换行键   

// '\123'为8进制转义字符,0123对应10进制数为83 

// '\x23'为16进制转义字符,0x23对应10进制数为35

#include <stdio.h>

int main()
{
	printf("abc");
	printf("\refg\n"); //\r切换到句首, \n为换行键

	printf("abc");
	printf("\befg\n");//\b为退格键, \n为换行键

	printf("%d\n", '\123');// '\123'为8进制转义字符,0123对应10进制数为83
	printf("%d\n", '\x23');// '\x23'为16进制转义字符,0x23对应10进制数为35

	return 0;
}

2.2.8实型(浮点型):float、double

实型变量也可以称为浮点型变量,浮点型变量是用来存储小数数值的。在C语言中, 浮点型变量分为两种: 单精度浮点数(float)、 双精度浮点数(double), 但是double型变量所表示的浮点数比 float 型变量更精确。

由于浮点型变量是由有限的存储单元组成的,因此只能提供有限的有效数字。在有效位以外的数字将被舍去,这样可能会产生一些误差。

不以f结尾的常量是double类型,以f结尾的常量(如3.14f)是float类型。

#include <stdio.h>

int main()
{
	//传统方式赋值
	float a = 3.14f; //或3.14F
	double b = 3.14;

	printf("a = %f\n", a);
	printf("b = %lf\n", b);

	//科学法赋值
	a = 3.2e3f; //3.2*1000 = 3200,e可以写E
	printf("a1 = %f\n", a);

	a = 100e-3f; //100*0.001 = 0.1
	printf("a2 = %f\n", a);

	a = 3.1415926f;
	printf("a3 = %f\n", a); //结果为3.141593

	return 0;
}

2.2.8 进制

进制也就是进位制,是人们规定的一种进位方法。 对于任何一种进制—X进制,就表示某一位置上的数运算时是逢X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,以此类推,x进制就是逢x进位。

十进制

二进制

八进制

十六进制

0

0

0

0

1

1

1

1

2

10

2

2

3

11

3

3

4

100

4

4

5

101

5

5

6

110

6

6

7

111

7

7

8

1000

10

8

9

1001

11

9

10

1010

12

A

11

1011

13

B

12

1100

14

C

13

1101

15

D

14

1110

16

E

15

1111

17

F

16

10000

20

10

2.2.8.1二进制

二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。

当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的

术语

含义

bit(比特)

一个二进制代表一位,一个位只能表示0或1两种状态。数据传输是习惯以“位”(bit)为单位。

Byte(字节)

一个字节为8个二进制,称为8位,计算机中存储的最小单位是字节。数据存储是习惯以“字节”(Byte)为单位。

WORD(双字节)

2个字节,16位

DWORD

两个WORD,4个字节,32位

1b

1bit,1位

1B

1Byte,1字节,8位

1k,1K

1024

1M(1兆)

1024k, 1024*1024

1G

1024M

1T

1024G

1Kb(千位)

1024bit,1024

1KB(千字节)

1024Byte,1024字节

1Mb(兆位)

1024Kb = 1024 * 1024bit

1MB(兆字节)

1024KB = 1024 * 1024Byte

十进制转化二进制的方法:用十进制数除以2,分别取余数和商数,商数为0的时候,将余数倒着数就是转化后的结果。

十进制的小数转换成二进制:小数部分和2相乘,取整数,不足1取0,每次相乘都是小数部分,顺序看取整后的数就是转化后的结果。

2.2.8.2八进制

八进制,Octal,缩写OCT或O,一种以8为基数的计数法,采用0,1,2,3,4,5,6,7八个数字,逢八进1。一些编程语言中常常以数字0开始表明该数字是八进制。

八进制的数和二进制数可以按位对应(八进制一位对应二进制三位),因此常应用在计算机语言中。

十进制转化八进制的方法:

用十进制数除以8,分别取余数和商数,商数为0的时候,将余数倒着数就是转化后的结果。

2.2.8.3 十六进制

十六进制(英文名称:Hexadecimal),同我们日常生活中的表示法不一样,它由0-9,A-F组成,字母不区分大小写。与10进制的对应关系是:0-9对应0-9,A-F对应10-15。

十六进制的数和二进制数可以按位对应(十六进制一位对应二进制四位),因此常应用在计算机语言中。

十进制转化十六进制的方法:

用十进制数除以16,分别取余数和商数,商数为0的时候,将余数倒着数就是转化后的结果。

2.2.8.4 C语言如何表示相应进制数

十进制

以正常数字1-9开头,如123

八进制

以数字0开头,如0123

十六进制

以0x开头,如0x123

二进制

C语言不能直接书写二进制数

#include <stdio.h>

int main()
{
	int a = 123;		//十进制方式赋值
	int b = 0123;		//八进制方式赋值, 以数字0开头
	int c = 0xABC;	//十六进制方式赋值

	//如果在printf中输出一个十进制数那么用%d,八进制用%o,十六进制是%x
	printf("十进制:%d\n",a );
	printf("八进制:%o\n", b);	//%o,为字母o,不是数字
	printf("十六进制:%x\n", c);

	return 0;
}

2.2.9计算机内存数值存储方式

2.2.9.1 原码

一个数的原码(原始的二进制码)有如下特点:

  1. 最高位做为符号位,0表示正,为1表示负
  2. 其它数值部分就是数值本身绝对值的二进制数
  3. 负数的原码是在其绝对值的基础上,最高位变为1

下面数值以1字节的大小描述:

十进制数

原码

+15

0000 1111

-15

1000 1111

+0

0000 0000

-0

1000 0000

原码表示法简单易懂,与带符号数本身转换方便,只要符号还原即可,但当两个正数相减或不同符号数相加时,必须比较两个数哪个绝对值大,才能决定谁减谁,才能确定结果是正还是负,所以原码不便于加减运算。

2.2.9.2反码

  1. 对于正数,反码与原码相同
  2. 对于负数,符号位不变,其它部分取反(1变0,0变1)

    十进制数

    反码

    +15

    0000 1111

    -15

    1111 0000

    +0

    0000 0000

    -0

    1111 1111

反码运算也不方便,通常用来作为求补码的中间过渡。

2.2.9.3 补码

在计算机系统中,数值一律用补码来存储。

补码特点:

  1. 对于正数,原码、反码、补码相同
  2. 对于负数,其补码为它的反码加1
  3. 补码符号位不动,其他位求反,最后整个数加1,得到原码

    十进制数

    补码

    +15

    0000 1111

    -15

    1111 0001

    +0

    0000 0000

    -0

    0000 0000

#include <stdio.h>

int main()
{
	int  a = -15;

	printf("%x\n", a);
	//结果为 fffffff1
	//fffffff1对应的二进制:1111 1111 1111 1111 1111 1111 1111 0001
	//符号位不变,其它取反:1000 0000 0000 0000 0000 0000 0000 1110
	//上面加1:1000 0000 0000 0000 0000 0000 0000 1111  最高位1代表负数,就是-15

	return 0;
}

2.2.9.4补码的意义

示例1:用8位二进制数分别表示+0和-0

十进制数

原码

+0

0000 0000

-0

1000 0000

十进制数

反码

+0

0000 0000

-0

1111 1111

不管以原码方式存储,还是以反码方式存储,0也有两种表示形式。为什么同样一个0有两种不同的表示方法呢?

但是如果以补码方式存储,补码统一了零的编码:

十进制数

补码

+0

 0000 0000

-0

10000 0000由于只用8位描述,最高位1丢弃,变为0000 0000

示例2:计算9-6的结果

以原码方式相加:

十进制数

原码

9

0000 1001

-6

1000 0110

结果为-15,不正确。

以补码方式相加:

十进制数

补码

9

0000 1001

-6

1111 1010

最高位的1溢出,剩余8位二进制表示的是3,正确。

在计算机系统中,数值一律用补码来存储,主要原因是:

  1. 统一了零的编码
  2. 将符号位和其它位统一处理
  3. 将减法运算转变为加法运算
  4. 两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃

2.2.9.5 数值溢出

当超过一个数据类型能够存放最大的范围时,数值会溢出。

有符号位最高位溢出的区别:符号位溢出会导致数的正负发生改变,但最高位的溢出会导致最高位丢失。

数据类型

占用空间

取值范围

char

1字节

-128到 127(-27 ~ 27-1)

unsigned char

1字节

0255(0 ~ 28-1)

#include <stdio.h>

int main()
{
	char ch;

	//符号位溢出会导致数的正负发生改变
	ch = 0x7f + 2; //127+2
	printf("%d\n", ch);
	//	0111 1111
	//+2后 1000 0001,这是负数补码,其原码为 1111 1111,结果为-127

	//最高位的溢出会导致最高位丢失
	unsigned char ch2;
	ch2 = 0xff+1; //255+1
	printf("%u\n", ch2);
	//	  1111 1111
	//+1后 10000 0000, char只有8位最高位的溢出,结果为0000 0000,十进制为0

	ch2 = 0xff + 2; //255+1
	printf("%u\n", ch2);
	//	  1111 1111
	//+1后 10000 0001, char只有8位最高位的溢出,结果为0000 0001,十进制为1

	return 0;
}

2.2.10 类型限定符

限定符

含义

extern

声明一个变量,extern声明的变量没有建立存储空间。

extern int a;//变量在定义的时候创建存储空间

const

定义一个常量,常量的值不能修改。

const int a = 10;

Volatile

防止编译器优化代码

register

定义寄存器变量,提高效率。register是建议型的指令,而不是命令型的指令,如果CPU有空闲寄存器,那么register就生效,如果没有空闲寄存器,那么register无效。

2.2.11字符串格式化输出和输入

2.2.11.1字符串常量

  1. 字符串是内存中一段连续的char空间,以'\0'(数字0)结尾。
  2. 字符串常量是由双引号括起来的字符序列,如“china”、“C program”,“$12.5”等都是合法的字符串常量。
  3. 字符串常量与字符常量的不同: 

  4. 每个字符串的结尾,编译器会自动的添加一个结束标志位'\0',即 "a" 包含两个字符'a'和’\0’。

2.2.11.2printf函数和putchar函数

printf是输出一个字符串,putchar输出一个char。

打印格式

对应数据类型

含义

%d

int

接受整数值并将它表示为有符号的十进制整数

%hd

short int

短整数

%hu

unsigned short

无符号短整数

%o

unsigned int

无符号8进制整数

%u

unsigned int

无符号10进制整数

%x,%X

unsigned int

无符号16进制整数,x对应的是abcdef,X对应的是ABCDEF

%f

float

单精度浮点数

%lf

double

双精度浮点数

%e,%E

double

科学计数法表示的数,此处"e"的大小写代表在输出时用的"e"的大小写

%c

char

字符型。可以把输入的数字按照ASCII码相应转换为对应的字符

%s

char *

字符串。输出字符串中的字符直至字符串中的空字符(字符串以'\0‘结尾,这个'\0'即空字符)

%p

void *

以16进制形式输出指针

%%

%

输出一个百分号

printf附加格式:

字符

含义

l(字母l)

附加在d,u,x,o前面,表示长整数

-

左对齐

m(代表一个整数)

数据最小宽度

0(数字0)

将输出的前面补上0直到占满指定列宽为止不可以搭配使用-

m.n(代表一个整数)

m指域宽,即对应的输出项在输出设备上所占的字符数。n指精度,用于说明输出的实型数的小数位数。对数值型的来说,未指定n时,隐含的精度为n=6位。

#include <stdio.h>
int main()
{
	int a = 100;
	printf("a = %d\n", a);//格式化输出一个字符串
	printf("%p\n", &a);//输出变量a在内存中的地址编号
	printf("%%d\n");

	char c = 'a';
	putchar(c);//putchar只有一个参数,就是要输出的char
	long a2 = 100;
	printf("%ld, %lx, %lo\n", a2, a2, a2);

	long long a3 = 1000;
	printf("%lld, %llx, %llo\n", a3, a3, a3);

	int abc = 10;
	printf("abc = '%6d'\n", abc);
	printf("abc = '%-6d'\n", abc);
	printf("abc = '%06d'\n", abc);
	printf("abc = '%-06d'\n", abc);

	double d = 12.3;
	printf("d = \' %-10.3lf \'\n", d);

	return 0;
}

2.2.11.3scanf函数与getchar函数

  1. getchar是从标准输入设备读取一个char。
  2. scanf通过%转义的方式可以得到用户通过标准输入设备输入的数据
    #include <stdio.h>
    
    int main()
    {
    	char ch1;
    	char ch2;
    	char ch3;
    	int a;
    	int b;
    
    	printf("请输入ch1的字符:");
    	ch1 = getchar();
    	printf("ch1 = %c\n", ch1);
    
    	getchar(); //测试此处getchar()的作用
    
    	printf("请输入ch2的字符:");
    	ch2 = getchar();
    	printf("\'ch2 = %ctest\'\n", ch2);
    
    	getchar(); //测试此处getchar()的作用
    	printf("请输入ch3的字符:");
    	scanf("%c", &ch3);//这里第二个参数一定是变量的地址,而不是变量名
    	printf("ch3 = %c\n", ch3);
    
    	printf("请输入a的值:");
    	scanf("%d", &a);
    	printf("a = %d\n", a);
    
    	printf("请输入b的值:");
    	scanf("%d", &b);
    	printf("b = %d\n", b);
    
    	return 0;
    }
    

3运算符与表达式

3.1常用运算符分类

运算符类型

作用

算术运算符

用于处理四则运算

赋值运算符

用于将表达式的值赋给变量

比较运算符

用于表达式的比较,并返回一个真值或假值

逻辑运算符

用于根据表达式的值返回真值或假值

位运算符

用于处理数据的位运算

sizeof运算符

用于求字节数长度

3.1.1算术运算符

运算符

术语

示例

结果

+

正号

+3

3

-

负号

-3

-3

+

10 + 5

15

-

10 - 5

5

*

10 * 5

50

/

10 / 5

2

%

取模(取余)

10 % 3

1

++

前自增

a=2; b=++a;

a=3; b=3;

++

后自增

a=2; b=a++;

a=3; b=2;

--

前自减

a=2; b=--a;

a=1; b=1;

--

后自减

a=2; b=a--;

a=1; b=2;

3.1.2 赋值运算符

运算符

术语

示例

结果

=

赋值

a=2; b=3;

a=2; b=3;

+=

加等于

a=0; a+=2;

a=2;

-=

减等于

a=5; a-=3;

a=2;

*=

乘等于

a=2; a*=2;

a=4;

/=

除等于

a=4; a/=2;

a=2;

%=

模等于

a=3; a%2;

a=1;

3.1.3  比较运算符

C 语言的比较运算中, “真”用数字“1”来表示, “假”用数字“0”来表示。

运算符

术语

示例

结果

==

相等于

4 == 3

0

!=

不等于

4 != 3

1

<

小于

4 < 3

0

>

大于

4 > 3

1

<=

小于等于

4 <= 3

0

>=

大于等于

4 >= 1

1

3.1.4逻辑运算符

运算符

术语

示例

结果

!

!a

如果a为假,则!a为真;

如果a为真,则!a为假。

&&

a && b

如果a和b都为真,则结果为真,否则为假。

||

a || b

如果a和b有一个为真,则结果为真,二者都为假时,结果为假。

3.1.5运算符优先级

优先级

运算符

名称或含义

使用形式

结合方向

说明

1

[]

数组下标

数组名[常量表达式]

左到右

--

()

圆括号

(表达式)/函数名(形参表)

--

.

成员选择(对象)

对象.成员名

--

->

成员选择(指针)

对象指针->成员名

--

2

-

负号运算符

-表达式

右到左

单目运算符

~

按位取反运算符

~表达式

++

自增运算符

++变量名/变量名++

--

自减运算符

--变量名/变量名--

*

取值运算符

*指针变量

&

取地址运算符

&变量名

!

逻辑非运算符

!表达式

(类型)

强制类型转换

(数据类型)表达式

--

sizeof

长度运算符

sizeof(表达式)

--

3

/

表达式/表达式

左到右

双目运算符

*

表达式*表达式

%

余数(取模)

整型表达式%整型表达式

4

+

表达式+表达式

左到右

双目运算符

-

表达式-表达式

5

<<

左移

变量<<表达式

左到右

双目运算符

>>

右移

变量>>表达式

6

>

大于

表达式>表达式

左到右

双目运算符

>=

大于等于

表达式>=表达式

<

小于

表达式<表达式

<=

小于等于

表达式<=表达式

7

==

等于

表达式==表达式

左到右

双目运算符

!=

不等于

表达式!= 表达式

8

&

按位与

表达式&表达式

左到右

双目运算符

9

^

按位异或

表达式^表达式

左到右

双目运算符

10

|

按位或

表达式|表达式

左到右

双目运算符

11

&&

逻辑与

表达式&&表达式

左到右

双目运算符

12

||

逻辑或

表达式||表达式

左到右

双目运算符

13

?:

条件运算符

表达式1?

表达式2: 表达式3

右到左

三目运算符

14

=

赋值运算符

变量=表达式

右到左

--

/=

除后赋值

变量/=表达式

--

*=

乘后赋值

变量*=表达式

--

%=

取模后赋值

变量%=表达式

--

+=

加后赋值

变量+=表达式

--

-=

减后赋值

变量-=表达式

--

<<=

左移后赋值

变量<<=表达式

--

>>=

右移后赋值

变量>>=表达式

--

&=

按位与后赋值

变量&=表达式

--

^=

按位异或后赋值

变量^=表达式

--

|=

按位或后赋值

变量|=表达式

--

15

逗号运算符

表达式,表达式,…

左到右

--

3.2类型转换

数据有不同的类型,不同类型数据之间进行混合运算时必然涉及到类型的转换问题

转换的方法有两种:

  1. 自动转换(隐式转换):遵循一定的规则,由编译系统自动完成。
  2. 强制类型转换:把表达式的运算结果强制转换成所需的数据类型。

类型转换的原则:占用内存字节数少(值域小)的类型,向占用内存字节数多(值域大)的类型转换,以保证精度不降低。

3.2.1隐式转换

#include <stdio.h>

int main()
{
	int num = 5;
	printf("s1=%d\n", num / 2);
	printf("s2=%lf\n", num / 2.0);

	return 0;
}

3.2.2强制转换

强制类型转换指的是使用强制类型转换运算符,将一个变量或表达式转化成所需的类型,其基本语法格式如下所示:

#include <stdio.h>

int main()
{
	float x = 0;
	int i = 0;
	x = 3.6f;

	i = x;			//x为实型, i为整型,直接赋值会有警告
	i = (int)x;		//使用强制类型转换

	printf("x=%f, i=%d\n", x, i);

	return 0;
}

程序流程结构

C语言支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构。

  1. 顺序结构:程序按顺序执行,不发生跳转。
  2. 选择结构:依据是否满足条件,有选择的执行相应功能。
  3. 循环结构:依据条件是否满足,循环多次执行某段代码。

4.1选择结构

if语句

#include <stdio.h>

int main()
{
	int a = 1;
	int b = 2;

	if (a > b)
	{
		printf("%d\n", a);
	}

	return 0;
} 

 if…else语句

#include <stdio.h>
int main()
{
	int a = 1;
	int b = 2;

	if (a > b)
	{
		printf("%d\n", a);
	}
	else
	{
		printf("%d\n", b);
	}
	return 0;
}

if…else if…else语句

#include <stdio.h>

int main()
{
	unsigned int a;
	scanf("%u", &a);

	if (a < 10)
	{
		printf("个位\n");
	}
	else if (a < 100)
	{
		printf("十位\n");
	}
	else if (a < 1000)
	{
		printf("百位\n");
	}
	else
	{
		printf("很大\n");
	}

	return 0;
}

三目运算符

#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c;

	if (a > b)
	{
		c = a;
	}
	else
	{
		c = b;
	}
	printf("c1 = %d\n", c);

	a = 1;
	b = 2;
	c = ( a > b ? a : b );
	printf("c2 = %d\n", c);

	return 0;
}

switch语句

#include <stdio.h>

int main()
{
	char c;
	c = getchar();

	switch (c) //参数只能是整型变量
	{
	case '1':
		printf("OK\n");
		break;//switch遇到break就中断了
	case '2':
		printf("not OK\n");
		break;
	default://如果上面的条件都不满足,那么执行default
		printf("are u ok?\n");
	}
	return 0;
}

4.2循环结构

while语句

#include <stdio.h>

int main()
{
	int a = 20;
	while (a > 10)
	{
		scanf("%d", &a);
		printf("a = %d\n", a);
	}

	return 0;
}

do…while语句

#include <stdio.h>

int main()
{
	int a = 1;
	do
	{
		a++;
		printf("a = %d\n", a);
	} while (a < 10);

	return 0;
}

 for语句

#include <stdio.h>

int main()
{
	int i;
	int sum = 0;
	for (i = 0; i <= 100; i++)
	{
		sum += i;

	}

	printf("sum = %d\n", sum);

	return 0;
}

拓展 嵌套循环

#include <stdio.h>

int main()
{
	int num = 0;
	int i, j, k;
	for (i = 0; i < 10; i++)
	{
		for (j = 0; j < 10; j++)
		{
			for (k = 0; k < 10; k++)
			{
				printf("hello world\n");
				num++;
			}
		}
	}

	printf("num = %d\n", num);

	return 0;
}

4.3 跳转语句break、continue、goto

4.3.1 break语句

在switch条件语句和循环语句中都可以使用break语句:

  1. 当它出现在switch条件语句中时,作用是终止某个case并跳出switch结构。
  2. 当它出现在循环语句中,作用是跳出当前内循环语句,执行后面的代码。
  3. 当它出现在嵌套循环语句中,跳出最近的内循环语句,执行后面的代码。
#include <stdio.h>

int main()
{
	int i = 0;
	while (1)
	{
		i++;
		printf("i = %d\n", i);

		if (i == 10)
		{
			break; //跳出while循环
		}
	}

	int flag = 0;
	int m = 0;
	int n = 0;

	for (m = 0; m < 10; m++)
	{
		for (n = 0; n < 10; n++)
		{
			if (n == 5)
			{
				flag = 1;
				break; //跳出for (n = 0; n < 10; n++)
			}
		}

		if (flag == 1)
		{
			break; //跳出for (m = 0; m < 10; m++)
		}
	}

	return 0;
}

4.3.2 continue语句

在循环语句中,如果希望立即终止本次循环,并执行下一次循环,此时就需要使用continue语句。

#include<stdio.h>

int main()
{
	int sum = 0;           //定义变量sum

	for (int i = 1; i <= 100; i++)
	{
		if (i % 2 == 0)   //如果i是一个偶数,执行if语句中的代码
		{
			continue;      //结束本次循环
		}
		sum += i;          //实现sum和i的累加
	}

	printf("sum = %d\n", sum);

	return 0;
}

4.3.3goto语句(无条件跳转,尽量少用)

#include <stdio.h>

int main()
{
	goto End; //无条件跳转到End的标识
	printf("aaaaaaaaa\n");

End:
	printf("bbbbbbbb\n");

	return 0;
}

 5 数组和字符串

5.1数组

概念 :在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来——称为数组。

数组就是在内存中连续的相同类型的变量空间。同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的。

数组属于构造数据类型:

  1. 一个数组可以分解为多个数组元素:这些数组元素可以是基本数据类型或构造类型

int a[10]; 

struct Stu boy[10];

2.按数组元素类型的不同,数组可分为:数值数组、字符数组、指针数组、结构数组等类别。

int a[10];

char s[10];

char *p[10];

通常情况下,数组元素下标的个数也称为维数,根据维数的不同,可将数组分为一维数组、二维数组、三维数组、四维数组等。通常情况下,我们将二维及以上的数组称为多维数组。 

5.1.1 一维数组的定义和使用

  1. 数组名字符合标识符的书写规定(数字、英文字母、下划线)
  2. 数组名不能与其它变量名相同,同一作用域内是唯一的
  3. 方括号[]中常量表达式表示数组元素的个数

int a[3]表示数组a有3个元素

其下标从0开始计算,因此3个元素分别为a[0],a[1],a[2]

定义数组时[]内最好是常量,使用数组时[]内即可是常量,也可以是变量

#include <stdio.h>

int main()
{
	int a[10];//定义了一个数组,名字叫a,有10个成员,每个成员都是int类型
	//a[0]…… a[9],没有a[10]
	//没有a这个变量,a是数组的名字,但不是变量名,它是常量
	a[0] = 0;
	//……
	a[9] = 9;

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		a[i] = i; //给数组赋值
	}

	//遍历数组,并输出每个成员的值
	for (i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

 一维数组的初始化

        在定义数组的同时进行赋值,称为初始化。全局数组若不初始化,编译器将其初始化为零。局部数组若不初始化,内容为随机值

int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定义一个数组,同时初始化所有成员变量

int a[10] = { 1, 2, 3 };//初始化前三个成员,后面所有元素都设置为0

int a[10] = { 0 };//所有的成员都设置为0

 //[]中不定义元素个数,定义时必须初始化

 int a[] = { 1, 2, 3, 4, 5 };//定义了一个数组,有5个成员

数组名是一个地址的常量,代表数组中首元素的地址

#include <stdio.h>

int main()
{
	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定义一个数组,同时初始化所有成员变量

	printf("a = %p\n", a);
	printf("&a[0] = %p\n", &a[0]);

	int n = sizeof(a); //数组占用内存的大小,10个int类型,10 * 4  = 40
	int n0 = sizeof(a[0]);//数组第0个元素占用内存大小,第0个元素为int,4

	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

  一维数组的最值

#include <stdio.h>

int main()
{
	int a[] = {  1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定义一个数组,同时初始化所有成员变量

	int i = 0;
	int max = a[0];
	for (i = 1; i < sizeof(a) / sizeof(a[0]); i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
	}
	printf("数组中最大值为:%d\n", max);

	return 0;
}

一维数组的逆置

#include <stdio.h>

int main()
{
	int a[] = {  1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定义一个数组,同时初始化所有成员变量

	int i = 0;
	int j = sizeof(a) / sizeof(a[0]) -1;
	int tmp;

	while (i < j)
	{
		i++;
		j--;

		tmp = a[i];
		a[i] = a[j];
		a[j] = tmp;
	}

	for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

  冒泡法排序

#include <stdio.h>

int main()
{
	int a[] = {  1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定义一个数组,同时初始化所有成员变量

	int i = 0;
	int j = 0;
	int n = sizeof(a) / sizeof(a[0]);
	int tmp;

	//1、流程
	//2、试数
	for (i = 0; i < n-1; i++)
	{
		for (j = 0; j < n - i -1 ; j++)//内循环的目的是比较相邻的元素,把大的放到后面
		{
			if (a[j]  > a[j + 1])
			{
				tmp = a[j];
				a[j] = a[j+1];
				a[j+1] = tmp;
			}
		}
	}

	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

5.1.2二维数组

二维数组定义的一般形式是:类型说明符 数组名[常量表达式1][常量表达式2]

其中常量表达式1表示第一维下标的长度,常量表达式2 表示第二维下标的长度。

int a[3][4];

  1. 定义了一个三行四列的数组,数组名为a其元素类型为整型,该数组的元素个数为3×4个,即:

 

二维数组a是按行进行存放的,先存放a[0]行,再存放a[1]行、a[2]行,并且每行有四个元素,也是依次存放的。

二维数组在概念上是二维的:其下标在两个方向上变化,对其访问一般需要两个下标。

在内存中并不存在二维数组,二维数组实际的硬件存储器是连续编址的,也就是说内存中只有一维数组,即放完一行之后顺次放入第二行,和一维数组存放方式是一样的。

#include <stdio.h>

int main()
{
	//定义了一个二维数组,名字叫a
	//由3个一维数组组成,这个一维数组是int [4]
	//这3个一维数组的数组名分别为a[0],a[1],a[2]
	int a[3][4];

	a[0][0] = 0;
	//……
	a[2][3] = 12;

	//给数组每个元素赋值
	int i = 0;
	int j = 0;
	int num = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			a[i][j] = num++;
		}
	}

	//遍历数组,并输出每个成员的值
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d, ", a[i][j]);
		}
		printf("\n");
	}

	return 0;
}

 二维数组的初始化

       int a[3][4] =

       {

              { 1, 2, 3, 4 },

              { 5, 6, 7, 8, },

              { 9, 10, 11, 12 }

       };

       //连续赋值

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

       //可以只给部分元素赋初值,未初始化则为0

       int a[3][4] = { 1, 2, 3, 4  };

       //所有的成员都设置为0

       int a[3][4] = {0};

       //[]中不定义元素个数,定义时必须初始化

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

数组名是一个地址的常量,代表数组中首元素的地址

#include <stdio.h>

int main()
{
	//定义了一个二维数组,名字叫a
	//二维数组是本质上还是一维数组,此一维数组有3个元素
//每个元素又是一个一维数组int[4]
	int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12  };

	//数组名为数组首元素地址,二维数组的第0个元素为一维数组
	//第0个一维数组的数组名为a[0]
	printf("a = %p\n", a);
	printf("a[0] = %p\n", a[0]);
	
	//测二维数组所占内存空间,有3个一维数组,每个一维数组的空间为4*4
	//sizeof(a) = 3 * 4 * 4 = 48
	printf("sizeof(a) = %d\n", sizeof(a));

	//测第0个元素所占内存空间,a[0]为第0个一维数组int[4]的数组名,4*4=16
	printf("sizeof(a[0]) = %d\n", sizeof(a[0]) );

	//测第0行0列元素所占内存空间,第0行0列元素为一个int类型,4字节
	printf("sizeof(a[0][0]) = %d\n", sizeof(a[0][0]));

	//求二维数组行数
	printf("i = %d\n", sizeof(a) / sizeof(a[0]));

	// 求二维数组列数
	printf("j = %d\n", sizeof(a[0]) / sizeof(a[0][0]));

	//求二维数组行*列总数
	printf("n = %d\n", sizeof(a) / sizeof(a[0][0]));

	return 0;
}
#include <stdio.h>

int main()
{
	//二维数组:  五行、三列
	//行代表人:  老大到老五
	//列代表科目:语、数、外
	float a[5][3] = { { 80, 75, 56 }, { 59, 65, 71 },
                     { 59, 63, 70 },{ 85, 45, 90 }, { 76, 77, 45 } };

	int i, j, person_low[3] = { 0 };
	float s = 0, lesson_aver[3] = { 0 };

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			s = s + a[j][i];
			if (a[j][i] < 60)
			{
				person_low[i]++;
			}
		}

		lesson_aver[i] = s / 5;
		s = 0;
	}

	printf("各科的平均成绩:\n");
	for (i = 0; i < 3; i++)
	{
		printf("%.2f\n", lesson_aver[i]);
	}
		
	printf("各科不及格的人数:\n");
	for (i = 0; i < 3; i++)
	{
		printf("%d\n", person_low[i]);
	}
		
	return 0;
}

5.1.3多维数组

多维数组的定义与二维数组类似,其语法格式具体如下:

数组类型修饰符 数组名 [n1][n2]…[nn]; /*由于很少用到多维数组就粗略介绍一下*/

int a[3][4][5];  

#include <stdio.h>

int main()
{
	//int a[3][4][5] ;//定义了一个三维数组,有3个二维数组int[4][5]
	int a[3][4][5] = { { { 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10 }, { 0 }, { 0 } }, { { 0 }, { 0 }, { 0 }, { 0 } }, { { 0 }, { 0 }, { 0 }, { 0 } } };

	int i, j, k;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			for (k = 0; k < 5; k++)
			{
				//添加访问元素代码
				printf("%d, ", a[i][j][k]);
			}
			printf("\n");
		}
	}
	return 0;
}

5.2字符串

5.2.1字符数组与字符串区别

  1. C语言中没有字符串这种数据类型,可以通过char的数组来替代;
  2. 字符串一定是一个char的数组,但char的数组未必是字符串;
  3. 数字0(和字符‘\0’等价)结尾的char数组就是一个字符串,但如果char数组没有以数字0结尾,那么就不是一个字符串,只是普通字符数组,所以字符串是一种特殊的char的数组
    #include <stdio.h>
    
    int main()
    {
    	char c1[] = { 'c', ' ', 'p', 'r', 'o', 'g' }; //普通字符数组
    	printf("c1 = %s\n", c1); //乱码,因为没有’\0’结束符
    
    	//以‘\0’(‘\0’就是数字0)结尾的字符数组是字符串
    	char c2[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0'}; 
    	printf("c2 = %s\n", c2);
    
    	//字符串处理以‘\0’(数字0)作为结束符,后面的'h', 'l', 'l', 'e', 'o'不会输出
    	char c3[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0', 'h', 'l', 'l', 'e', 'o', '\0'};
    	printf("c3 = %s\n", c3);
    
    	return 0;
    }
    

     字符串的初始化

    #include <stdio.h>
    
    // C语言没有字符串类型,通过字符数组模拟
    // C语言字符串,以字符‘\0’, 数字0
    int main()
    {
    	//不指定长度, 没有0结束符,有多少个元素就有多长
    	char buf[] = { 'a', 'b', 'c' };
    	printf("buf = %s\n", buf);	//乱码
    
    	//指定长度,后面没有赋值的元素,自动补0
    	char buf2[100] = { 'a', 'b', 'c' };
    char buf[1000]={“hello”};
    	printf("buf2 = %s\n", buf2);
    
    	//所有元素赋值为0
    	char buf3[100] = { 0 };
    
    	//char buf4[2] = { '1', '2', '3' };//数组越界
    
    	char buf5[50] = { '1', 'a', 'b', '0', '7' };
    	printf("buf5 = %s\n", buf5);
    
    	char buf6[50] = { '1', 'a', 'b', 0, '7' };
    	printf("buf6 = %s\n", buf6);
    
    	char buf7[50] = { '1', 'a', 'b', '\0', '7' };
    	printf("buf7 = %s\n", buf7);
    
    	//使用字符串初始化,编译器自动在后面补0,常用
    	char buf8[] = "agjdslgjlsdjg";
    
    	//'\0'后面最好不要连着数字,有可能几个数字连起来刚好是一个转义字符
    	//'\ddd'八进制字义字符,'\xdd'十六进制转移字符
    	// \012相当于\n
    	char str[] = "\012abc";
    	printf("str == %s\n", str);
    
    	return 0;
    }
    

 5.2.1字符串的输入输出

 由于字符串采用了'\0'标志,字符串的输入输出将变得简单方便。

#include <stdio.h>

int main()
{
	char str[100];
   
	printf("input string1 : \n");
	scanf("%s", str);//scanf(“%s”,str)默认以空格分隔
	printf("output:%s\n", str);

	return 0;
}

gets();

#include <stdio.h>

char *gets(char *s);

功能:从标准输入读入字符,并保存到s指定的内存空间,直到出现换行符或读到文件结尾为止。

参数

       s:字符串首地址

返回值

       成功:读入的字符串

       失败:NULL

gets(str)与scanf(“%s”,str)的区别:

  1. gets(str)允许输入的字符串含有空格
  2. scanf(“%s”,str)不允许含有空格
  3. 注意:由于scanf()和gets()无法知道字符串s大小,必须遇到换行符或读到文件结尾为止才接收输入,因此容易导致字符数组越界(缓冲区溢出)的情况。
  4. char str[100];

           printf("请输入str: ");

           gets(str);

           printf("str = %s\n", str);

fgets();

#include <stdio.h>

char *fgets(char *s, int size, FILE *stream);

功能:从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。

参数

       s:字符串

       size:指定最大读取字符串的长度(size - 1)

       stream:文件指针,如果读键盘输入的字符串,固定写为stdin

返回值

       成功:成功读取的字符串

       读到文件尾或出错: NULL

fgets()在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车也做为字符串的一部分。通过scanf和gets输入一个字符串的时候,不包含结尾的“\n”,但通过fgets结尾多了“\n”。fgets()函数是安全的,不存在缓冲区溢出的问题。

char str[100];

       printf("请输入str: ");

       fgets(str, sizeof(str), stdin);

       printf("str = \"%s\"\n", str);

 puts();

#include <stdio.h>

int puts(const char *s);

功能:标准设备输出s字符串,在输出完成后自动输出一个'\n'

参数

       s:字符串首地址

返回值

       成功:非负数

       失败:-1

#include <stdio.h>

int main()
{
	printf("hello world");
	puts("hello world");

	return 0;
}

#include <stdio.h>

int fputs(const char * str, FILE * stream);

功能:将str所指定的字符串写入到stream指定的文件中 字符串结束符 '\0'  不写入文件。

参数:

       str:字符串

       stream:文件指针如果字符串输出到屏幕,固定写为stdout

返回值:

       成功:0

       失败:-1

 fputs()是puts()的文件操作版本,但fputs()不会自动输出一个'\n'。

printf ("hello world");

puts("hello world");

fputs("hello world", stdout);

#include <string.h>

size_t strlen(const char *s);

功能:计算指定指定字符串s的长度,不包含字符串结束符‘\0’

参数

s:字符串首地址

返回值:字符串s的长度,size_t为unsigned int类型

       char str[] = "abc\0defg";

       int n = strlen(str);

       printf("n = %d\n", n);

#include <stdio.h>

int main()
{
	char str1[] = "abcdef";
	char str2[] = "123456";
	char dst[100];

	int i = 0;
	while (str1[i] != 0)
	{
		dst[i] = str1[i];
		i++;
	}

	int j = 0;
	while (str2[j] != 0)
	{
		dst[i + j] = str2[j];
		j++;
	}
	dst[i + j] = 0; //字符串结束符

	printf("dst = %s\n", dst);

	return 0;
}

 6 函数

6.1函数分类

C 程序是由函数组成的,我们写的代码都是由主函数 main()开始执行的。函数是 C 程序的基本模块,是用于完成特定任务的程序代码单元。

从函数定义的角度看,函数可分为系统函数和用户定义函数两种:

  1. 系统函数,即库函数:这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用它们,如我们常用的打印函数printf()。
  2. 用户定义函数:用以解决用户的专门需要。

6.1.1函数的作用

  1. 函数的使用可以省去重复代码的编写,降低代码重复率
// 求两数的最大值
int max(int a, int b)
{
	if (a > b){
		return a;
	}
	else{
		return b;
	}
}

int main()
{
	// 操作1 ……
	// ……
	int a1 = 10, b1 = 20, c1 = 0;
	c1 = max(a1, b1); // 调用max()

	// 操作2 ……
	// ……
	int a2 = 11, b2 = 21, c2 = 0;
	c2 = max(a2, b2); // 调用max()

	// ……

	return 0;
}
  1. 函数可以让程序更加模块化,从而有利于程序的阅读,修改和完善
  2. 假如我们编写一个实现以下功能的程序:读入一行数字;对数字进行排序;找到它们的平均值;打印出一个柱状图。如果我们把这些操作直接写在main()里,这样可能会给用户感觉代码会有点凌乱。但,假如我们使用函数,这样可以让程序更加清晰、模块化:

3 函数的调用:产生随机数

当调用函数时,需要关心5要素:

  1. 头文件:包含指定的头文件
  2. 函数名字:函数名字必须和头文件声明的名字一样
  3. 功能:需要知道此函数能干嘛后才调用
  4. 参数:参数类型要匹配
  5. 返回值:根据需要接收返回值
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main()
{
	time_t tm = time(NULL);//得到系统时间
	srand((unsigned int)tm);//随机种子只需要设置一次即可

	int r = rand();
	printf("r = %d\n", r);

	return 0;
}

 6.2 函数的定义

返回类型 函数名(形式参数列表)

 {

              数据定义部分;  /*函数体部分*/

              执行语句部分;

}

 6.3函数的声明

如果使用用户自己定义的函数,而该函数与调用它的函数(即主调函数)不在同一文件中,或者函数定义的位置在主调函数之后,则必须在调用此函数之前对被调用的函数作声明。

所谓函数声明,就是在函数尚在未定义的情况下,事先将该函数的有关信息通知编译系统,相当于告诉编译器,函数在后面定义,以便使编译能正常进行。

注意:一个函数只能被定义一次,但可以声明多次。

6.4 main函数与exit函数

在main函数中调用exit和return结果是一样的,但在子函数中调用return只是代表子函数终止了,在子函数中调用exit,那么程序终止。

#include <stdio.h>

#include <stdlib.h>

void fun()

{

       printf("fun\n");

       //return;

       exit(0); /*使用了这个函数之后整个进程退出*/

}

int main()

{

       fun();

       while (1);

       return 0;

}

6.5 多文件(分文件)编程

  1. 把函数声明放在头文件xxx.h中,在主函数中包含相应头文件
  2. 在头文件对应的xxx.c中实现xxx.h声明的函数

防止头文件重复包含

当一个项目比较大时,往往都是分文件,这时候有可能不小心把同一个头文件 include 多次,或者头文件嵌套包含。重复包含编译会报错

为了避免同一个文件被include多次,C/C++中有两种方式,一种是 #ifndef 方式,一种是 #pragma once 方式。

#ifndef __SOMEFILE_H__

#define __SOMEFILE_H__

// 声明语句

#endif

c++   #pragma once

7 指针

7.1内存

内存含义:

  1. 存储器:计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分。
  2. 内存:内部存贮器,暂存程序/数据——掉电丢失 SRAM、DRAM、DDR、DDR2、DDR3。
  3. 外存:外部存储器,长时间保存程序/数据—掉电不丢ROM、ERRROM、FLASH(NAND、NOR)、硬盘、光盘。

内存是沟通CPU与硬盘的桥梁:

  1. 暂存放CPU中的运算数据
  2. 暂存与硬盘等外部存储器交换的数据

有关内存的两个概念:物理存储器和存储地址空间。

物理存储器:实际存在的具体存储器芯片。

  1. 主板上装插的内存条
  2. 显示卡上的显示RAM芯片
  3. 各种适配卡上的RAM芯片和ROM芯片

存储地址空间:对存储器编码的范围。我们在软件上常说的内存是指这一层含义。

  1. 编码:对每个物理存储单元(一个字节)分配一个号码
  2. 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写
  3. 将内存抽象成一个很大的一维字符数组。
  4. 编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关)。
  5. 这个内存编号我们称之为内存地址。
  6. 内存中的每一个数据都会分配相应的地址:

  7. char:占一个字节分配一个地址
  8. int: 占四个字节分配四个地址
  9. float、struct、函数、数组等

7.2 指针基础知识

指针变量的定义和使用

  1. 指针也是一种数据类型,指针变量也是一种变量
  2. 指针变量指向谁,就把谁的地址赋值给指针变量
  3. “*”操作符操作的是指针变量指向的内存空间
  4. &可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的
#include <stdio.h>

int main()
{
	int a = 0;
	char b = 100;
	printf("%p, %p\n", &a, &b); //打印a, b的地址

	//int *代表是一种数据类型,int*指针类型,p才是变量名
	//定义了一个指针类型的变量,可以指向一个int类型变量的地址
	int *p;
	p = &a;//将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
	printf("%d\n", *p);//p指向了a的地址,*p就是a的值

	char *p1 = &b;
	printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值

	return 0;
}

 通过指针间接修改变量的值

int a = 0;

       int b = 11;

       int *p = &a;

       *p = 100;

       printf("a = %d, *p = %d\n", a, *p);

       p = &b;

       *p = 22;

       printf("b = %d, *p = %d\n", b, *p);

 指针大小

  1. 使用sizeof()测量指针的大小,得到的总是:4或8
  2. sizeof()测的是指针变量指向存储地址的大小
  3. 在32位平台,所有的指针(地址)都是32位(4字节)
  4. 在64位平台,所有的指针(地址)都是64位(8字节)

int *p1;

       int **p2;

       char *p3;

       char **p4;

       printf("sizeof(p1) = %d\n", sizeof(p1));

       printf("sizeof(p2) = %d\n", sizeof(p2));

       printf("sizeof(p3) = %d\n", sizeof(p3));

       printf("sizeof(p4) = %d\n", sizeof(p4));

       printf("sizeof(double *) = %d\n", sizeof(double *));

 野指针和空指针

指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题

int a = 100;

 int *p;

       p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义

       p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义

       *p = 1000;  //操作野指针指向未知区域,内存出问题,err

但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。

int *p = NULL;

NULL是一个值为0的宏常量:

#define NULL    ((void *)0)

 万能指针void *

void *指针可以指向任意变量的内存空间:

       void *p = NULL;

       int a = 10;

       p = (void *)&a; //指向变量时,最好转换为void *

       //使用指针变量指向的内存时,转换为int *

       *( (int *)p ) = 11;

       printf("a = %d\n", a);

const修饰的指针变量

       int a = 100;

       int b = 200;

//指向常量的指针

       //修饰*,指针指向内存区域不能修改,指针指向可以变

       const int * p1 = &a; //等价于int const *p1 = &a;

       //*p1 = 111; //err

       p1 = &b; //ok

//指针常量

       //修饰p1,指针指向不能变,指针指向的内存可以修改

       int * const p2 = &a;

       //p2 = &b; //err

       *p2 = 222; //ok

在编辑程序时,指针作为函数参数,如果不想修改指针对应内存空间的值,需要使用const修饰指针数据类型。

7.3 指针和数组

数组名字是数组的首元素地址,但它是一个地址常量:

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

       printf("a = %p\n", a);

       printf("&a[0] = %p\n", &a[0]);

       //a = 10; //err, 数组名只是常量,不能修改

指针操作数组元素

#include <stdio.h>

int  main()

{

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

       int i = 0;

       int n = sizeof(a) / sizeof(a[0]);

      

       for (i = 0; i < n; i++)

       {

              //printf("%d, ", a[i]);

              printf("%d, ", *(a+i));

       }

       printf("\n");

       int *p = a;    //定义一个指针变量保存a的地址

       for (i = 0; i < n; i++)

       {

              p[i] = 2 * i;

       }

       for (i = 0; i < n; i++)

       {

              printf("%d, ", *(p + i));

       }

       printf("\n");

       return 0;

}

 7.4指针加减运算

1)加法运算

  1. 指针计算不是简单的整数相加
  2. 如果是一个int *,+1的结果是增加一个int的大小
  3. 如果是一个char *,+1的结果是增加一个char大小

#include <stdio.h>

int main()

{

       int a;

       int *p = &a;

       printf("%d\n", p);

       p += 2;//移动了2个int

       printf("%d\n", p);

       char b = 0;

       char *p1 = &b;

       printf("%d\n", p1);

       p1 += 2;//移动了2个char

       printf("%d\n", p1);

       return 0;

}

通过改变指针指向操作数组元素:

#include <stdio.h>

int main()

{

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

       int i = 0;

       int n = sizeof(a) / sizeof(a[0]);

       int *p = a;

       for (i = 0; i < n; i++)

       {

              printf("%d, ", *p);

              p++;

       }

       printf("\n");

      

       return 0;

}

减法运算

示例1:

#include <stdio.h>

int main()

{

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

       int i = 0;

       int n = sizeof(a) / sizeof(a[0]);

       int *p = a+n-1;

       for (i = 0; i < n; i++)

       {

              printf("%d, ", *p);

              p--;

       }

       printf("\n");

       return 0;

}

示例2:

#include <stdio.h>

int main()

{

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

       int *p2 = &a[2]; //第2个元素地址

       int *p1 = &a[1]; //第1个元素地址

       printf("p1 = %p, p2 = %p\n", p1, p2);

       int n1 = p2 - p1; //n1 = 1

       int n2 = (int)p2 - (int)p1; //n2 = 4

       printf("n1 = %d, n2 = %d\n", n1, n2);

      

       return 0;

}

 指针数组

指针数组,它是数组,数组的每个元素都是指针类型。

#include <stdio.h>

int main()

{

       //指针数组

       int *p[3];

       int a = 1;

       int b = 2;

       int c = 3;

       int i = 0;

       p[0] = &a;

       p[1] = &b;

       p[2] = &c;

       for (i = 0; i < sizeof(p) / sizeof(p[0]); i++ )

       {

              printf("%d, ", *(p[i]));

       }

       printf("\n");

      

       return 0;

}

7.5 多级指针

  1. C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
  2. 二级指针就是指向一个一级指针变量地址的指针。
  3. 三级指针基本用不着,但考试会考。

       int a = 10;

       int *p = &a; //一级指针

       *p = 100; //*p就是a

       int **q = &p;

       //*q就是p

       //**q就是a

       int ***t = &q;

       //*t就是q

       //**t就是p

       //***t就是a

 7.6指针和函数

 函数形参改变实参的值

#include <stdio.h>

void swap1(int x, int y)

{

       int tmp;

       tmp = x;

       x = y;

       y = tmp;

       printf("x = %d, y = %d\n", x, y);

}

void swap2(int *x, int *y)

{

       int tmp;

       tmp = *x;

       *x = *y;

       *y = tmp;

}

int main()

{

       int a = 3;

       int b = 5;

       swap1(a, b); //值传递

       printf("a = %d, b = %d\n", a, b);

       a = 3;

       b = 5;

       swap2(&a, &b); //地址传递

       printf("a2 = %d, b2 = %d\n", a, b);

       return 0;

}

数组名做函数参数

数组名做函数参数,函数的形参会退化为指针:

#include <stdio.h>

void printArrary(int *a, int n

{

       int i = 0;

       for (i = 0; i < n; i++)

       {

              printf("%d, ", a[i]);

       }

       printf("\n");

}

int main()

{

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

       int n = sizeof(a) / sizeof(a[0]);

       //数组名做函数参数

       printArrary(a, n);

       return 0;

}

指针做为函数的返回值

#include <stdio.h>

int a = 10;

int *getA()

{

       return &a;

}

int main()

{

       *( getA() ) = 111;

       printf("a = %d\n", a);

       return 0;

}

7.7 指针和字符串

 字符指针

#include <stdio.h>

int main()

{

       char str[] = "hello world";

       char *p = str;

       *p = 'm';

       p++;

       *p = 'i';

       printf("%s\n", str);

       p = "mike jiang";

       printf("%s\n", p);

       char *q = "test";

       printf("%s\n", q);

       return 0;

}

字符指针做函数参数

#include <stdio.h>

void mystrcat(char *dest, const char *src)

{

       int len1 = 0;

       int len2 = 0;

       while (dest[len1])

       {

              len1++;

       }

       while (src[len2])

       {

              len2++;

       }

       int i;

       for (i = 0; i < len2; i++)

       {

              dest[len1 + i] = src[i];

       }

}

int main()

{

       char dst[100] = "hello mike";

       char src[] = "123456";

      

       mystrcat(dst, src);

       printf("dst = %s\n", dst);

       return 0;

}

const修饰的指针变量

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

       //const修饰一个变量为只读

       const int a = 10;

       //a = 100; //err

       //指针变量, 指针指向的内存, 2个不同概念

       char buf[] = "aklgjdlsgjlkds";

       //从左往右看,跳过类型,看修饰哪个字符

       //如果是*, 说明指针指向的内存不能改变

       //如果是指针变量,说明指针的指向不能改变,指针的值不能修改

       const char *p = buf;

       // 等价于上面 char const *p1 = buf;

       //p[1] = '2'; //err

       p = "agdlsjaglkdsajgl"; //ok

       char * const p2 = buf;

       p2[1] = '3';

       //p2 = "salkjgldsjaglk"; //err

       //p3为只读,指向不能变,指向的内存也不能变

       const char * const p3 = buf;

       return 0;

}

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

int main(int argc, char *argv[]);

  1. main函数是操作系统调用的,第一个参数标明argc数组的成员数量,argv数组的每个成员都是char *类型
  2. argv是命令行参数的字符串数组
  3. argc代表命令行参数的数量,程序名字本身算一个参数

#include <stdio.h>

//argc: 传参数的个数(包含可执行程序)

//argv:指针数组,指向输入的参数

int main(int argc, char *argv[])

{

       //指针数组,它是数组,每个元素都是指针

       char *a[] = { "aaaaaaa", "bbbbbbbbbb", "ccccccc" };

       int i = 0;

       printf("argc = %d\n", argc);

       for (i = 0; i < argc; i++)

       {

              printf("%s\n", argv[i]);

       }

       return 0;

}

 7.9项目开发常用字符串应用模型

项目开发常用字符串应用模型

1) strstr中的while和do-while模型

利用strstr标准库函数找出一个字符串中substr出现的个数。

  1. while模型

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

       char *p = "11abcd111122abcd333abcd3322abcd3333322qqq";

       int n = 0;

       while ((p = strstr(p, "abcd")) != NULL)

       {

              //能进来,肯定有匹配的子串

              //重新设置起点位置

              p = p + strlen("abcd");

              n++;

              if (*p == 0) //如果到结束符

              {

                     break;

              }

       }

       printf("n = %d\n", n);

       return 0;

}

  1. do-while模型

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

       char *p = "11abcd111122abcd333abcd3322abcd3333322qqq";

       int n = 0;

       do

       {

              p = strstr(p, "abcd");

              if (p != NULL)

              {

                     n++; //累计个数

                     //重新设置查找的起点

                     p = p + strlen("abcd");

              }

              else //如果没有匹配的字符串,跳出循环

              {

                     break;

              }

       } while (*p != 0); //如果没有到结尾

       printf("n = %d\n", n);

       return 0;

}

两头堵模型

求非空字符串元素的个数:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <ctype.h>

int fun(char *p, int *n)

{

       if (p == NULL || n == NULL)

       {

              return -1;

       }

       int begin = 0;

       int end = strlen(p) - 1;

       //从左边开始

       //如果当前字符为空,而且没有结束

       while (p[begin] == ' ' && p[begin] != 0)

       {

              begin++; //位置从右移动一位

       }

       //从右往左移动

       while (p[end] == ' ' && end > 0)

       {

              end--; //往左移动

       }

       if (end == 0)

       {

              return -2;

       }

       //非空元素个数

       *n = end - begin + 1;

       return 0;

}

int main(void)

{

       char *p = "      abcddsgadsgefg      ";

       int ret = 0;

       int n = 0;

       ret = fun(p, &n);

       if (ret != 0)

       {

              return ret;

       }

       printf("非空字符串元素个数:%d\n", n);

       return 0;

}

 3) 字符串反转模型(逆置)

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int inverse(char *p)

{

       if (p == NULL)

       {

              return -1;

       }

       char *str = p;

       int begin = 0;

       int end = strlen(str) - 1;

       char tmp;

       while (begin < end)

       {

              //交换元素

              tmp = str[begin];

              str[begin] = str[end];

              str[end] = tmp;

              begin++;  //往右移动位置

              end--;        //往左移动位置

       }

       return 0;

}

int main(void)

{

       //char *str = "abcdefg"; //文件常量区,内容不允许修改

       char str[] = "abcdef";

       int ret = inverse(str);

       if (ret != 0)

       {

              return ret;

       }

       printf("str ========== %s\n", str);

       return 0;

}

 7.10 字符串处理函数

1) strcpy()

#include <string.h>

char *strcpy(char *dest, const char *src);

功能:把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去

参数

       dest:目的字符串首地址

       src:源字符首地址

返回值

       成功:返回dest字符串的首地址

       失败:NULL

注意:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。

char dest[20] = "123456789";

       char src[] = "hello world";

       strcpy(dest, src);

       printf("%s\n", dest);

2) strncpy()

#include <string.h>

char *strncpy(char *dest, const char *src, size_t n);

功能:把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含'\0'

参数

       dest:目的字符串首地址

       src:源字符首地址

       n:指定需要拷贝字符串个数

返回值

       成功:返回dest字符串的首地址

       失败:NULL

       char dest[20] ;

       char src[] = "hello world";

       strncpy(dest, src, 5);

       printf("%s\n", dest);

       dest[5] = '\0';

       printf("%s\n", dest);

3) strcat()

#include <string.h>

char *strcat(char *dest, const char *src);

功能:将src字符串连接到dest的尾部,‘\0’也会追加过去

参数

       dest:目的字符串首地址

       src:源字符首地址

返回值

       成功:返回dest字符串的首地址

       失败:NULL

       char str[20] = "123";

       char *src = "hello world";

       printf("%s\n", strcat(str, src));

4) strncat()

#include <string.h>

char *strncat(char *dest, const char *src, size_t n);

功能:将src字符串前n个字符连接到dest的尾部,‘\0’也会追加过去

参数

       dest:目的字符串首地址

       src:源字符首地址

       n:指定需要追加字符串个数

返回值

       成功:返回dest字符串的首地址

       失败:NULL

       char str[20] = "123";

       char *src = "hello world";

       printf("%s\n", strncat(str, src, 5));

5) strcmp()

#include <string.h>

int strcmp(const char *s1, const char *s2);

功能:比较 s1s2 的大小比较的是字符ASCII码大小

参数

       s1:字符串1首地址

       s2:字符串2首地址

返回值

       相等:0

       大于:>0 在不同操作系统strcmp结果会不同   返回ASCII差值

       小于:<0

      

char *str1 = "hello world";

       char *str2 = "hello mike";

       if (strcmp(str1, str2) == 0)

       {

              printf("str1==str2\n");

       }

       else if (strcmp(str1, str2) > 0)

       {

              printf("str1>str2\n");

       }    

       else

       {

              printf("str1<str2\n");

       }

6) strncmp()

#include <string.h>

int strncmp(const char *s1, const char *s2, size_t n);

功能:比较 s1s2 前n个字符的大小比较的是字符ASCII码大小

参数

       s1:字符串1首地址

       s2:字符串2首地址

       n:指定比较字符串的数量

返回值

       相等:0

       大于: > 0

       小于: < 0

      

char *str1 = "hello world";

       char *str2 = "hello mike";

       if (strncmp(str1, str2, 5) == 0)

       {

              printf("str1==str2\n");

       }

       else if (strcmp(str1, "hello world") > 0)

       {

              printf("str1>str2\n");

       }

       else

       {

              printf("str1<str2\n");

       }

7) sprintf()

#include <stdio.h>

int sprintf(char *str, const char *format, ...);

功能:根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 '\0'  为止。

参数

       str:字符串首地址

       format:字符串格式,用法和printf()一样

返回值

       成功:实际格式化的字符个数

       失败: - 1

       char dst[100] = { 0 };

       int a = 10;

       char src[] = "hello world";

       printf("a = %d, src = %s", a, src);

       printf("\n");

       int len = sprintf(dst, "a = %d, src = %s", a, src);

       printf("dst = \" %s\"\n", dst);

       printf("len = %d\n", len);

8) sscanf()

#include <stdio.h>

int sscanf(const char *str, const char *format, ...);

功能:从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。

参数

       str:指定的字符串首地址

       format:字符串格式,用法和scanf()一样

返回值

       成功:参数数目,成功转换的值的个数

       失败: - 1

       char src[] = "a=10, b=20";

       int a;

       int b;

       sscanf(src, "a=%d,  b=%d", &a, &b);

       printf("a:%d, b:%d\n", a, b);

9) strchr()

#include <string.h>

char *strchr(const char *s, int c);

功能:在字符串s中查找字母c出现的位置

参数

       s:字符串首地址

       c:匹配字母(字符)

返回值

       成功:返回第一次出现的c地址

       失败:NULL

       char src[] = "ddda123abcd";

       char *p = strchr(src, 'a');

       printf("p = %s\n", p);

10) strstr()

#include <string.h>

char *strstr(const char *haystack, const char *needle);

功能:在字符串haystack中查找字符串needle出现的位置

参数

       haystack:源字符串首地址

       needle:匹配字符串首地址

返回值

       成功:返回第一次出现的needle地址

       失败:NULL

       char src[] = "ddddabcd123abcd333abcd";

       char *p = strstr(src, "abcd");

       printf("p = %s\n", p);

11) strtok()

#include <string.h>

char *strtok(char *str, const char *delim);

功能:来将字符串分割成一个个片段。当strtok()在参数s的字符串中发现参数delim中包含的分割字符时, 则会将该字符改为\0 字符,当连续出现多个时只替换第一个为\0。

参数

       str:指向欲分割的字符串

       delim:为分割字符串中包含的所有字符

返回值

       成功:分割后字符串首地址

       失败:NULL

  1. 在第一次调用时:strtok()必需给予参数s字符串
  2. 往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针

       char a[100] = "adc*fvcv.ebcy*hghbdfg$casdert";

       char *s = strtok(a, ".*$");//将"*"分割的子串取出

       while (s != NULL)

       {

              printf("%s\n", s);

              s = strtok(NULL, "*");

       }

12) atoi()

#include <stdlib.h>

int atoi(const char *nptr);

功能:atoi()会扫描nptr字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数字或字符串结束符('\0')才结束转换,并将结果返回返回值。

参数

       nptr:待转换的字符串

返回值:成功转换后整数

类似的函数有:

  1. atof():把一个小数形式的字符串转化为一个浮点数。
  2. atol():将一个字符串转化为long类型

       char str1[] = "          -10";

       int num1 = atoi(str1);

       printf("num1 = %d\n", num1);

       char str2[] = "0.123";

       double num2 = atof(str2);

       printf("num2 = %lf\n", num2);

       char str3[] = "123L";

       long num3 = atol(str3);

       printf("num3 = %ld\n", num3);

 7.11指针小结

定义

说明

int  i

定义整形变量

int *p

定义一个指向int的指针变量

int a[10]

定义一个有10个元素的数组,每个元素类型为int

int *p[10]

定义一个有10个元素的数组,每个元素类型为int*

int func()

定义一个函数,返回值为int型

int *func()

定义一个函数,返回值为int *型

int **p

定义一个指向int的指针的指针,二级指针

8. 内存管

8.1作用域

 局部变量

局部变量也叫auto自动变量(auto可写可不写),一般情况下代码块{}内部定义的变量都是自动变量,它有如下特点:

  1. 在一个函数内定义,只在函数范围内有效
  2. 在复合语句中定义,只在复合语句中有效
  3. 随着函数调用的结束或复合语句的结束局部变量的声明声明周期也结束
  4. 如果没有赋初值,内容为随机

#include <stdio.h>

void test()

{

       //auto写不写是一样的

       //auto只能出现在{}内部

       auto int b = 10;

}

int main(void)

{

       //b = 100; //err, 在main作用域中没有b

       if (1)

       {

              //在复合语句中定义,只在复合语句中有效

              int a = 10;

              printf("a = %d\n", a);

       }

       //a = 10; //err离开if()的复合语句,a已经不存在

      

       return 0;

}

静态(static)局部变量

  1. static局部变量的作用域也是在定义的函数内有效
  2. static局部变量的生命周期和程序运行周期一样,同时staitc局部变量的值只初始化一次,但可以赋值多次
  3. static局部变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋空字符

#include <stdio.h>

void fun1()

{

       int i = 0;

       i++;

       printf("i = %d\n", i);

}

void fun2()

{

       //静态局部变量,没有赋值,系统赋值为0,而且只会初始化一次

       static int a;

       a++;

       printf("a = %d\n", a);

}

int main(void)

{

       fun1();

       fun1();

       fun2();

       fun2();

      

       return 0;

}

 8.1.1 全局变量

  1. 在函数外定义,可被本文件及其它文件中的函数所共用,若其它文件中的函数调用此变量,须用extern声明
  2. 全局变量的生命周期和程序运行周期一样
  3. 不同文件的全局变量不可重名

8.1.2 静态(static)全局变量

  1. 在函数外定义,作用范围被限制在所定义的文件中
  2. 不同文件静态全局变量可以重名,但作用域不冲突
  3. static全局变量的生命周期和程序运行周期一样,同时staitc全局变量的值只初始化一次

8.1.3extern全局变量声明

extern int a;声明一个变量,这个全局变量在别的文件中已经定义了,这里只是声明,而不是定义。

8.1.4 全局函数和静态函数

在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,函数定义为static就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在其他文件中声明这个函数都没用。

对于不同文件中的staitc函数名字可以相同。

注意:

  1. 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰。
  2. 同一源文件中,允许全局变量和局部变量同名,在局部变量的作用域内,全局变量不起作用。
  3. 所有的函数默认都是全局的,意味着所有的函数都不能重名,但如果是staitc函数,那么作用域是文件级的,所以不同的文件static函数名是可以相同的。

8.1.5总结

类型

作用域

生命周期

auto变量

一对{}内

当前函数

static局部变量

一对{}内

整个程序运行期

extern变量

整个程序

整个程序运行期

static全局变量

当前文件

整个程序运行期

extern函数

整个程序

整个程序运行期

static函数

当前文件

整个程序运行期

register变量

一对{}内

当前函数

全局变量

整个程序  

 整个程序运行期

8.2 内存布局 

8.2.1 内存分区

C代码经过预处理编译汇编链接4步后生成一个可执行程序。

在 Windows 下,程序是一个普通的可执行文件,以下列出一个二进制可执行文件的基本情况:

 通过上图可以得知,在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(data)和未初始化数据区(bss)3 个部分(有些人直接把data和bss合起来叫做静态区或全局区)。

  1. 代码区

存放 CPU 执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。

  1. 全局初始化数据区/静态数据区(data段)

该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。

  1. 未初始化数据区(又叫 bss 区)

存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为 0 或者空(NULL)。

程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。然后,运行可执行程序,系统把程序加载到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区

 

  1. 代码区(text segment)

加载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以在运行期间修改的。

  1. 未初始化数据区(BSS)

加载的是可执行文件BSS段,位置可以分开亦可以紧靠数据段,存储于数据段的数据(全局未初始化,静态未初始化数据)的生存周期为整个程序运行过程。

  1. 全局初始化数据区/静态数据区(data segment)

加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常量(只读))的数据的生存周期为整个程序运行过程。

  1. 栈区(stack)

栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。

  1. 堆区(heap)

堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

8.2.2 存储类型总结 

类型

作用域

生命周期

存储位置

auto变量

一对{}内

当前函数

栈区

static局部变量

一对{}内

整个程序运行期

初始化在data段,未初始化在BSS段

extern变量

整个程序

整个程序运行期

初始化在data段,未初始化在BSS段

static全局变量

当前文件

整个程序运行期

初始化在data段,未初始化在BSS段

extern函数

整个程序

整个程序运行期

代码区

static函数

当前文件

整个程序运行期

代码区

register变量

一对{}内

当前函数

运行时存储在CPU寄存器

字符串常量

当前文件

整个程序运行期

data

#include <stdio.h>

#include <stdlib.h>

int e;

static int f;

int g = 10;

static int h = 10;

int main()

{

       int a;

       int b = 10;

       static int c;

       static int d = 10;

       char *i = "test";

       char *k = NULL;

       printf("&a\t %p\t //局部未初始化变量\n", &a);

       printf("&b\t %p\t //局部初始化变量\n", &b);

       printf("&c\t %p\t //静态局部未初始化变量\n", &c);

       printf("&d\t %p\t //静态局部初始化变量\n", &d);

       printf("&e\t %p\t //全局未初始化变量\n", &e);

       printf("&f\t %p\t //全局静态未初始化变量\n", &f);

       printf("&g\t %p\t //全局初始化变量\n", &g);

       printf("&h\t %p\t //全局静态初始化变量\n", &h);

       printf("i\t %p\t //只读数据(文字常量区)\n", i);

       k = (char *)malloc(10);

       printf("k\t %p\t //动态分配的内存\n", k);

       return 0;

}

8.2.3内存操作函数 

1) memset()

#include <string.h>

void *memset(void *s, int c, size_t n);

功能:将s的内存区域的前n个字节以参数c填入

参数

       s:需要操作内存s的首地址

       c:填充的字符,c虽然参数为int,但必须是unsigned char , 范围为0~255

       n:指定需要设置的大小

返回值:s的首地址

       int a[10];

       memset(a, 0, sizeof(a));

       memset(a, 97, sizeof(a));

       int i = 0;

       for (i = 0; i < 10; i++)

       {

              printf("%c\n", a[i]);

       }

2) memcpy()

#include <string.h>

void *memcpy(void *dest, const void *src, size_t n);

功能:拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。

参数

       dest:目的内存首地址

       src:源内存首地址,注意:dest和src所指的内存空间不可重叠,可能会导致程序报错

       n:需要拷贝的字节数

返回值:dest的首地址

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

       int b[10];

      

       memcpy(b, a, sizeof(a));

       int i = 0;

       for (i = 0; i < 10; i++)

       {

              printf("%d, ", b[i]);

       }

       printf("\n");

       //memcpy(&a[3], a, 5 * sizeof(int)); //err, 内存重叠

3) memmove()

memmove()功能用法和memcpy()一样,区别在于:dest和src所指的内存空间重叠时,memmove()仍然能处理,不过执行效率比memcpy()低些。

4) memcmp()

#include <string.h>

int memcmp(const void *s1, const void *s2, size_t n);

功能:比较s1和s2所指向内存区域的前n个字节

参数

       s1:内存首地址1

       s2:内存首地址2

       n:需比较的前n个字节

返回值

       相等:=0

       大于:>0

       小于:<0

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

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

       int flag = memcmp(a, b, sizeof(a));

       printf("flag = %d\n", flag);

 8.2.4 堆区内存分配和释放

1)malloc()

#include <stdlib.h>

void *malloc(size_t size);

功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。分配的内存空间内容不确定,一般使用memset初始化。

参数

       size:需要分配内存大小(单位:字节)

返回值

成功:分配空间的起始地址

失败:NULL

#include <stdio.h>

#include <string.h>

int main()

{

       int count, *array, n;

       printf("请输入要申请数组的个数:\n");

       scanf("%d", &n);

       array = (int *)malloc(n * sizeof (int));

       if (array == NULL)

       {

              printf("申请空间失败!\n");

              return -1;

       }

       //将申请到空间清0

       memset(array, 0, sizeof(int)*n);

       for (count = 0; count < n; count++) /*给数组赋值*/

              array[count] = count;

       for (count = 0; count < n; count++) /*打印数组元素*/

              printf("%2d", array[count]);

       free(array);

       return 0;

}

2)free()

#include <stdlib.h>

void free(void *ptr);

功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址。对同一内存空间多次释放会出错。

参数

ptr:需要释放空间的首地址,被释放区应是由malloc函数所分配的区域。

返回值:无

 8.3 内存分区代码分析

返回栈区地址

#include <stdio.h>

int *fun()

{

       int a = 10;

       return &a;//函数调用完毕,a释放

}

int main(int argc, char *argv[])

{

       int *p = NULL;

       p = fun();

       *p = 100; //操作野指针指向的内存,err

       return 0;

}

2) 返回data区地址

#include <stdio.h>

int *fun()

{

       static int a = 10;

       return &a; //函数调用完毕,a不释放

}

int main(int argc, char *argv[])

{

       int *p = NULL;

       p = fun();

       *p = 100; //ok

       printf("*p = %d\n", *p);

       return 0;

}

3) 值传递1

#include <stdio.h>

#include <stdlib.h>

void fun(int *tmp)

{

       tmp = (int *)malloc(sizeof(int));

       *tmp = 100;

}

int main(int argc, char *argv[])

{

       int *p = NULL;

       fun(p); //值传递,形参修改不会影响实参

       printf("*p = %d\n", *p);//err,操作空指针指向的内存

       return 0;

}

4) 值传递2

#include <stdio.h>

#include <stdlib.h>

void fun(int *tmp)

{

       *tmp = 100;

}

int main(int argc, char *argv[])

{

       int *p = NULL;

       p = (int *)malloc(sizeof(int));

       fun(p); //值传递

       printf("*p = %d\n", *p); //ok,*p为100

       return 0;

}

5) 返回堆区地址

#include <stdio.h>

#include <stdlib.h>

int *fun()

{

       int *tmp = NULL;

       tmp = (int *)malloc(sizeof(int));

       *tmp = 100;

       return tmp;//返回堆区地址,函数调用完毕,不释放

}

int main(int argc, char *argv[])

{

       int *p = NULL;

       p = fun();

       printf("*p = %d\n", *p);//ok

       //堆区空间,使用完毕,手动释放

       if (p != NULL)

       {

              free(p);

              p = NULL;

       }

       return 0;

}

 9. 复合类型(自定义类型)

9.1 结构体 

数组:描述一组具有相同类型数据的有序集合,用于处理大量相同类型的数据运算。

有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理。

C语言中给出了另一种构造数据类型——结构体。

 9.1.2 结构体变量的定义和初始化

定义结构体变量的方式:

  1. 先声明结构体类型再定义变量名
  2. 在声明类型的同时定义变量
  3. 直接定义结构体类型变量(无类型名)

 

结构体类型和结构体变量关系:

  1. 结构体类型:指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元。
  2. 结构体变量:系统根据结构体类型(内部成员状况)为之分配空间。

//结构体类型的定义

struct stu

{

       char name[50];

       int age;

};

//先定义类型,再定义变量(常用)

struct stu s1 = { "mike", 18 };

//定义类型同时定义变量

struct stu2

{

       char name[50];

       int age;

}s2 = { "lily", 22 };

struct

{

       char name[50];

       int age;

}s3 = { "yuri", 25 };

9.1.3 结构体成员的使用 

#include<stdio.h>

#include<string.h>

//结构体类型的定义

struct stu

{

       char name[50];

       int age;

};

int main()

{

       struct stu s1;

       //如果是普通变量,通过点运算符操作结构体成员

       strcpy(s1.name, "abc");

       s1.age = 18;

       printf("s1.name = %s, s1.age = %d\n", s1.name, s1.age);

       //如果是指针变量,通过->操作结构体成员

       strcpy((&s1)->name, "test");

       (&s1)->age = 22;

       printf("(&s1)->name = %s, (&s1)->age = %d\n", (&s1)->name, (&s1)->age);

       return 0;

}

9.1.4 结构体数组 

#include <stdio.h>

//统计学生成绩

struct stu

{

       int num;

       char name[20];

       char sex;

       float score;

};

int main()

{

       //定义一个含有5个元素的结构体数组并将其初始化

       struct stu boy[5] = {

              { 101, "Li ping", 'M', 45 },                   

              { 102, "Zhang ping", 'M', 62.5 },

              { 103, "He fang", 'F', 92.5 },

              { 104, "Cheng ling", 'F', 87 },

              { 105, "Wang ming", 'M', 58 }};

       int i = 0;

       int c = 0;

       float ave, s = 0;

       for (i = 0; i < 5; i++)

       {

              s += boy[i].score;      //计算总分

              if (boy[i].score < 60)

              {

                     c += 1;          //统计不及格人的分数

              }

       }

       printf("s=%f\n", s);//打印总分数

       ave = s / 5;                               //计算平均分数

       printf("average=%f\ncount=%d\n\n", ave, c); //打印平均分与不及格人数

       for (i = 0; i < 5; i++)

       {

              printf(" name=%s,  score=%f\n", boy[i].name, boy[i].score);

           // printf(" name=%s,  score=%f\n", (boy+i)->name, (boy+i)->score);

       }

       return 0;

}

 9.1.5 结构体套结构体

#include <stdio.h>

struct person

{

       char name[20];

       char sex;

};

struct stu

{

       int id;

       struct person info;

};

int main()

{

       struct stu s[2] = { 1, "lily", 'F', 2, "yuri", 'M' };

       int i = 0;

       for (i = 0; i < 2; i++)

       {

              printf("id = %d\tinfo.name=%s\tinfo.sex=%c\n", s[i].id, s[i].info.name, s[i].info.sex);

       }

       return 0;

}

 9.1.6 结构体赋值

#include<stdio.h>

#include<string.h>

//结构体类型的定义

struct stu

{

       char name[50];

       int age;

};

int main()

{

       struct stu s1;

       //如果是普通变量,通过点运算符操作结构体成员

       strcpy(s1.name, "abc");

       s1.age = 18;

       printf("s1.name = %s, s1.age = %d\n", s1.name, s1.age);

       //相同类型的两个结构体变量,可以相互赋值

       //把s1成员变量的值拷贝给s2成员变量的内存

       //s1和s2只是成员变量的值一样而已,它们还是没有关系的两个变量

       struct stu s2 = s1;

//memcpy(&s2, &s1, sizeof(s1));

       printf("s2.name = %s, s2.age = %d\n", s2.name, s2.age);

       return 0;

}

 9.1.7 结构体和指针

指向普通结构体变量的指针

#include<stdio.h>

//结构体类型的定义

struct stu

{

       char name[50];

       int age;

};

int main()

{

       struct stu s1 = { "lily", 18 };

       //如果是指针变量,通过->操作结构体成员

       struct stu *p = &s1;

       printf("p->name = %s, p->age=%d\n", p->name, p->age);

       printf("(*p).name = %s, (*p).age=%d\n"(*p).name(*p).age);

       return 0;

}

2)堆区结构体变量

#include<stdio.h>

#include <string.h>

#include <stdlib.h>

//结构体类型的定义

struct stu

{

       char name[50];

       int age;

};

int main()

{

       struct stu *p = NULL;

       p = (struct stu *)malloc(sizeof(struct  stu));

       //如果是指针变量,通过->操作结构体成员

       strcpy(p->name, "test");

       p->age = 22;

       printf("p->name = %s, p->age=%d\n", p->name, p->age);

       printf("(*p).name = %s, (*p).age=%d\n", (*p).name(*p).age);

       free(p);

       p = NULL;

       return 0;

}

3)结构体套一级指针

#include<stdio.h>

#include <string.h>

#include <stdlib.h>

//结构体类型的定义

struct stu

{

       char *name; //一级指针

       int age;

};

int main()

{

       struct stu *p = NULL;

       p = (struct stu *)malloc(sizeof(struct  stu));

       p->name = malloc(strlen("test") + 1);

       strcpy(p->name, "test");

       p->age = 22;

       printf("p->name = %s, p->age=%d\n", p->name, p->age);

       printf("(*p).name = %s, (*p).age=%d\n", (*p).name, (*p).age);

       if (p->name != NULL)

       {

              free(p->name);

              p->name = NULL;

       }

       if (p != NULL)

       {

              free(p);

              p = NULL;

       }

       return 0;

}

 9.1.8 结构体做函数参数

1)结构体普通变量做函数参数

#include<stdio.h>

#include <string.h>

//结构体类型的定义

struct stu

{

       char name[50];

       int age;

};

//函数参数为结构体普通变量

void set_stu(struct stu tmp)

{

       strcpy(tmp.name, "mike");

       tmp.age = 18;

       printf("tmp.name = %s, tmp.age = %d\n", tmp.name, tmp.age);

}

int main()

{

       struct stu s = { 0 };

       set_stu(s); //值传递

       printf("s.name = %s, s.age = %d\n", s.name, s.age);

       return 0;

}

2)结构体指针变量做函数参数

#include<stdio.h>

#include <string.h>

//结构体类型的定义

struct stu

{

       char name[50];

       int age;

};

//函数参数为结构体指针变量

void set_stu_pro(struct stu *tmp)

{

       strcpy(tmp->name, "mike");

       tmp->age = 18;

}

int main()

{

       struct stu s = { 0 };

       set_stu_pro(&s); //地址传递

       printf("s.name = %s, s.age = %d\n", s.name, s.age);

       return 0;

}

3)结构体数组名做函数参数

#include<stdio.h>

//结构体类型的定义

struct stu

{

       char name[50];

       int age;

};

//void set_stu_pro(struct stu tmp[100], int n)

//void set_stu_pro(struct stu tmp[], int n)

void set_stu_pro(struct stu *tmp, int n)

{

       int i = 0;

       for (i = 0; i < n; i++)

       {

              sprintf(tmp->name, "name%d%d%d", i, i, i);

              tmp->age = 20 + i;

              tmp++;

       }

}

int main()

{

       struct stu s[3] = { 0 };

       int i = 0;

       int n = sizeof(s) / sizeof(s[0]);

       set_stu_pro(s, n); //数组名传递

       for (i = 0; i < n; i++)

       {

              printf("%s, %d\n", s[i].name, s[i].age);

       }

       return 0;

}

4)const修饰结构体指针形参变量

//结构体类型的定义

struct stu

{

       char name[50];

       int age;

};

void fun1(struct stu * const p)

{

       //p = NULL; //err

       p->age = 10; //ok

}

//void fun2(struct stu const*  p)

void fun2(const struct stup)

{

       p = NULL; //ok

       //p->age = 10; //err

}

void fun3(const struct stu * const p)

{

       //p = NULL; //err

       //p->age = 10; //err

}

9.2 共用体(联合体) 

  1. 联合union是一个能在同一个存储空间存储不同类型数据的类型;
  2. 联合体所占的内存长度等于其最长成员的长度倍数,也有叫做共用体;
  3. 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用;
  4. 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖;
  5. 共用体变量的地址和它的各成员的地址都是同一地址。

#include <stdio.h>

//共用体也叫联合体

union Test

{

       unsigned char a;

       unsigned int b;

       unsigned short c;

};

int main()

{

       //定义共用体变量

       union Test tmp;

       //1、所有成员的首地址是一样的

       printf("%p, %p, %p\n", &(tmp.a), &(tmp.b), &(tmp.c));

       //2、共用体大小为最大成员类型的大小

       printf("%lu\n", sizeof(union Test));

       //3、一个成员赋值,会影响另外的成员

       //左边是高位,右边是低位

       //低位放低地址,高位放高地址

       tmp.b = 0x44332211;

       printf("%x\n", tmp.a); //11

       printf("%x\n", tmp.c); //2211

       tmp.a = 0x00;

       printf("short: %x\n", tmp.c); //2200

       printf("int: %x\n", tmp.b); //44332200

       return 0;

}

9.3 枚举 

枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。

枚举类型定义:

enum  枚举名

{

       枚举值表

};

  1. 在枚举值表中应列出所有可用值,也称为枚举元素。
  2. 枚举值是常量,不能在程序中用赋值语句再对它赋值。
  3. 举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2 …

#include <stdio.h>

enum weekday

{

       sun = 2, mon, tue, wed, thu, fri, sat

} ;

enum bool

{

       flase, true

};

int main()

{

       enum weekday a, b, c;

       a = sun;

       b = mon;

       c = tue;

       printf("%d,%d,%d\n", a, b, c);

       enum bool flag;

       flag = true;

       if (flag == 1)

       {

              printf("flag为真\n");

       }

       return 0;

}

9.4 typedef 

typedef为C语言的关键字,作用是为一种数据类型(基本类型或自定义数据类型)定义一个新名字,不能创建新类型

  1. 与#define不同,typedef仅限于数据类型,而不是能是表达式或具体的值
  2. #define发生在预处理,typedef发生在编译阶段

#include <stdio.h>

typedef int INT;

typedef char BYTE;

typedef BYTE T_BYTE;

typedef unsigned char UBYTE;

typedef struct type

{

       UBYTE a;

       INT b;

       T_BYTE c;

}TYPE, *PTYPE;

int main()

{

       TYPE t;

       t.a = 254;

       t.b = 10;

       t.c = 'c';

       PTYPE p = &t;

       printf("%u, %d, %c\n", p->a, p->b, p->c);

       return 0;

}

 10. 文件操作

​​​​​​​10.1 概述

10.1.1磁盘文件和设备文件

  1. 磁盘文件

指一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,使用时才调入内存。

  1. 设备文件

在操作系统中把每一个与主机相连的输入、输出设备看作是一个文件,把它们的输入、输出等同于对磁盘文件的读和写。

10.1.2 磁盘文件的分类 

 计算机的存储在物理上是二进制的,所以物理上所有的磁盘文件本质上都是一样的:以字节为单位进行顺序存储。

 

从用户或者操作系统使用的角度(逻辑上)把文件分为:

  1. 文本文件:基于字符编码的文件 
  2. 二进制文件:基于值编码的文件

 10.1.3 文本文件和二进制文件

1)文本文件

  1. 基于字符编码,常见编码有ASCII、UNICODE等
  2. 一般可以使用文本编辑器直接打开
  3. 数5678的以ASCII存储形式(ASCII码)为:

00110101 00110110 00110111 00111000

2)二进制文件

  1. 基于值编码,自己根据具体应用,指定某个值是什么意思
  2. 把内存中的数据按其在内存中的存储形式原样输出到磁盘上
  3. 数5678的存储形式(二进制码)为:
  4. 00010110 00101110

10.2 文件的打开和关闭 

10.2.1 文件指针

在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。

typedef struct

{

       short           level; //缓冲区"满"或者"空"的程度

       unsigned        flags;      //文件状态标志

       char            fd;           //文件描述符

       unsigned char   hold;      //如无缓冲区不读取字符

       short           bsize; //缓冲区的大小

       unsigned char   *buffer;//数据缓冲区的位置

       unsigned        ar;   //指针,当前的指向

       unsigned        istemp;   //临时文件,指示器

       short           token;      //用于有效性的检查

}FILE;

FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息

声明FILE结构体类型的信息包含在头文件“stdio.h”中,一般设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。通过文件指针就可对它所指的文件进行各种操作。 

C语言中有三个特殊的文件指针由系统默认打开,用户无需定义即可直接使用:

  1. stdin: 标准输入,默认为当前终端(键盘),我们使用的scanf、getchar函数默认从此终端获得数据。
  2. stdout:标准输出,默认为当前终端(屏幕),我们使用的printf、puts函数默认输出信息到此终端。
  3. stderr:标准出错,默认为当前终端(屏幕),我们使用的perror函数默认输出信息到此终端。

 10.2.2 文件的打开

任何文件使用之前必须打开:

#include <stdio.h>

FILE * fopen(const char * filename, const char * mode);

功能:打开文件

参数:

       filename:需要打开的文件名,根据需要加上路径

       mode:打开文件的模式设置

返回值:

       成功:文件指针

       失败:NULL

第一个参数的几种形式:

       FILE *fp_passwd = NULL;

       //相对路径:

       //打开当前目录passdw文件:源文件(源程序)所在目录

       FILE *fp_passwd = fopen("passwd.txt", "r");

      

       //打开当前目录(test)下passwd.txt文件

       fp_passwd = fopen(". / test / passwd.txt", "r");

      

       //打开当前目录上一级目录(相对当前目录)passwd.txt文件

       fp_passwd = fopen(".. / passwd.txt", "r");

             

       //绝对路径:

       //打开C盘test目录下一个叫passwd.txt文件

       fp_passwd = fopen("c:/test/passwd.txt","r");

第二个参数的几种形式(打开文件的方式): 

打开模式

含义

r或rb

以只读方式打开一个文本文件(不创建文件,若文件不存在则报错)

w或wb

以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)

a或ab

以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件

r+或rb+

以可读、可写的方式打开文件(不创建新文件)

w+或wb+

以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)

a+或ab+

以添加方式打开可读、可写的文件。若文件不存在则创建文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留。

注意:

  1. b是二进制模式的意思,b只是在Windows有效,在Linux用r和rb的结果是一样的
  2. Unix和Linux下所有的文本文件行都是\n结尾,而Windows所有的文本文件行都是\r\n结尾
  3. 在Windows平台下,以“文本”方式打开文件,不加b:
    1. 当读取文件的时候,系统会将所有的 "\r\n" 转换成 "\n"
    2. 当写入文件的时候,系统会将 "\n" 转换成 "\r\n" 写入
    3. 以"二进制"方式打开文件,则读写都不会进行这样的转换
  1. 在Unix/Linux平台下,“文本”与“二进制”模式没有区别,"\r\n" 作为两个字符原样输入输出

int main(void)

{

       FILE *fp = NULL;

       // "\\"这样的路径形式,只能在windows使用

       // "/"这样的路径形式,windows和linux平台下都可用,建议使用这种

       // 路径可以是相对路径,也可是绝对路径

       fp = fopen("../test", "w");

       //fp = fopen("..\\test", "w");

       if (fp == NULL) //返回空,说明打开失败

       {

              //perror()是标准出错打印函数,能打印调用库函数出错原因

              perror("open");

              return -1;

       }

       return 0;

}

10.2.3 文件的关闭 

任何文件在使用后应该关闭:

  1. 打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存
  2. 一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用fopen打开文件会失败
  3. 如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭。

#include <stdio.h>

int fclose(FILE * stream);

功能:关闭先前fopen()打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。

参数:

       stream:文件指针

返回值:

       成功:0

       失败:-1

       FILE * fp = NULL;

       fp = fopen("abc.txt", "r");

       fclose(fp);

10.3 文件的顺序读写

 10.3.1 按照字符读写文件fgetc、fputc

1)写文件

#include <stdio.h>

int fputc(int ch, FILE * stream);

功能:将ch转换为unsigned char后写入stream指定的文件中

参数:

       ch:需要写入文件的字符

       stream:文件指针

返回值:

       成功:成功写入文件的字符

       失败:返回-1

char buf[] = "this is a test for fputc";

int i = 0;

int n = strlen(buf);

for (i = 0; i < n; i++)

{

       //往文件fp写入字符buf[i]

       int ch = fputc(buf[i], fp);

       printf("ch = %c\n", ch);

}

2)文件结尾

在C语言中,EOF表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。

#define EOF    (-1)

当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,ANSI C提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件

#include <stdio.h>

int feof(FILE * stream);

功能:检测是否读取到了文件结尾判断的是最后一次“读操作的内容”,不是当前位置内容(上一个内容)

参数:

       stream:文件指针

返回值:

       非0值:已经到文件结尾

       0:没有到文件结尾

3)读文件

#include <stdio.h>

int fgetc(FILE * stream);

功能:从stream指定的文件中读取一个字符

参数:

       stream:文件指针

返回值:

       成功:返回读取到的字符

       失败:-1

char ch;

#if 0

while ((ch = fgetc(fp)) != EOF)

{

       printf("%c", ch);

}

printf("\n");

#endif

while (!feof(fp)) //文件没有结束,则执行循环

{

       ch = fgetc(fp);

       printf("%c", ch);

}

printf("\n");

10.3.2按照行读写文件fgets、fputs 

1)写文件

#include <stdio.h>

int fputs(const char * str, FILE * stream);

功能:将str所指定的字符串写入到stream指定的文件中,字符串结束符 '\0'  不写入文件。

参数:

       str:字符串

       stream:文件指针

返回值:

       成功:0

       失败:-1

char *buf[] = { "123456\n", "bbbbbbbbbb\n", "ccccccccccc\n" };

int i = 0;

int n = 3;

for (i = 0; i < n; i++)

{

       int len = fputs(buf[i], fp);

       printf("len = %d\n", len);

}

2)读文件

#include <stdio.h>

char * fgets(char * str, int size, FILE * stream);

功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。

参数:

       str:字符串

       size:指定最大读取字符串的长度(size - 1)

       stream:文件指针

返回值:

       成功:成功读取的字符串

       读到文件尾或出错: NULL

char buf[100] = 0;

while (!feof(fp)) //文件没有结束

{

       memset(buf, 0, sizeof(buf));

       char *p = fgets(buf, sizeof(buf), fp);

       if (p != NULL)

       {

              printf("buf = %s", buf);

       }

}

3)强化训练:文件版四则运算

有个文件大小不确定,每行内容都是一个四则运算表达式,还没有算出结果,写一个程序,自动算出其结果后修改文件。

 10.3.3按照格式化文件fprintf、fscanf

1)写文件

#include <stdio.h>

int fprintf(FILE * stream, const char * format, ...);

功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 '\0'  为止。

参数:

       stream:已经打开的文件

       format:字符串格式,用法和printf()一样

返回值:

       成功:实际写入文件的字符个数

       失败:-1

fprintf(fp, "%d %d %d\n", 1, 2, 3);

  1. 读文件

#include <stdio.h>

int fscanf(FILE * stream, const char * format, ...);

功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。

参数:

       stream:已经打开的文件

       format:字符串格式,用法和scanf()一样

返回值:

       成功:参数数目,成功转换的值的个数

       失败: - 1

int a = 0;

int b = 0;

int c = 0;

fscanf(fp, "%d %d %d\n", &a, &b, &c);

printf("a = %d, b = %d, c = %d\n", a, b, c);

  1. 强化训练:文件版排序

10.3.4按照块读写文件fread、fwrite 

1)写文件

#include <stdio.h>

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

功能:以数据块的方式给文件写入内容

参数:

       ptr:准备写入文件数据的地址

       size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小

       nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb

       stream:已经打开的文件指针

返回值:

       成功:实际成功写入文件数据的块数,此值和 nmemb 相等

       失败:0

typedef struct Stu

{

       char name[50];

       int id;

}Stu;

Stu s[3];

int i = 0;

for (i = 0; i < 3; i++)

{

       sprintf(s[i].name, "stu%d%d%d", i, i, i);

       s[i].id = i + 1;

}

int ret = fwrite(s, sizeof(Stu), 3, fp);

printf("ret = %d\n", ret);

2)读文件

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

功能:以数据块的方式从文件中读取内容

参数:

       ptr:存放读取出来数据的内存空间

       size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小

       nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb

       stream:已经打开的文件指针

返回值:

成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。

       失败:0

       0: 表示读到文件结尾。(feof())

typedef struct Stu

{

       char name[50];

       int id;

}Stu;

Stu s[3];

int ret = fread(s, sizeof(Stu), 3, fp);

printf("ret = %d\n", ret);

int i = 0;

for (i = 0; i < 3; i++)

{

       printf("s = %s, %d\n", s[i].name, s[i].id);

}

 t3)强化训练:大文件拷贝

 

10.4 文件的随机读写

#include <stdio.h>

int fseek(FILE *stream, long offset, int whence);

功能:移动文件流(文件光标)的读写位置。

参数:

       stream:已经打开的文件指针

offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。

       whence:其取值如下:

              SEEK_SET:从文件开头移动offset个字节

              SEEK_CUR:从当前位置移动offset个字节

              SEEK_END:从文件末尾移动offset个字节

返回值:

       成功:0

       失败:-1

#include <stdio.h>

long ftell(FILE *stream);

功能:获取文件流(文件光标)的读写位置。

参数:

       stream:已经打开的文件指针

返回值:

       成功:当前文件流(文件光标)的读写位置

       失败:-1

#include <stdio.h>

void rewind(FILE *stream);

功能:把文件流(文件光标)的读写位置移动到文件开头。

参数:

       stream:已经打开的文件指针

返回值:

       无返回值

typedef struct Stu

{

       char name[50];

       int id;

}Stu;

//假如已经往文件写入3个结构体

//fwrite(s, sizeof(Stu), 3, fp);

Stu s[3];

Stu tmp;

int ret = 0;

//文件光标读写位置从开头往右移动2个结构体的位置

fseek(fp, 2 * sizeof(Stu), SEEK_SET);

//读第3个结构体

ret = fread(&tmp, sizeof(Stu), 1, fp);

if (ret == 1)

{

       printf("[tmp]%s, %d\n", tmp.name, tmp.id);

}

//把文件光标移动到文件开头

//fseek(fp, 0, SEEK_SET);

rewind(fp);

ret = fread(s, sizeof(Stu), 3, fp);

printf("ret = %d\n", ret);

int i = 0;

for (i = 0; i < 3; i++)

{

       printf("s === %s, %d\n", s[i].name, s[i].id);

}

 10.5 Windows和Linux文本文件区别

  1. b是二进制模式的意思,b只是在Windows有效,在Linux用r和rb的结果是一样的
  2. Unix和Linux下所有的文本文件行都是\n结尾,而Windows所有的文本文件行都是\r\n结尾
  3. 在Windows平台下,以“文本”方式打开文件,不加b:
    1. 当读取文件的时候,系统会将所有的 "\r\n" 转换成 "\n"
    2. 当写入文件的时候,系统会将 "\n" 转换成 "\r\n" 写入
    3. 以"二进制"方式打开文件,则读\写都不会进行这样的转换
  1. 在Unix/Linux平台下,“文本”与“二进制”模式没有区别,"\r\n" 作为两个字符原样输入输出

判断文本文件是Linux格式还是Windows格式:



#include<stdio.h>

int main(int argc, char **args)

{

       if (argc < 2)

              return 0;

       FILE *p = fopen(args[1], "rb");

       if (!p)

              return 0;

       char a[1024] = { 0 };

       fgets(a, sizeof(a), p);

       int len = 0;

       while (a[len])

       {

              if (a[len] == '\n')

              {

                     if (a[len - 1] == '\r')

                     {

                            printf("windows file\n");

                     }

                     else

                     {

                            printf("linux file\n");

                     }

              }

              len++;

       }

       fclose(p);

       return 0;

}

10.6 获取文件状态

#include <sys/types.h>

#include <sys/stat.h>

int stat(const char *path, struct stat *buf);

功能:获取文件状态信息

参数

path:文件名

buf:保存文件信息的结构体

返回值

成功:0

失败-1

struct stat {

       dev_t         st_dev;         //文件的设备编号

       ino_t         st_ino;          //节点

       mode_t        st_mode;   //文件的类型和存取的权限

       nlink_t       st_nlink;     //连到该文件的硬连接数目,刚建立的文件值为1

       uid_t         st_uid;         //用户ID

       gid_t         st_gid;         //组ID

       dev_t         st_rdev;      //(设备类型)若此文件为设备文件,则为其设备编号

       off_t         st_size;        //文件字节数(文件大小)

       unsigned long st_blksize;   //块大小(文件系统的I/O 缓冲区大小)

       unsigned long st_blocks;    //块数

       time_t        st_atime;     //最后一次访问时间

       time_t        st_mtime;    //最后一次修改时间

       time_t        st_ctime;     //最后一次改变时间(指属性)

};

#include <sys/types.h>

#include <sys/stat.h>

#include <stdio.h>

int main(int argc, char **args)

{

       if (argc < 2)

              return 0;

       struct stat st = { 0 };

       stat(args[1], &st);

       int size = st.st_size;//得到结构体中的成员变量

       printf("%d\n", size);

       return 0;

}

10.7 删除文件、重命名文件名

#include <stdio.h>

int remove(const char *pathname);

功能:删除文件

参数

       pathname:文件名

返回值

       成功:0

       失败:-1

#include <stdio.h>

int rename(const char *oldpath, const char *newpath);

功能:把oldpath的文件名改为newpath

参数

oldpath:旧文件名

newpath:新文件名

返回值

成功:0

失败: - 1

10.8 文件缓冲区

10.8.1文件缓冲区

ANSI C标准采用“缓冲文件系统”处理数据文件

所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。​​​​​​​

如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量) 。

10.8.2磁盘文件的存取

 

  1. 磁盘文件,一般保存在硬盘、U盘等掉电不丢失的磁盘设备中,在需要时调入内存
  2. 在内存中对文件进行编辑处理后,保存到磁盘中
  3. 程序与磁盘之间交互,不是立即完成,系统或程序可根据需要设置缓冲区,以提高存取效率

10.8.3更新缓冲区 

#include <stdio.h>

int fflush(FILE *stream);

功能:更新缓冲区,让缓冲区的数据立马写到文件中

参数

stream:文件指针

返回值

成功:0

失败:-1

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值