popring's Blog

何时才能财富自由

第十章 文件操作

文件

文本文件

也称ASCII码文件,每字符占一个字节存储,每字节保存的是字符的ASCII码。可用文本编辑器打开查看,例如.txt、代码源程序.c、等等

二进制文件

以二进制编码的形式保存,例如可执行文件(.exe)、位图文件(.bmp)、word文件(.docx)等都属于二进制文件。这种文件不能用文本编辑器打开查看,强行打开会乱码,无法读懂。

优点

“全部看作二进制”,输入输出的开始和结束由程序控制而不受换行、空格等字符的限制;比文本文件一般体积小,节省存储空间。

文件的读写

文件操作步骤

1
2
3
4
5
6
7
8
9
10
11
// 定义文件指针
FILE *fp;

// 打开文件(使文件指针关联文件)
fp = fopen(文件名,打开方式);

// 读写文件
// 通过调用系统库函数读写文件,函数中都需要一个文件指针参数fp

// 关闭文件(断开文件指针和文件的关联)
fclose(fp);

向文件写入字符串

如果文件事先已经存在,则会被删除重;如不存在,则直接新建。

fprintf()为写入文件

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main()
{
FILE *fp;
fp = fopen("file.txt","w");
fprintf(fp, "abc");
fclose(fp);
}

fopen(char *filename, char *mode)

filename:要打开的文件名称

mode:文件访问模式

modeintroduceremark
r允许读文件(read文件必须存在,否则出错
w允许覆盖写文件(write)文件必须被新建(如文件已存在会删除源文件,然后新建)
a允许追加写文件(append文件不存在时才新建,否则只在源文件末尾添加数据
+即允许读也允许写文件
b以二进制格式打开文件(binary
t以文本格式打开文件(text

C语言常用文本文件读写函数

函数功能用法
fgetc或getc从当前位置指针处读取文件中的一个字符(1个字符占1个字节),读取后,读写位置指针自动后移1字节字符变量=fgetc(fp);
fputc或putc在当前位置指针处向文件中写入一个字符(1个字符占1个字节),写入后,读写位置指针自动后移1字节fputc(字符, fp);
fgets读取文件中的一个字符串,读写位置指针自动后移fgets(字符数组名, n, fp);
fputs在当前位置指针处向文件中写入一个字符串(不写入’\0’字符,最后也不自动加’\n’);写入后,位置指针自动后移该字符串长度的字节。fputs(字符串首地址, fp);
fscanf从当前位置指针处按格式读取文件中的多个数据,类似于scanf,只不过不是从键盘输入,而是从文件中读取;读取后,位置指针自动后移fscanf(fp, “格式控制字符串”, 变量1的地址, 变量2的地址, …);
fprintf在当前位置指针处按格式向文件中写入多个数据,类似于printf,只不过不是显示到屏幕上,而是写入到文件中;写入后,位置指针自动后移fprintf(fp, “格式控制字符串”, 数据1, 数据2, …);

C语言常用二进制文件读写函数

函数功能用法
fread从当前位置指针处读取文件中的一批字节,这批字节由count个数据块、每数据块长size个字节组成,共size*count个字节。所读取的字节存入参数buffer地址开始的一段内存空间。读取后,文件位置指针跟随后移实际读取的字节数。函数返回实际读取的数据块数(如读到文件尾或出错,实际读取的数据块数可能小于count)fread(buffer, size, count, fp);
fwrite在当前位置指针处向文件中写入一批字节,这批字节位于内存中参数buffer地址开始的一段内存空间,由count个数据块、每数据块长size个字节组成,共size*count个字节。写入后,文件位置指针跟随后移实际写入的字节数。函数返回实际写入的数据块数(如写入出错,实际写入的数据块数可能小于count)fwrite(buffer, size, count, fp);

C语言常用文件位置指针定位库函数

函数功能用法
rewind把文件位置指针移到文件开头rewind(fp);
fseek把文件位置指针从ori开始的位置,向文件尾部(n>0时)或文件首部(n<0时)移动n个字节。ori可有3种取值: 0、1、2分别表示从文件首、当前位置和文件尾开始移动, 0、1、2也可分别写为符号常量SEEK_SET、SEEK_CUR、SEEK_ENDfseek(fp, n, ori); 一般n为long型,常量加字母后缀L(l)
ftell若执行成功,函数返回当前文件位置指针的位置(文件中第一个字节的位置为0);若执行失败,函数返回-1n = ftell(fp);
feof判断读文件是否已越过了文件末尾if (feof(fp) ) …

第九章 结构体

自定义类型 – 结构体

结构体类型,不占内存,不能保存数据;

内存空间,可以保存数据

1
2
3
4
5
6
7
8
9
struct stu
{
int num;
char name[10];
char sex;
float score;
};
struct stu boy1;

sizeof对类型和变量均可使用

1
2
sizeof(boy1);
sizeof(struct stu);

结构体类型变量的定义方式

先定义类型,再定义变量

1
2
3
4
5
6
7
8
struct stu
{
int num;
char name[10];
char sex;
float score;
};
struct stu boy1,boy2;

定义类型的同时,定义变量

1
2
3
4
5
6
7
8
9
struct stu
{
int num;
char name[10];
char sex;
float score;
}boy1, boy2;

struct stu boy3;

定义类型的同时定义变量,但省略类型名

因无类型名,无法再重新定义其他变量

1
2
3
4
5
6
7
struct
{
int num;
char name[10];
char sex;
float score;
}boy1, boy2;

结构体类型变量的初始化

1
2
3
4
5
6
7
8
9
10
11
12
struct stu
{
int num;
char name[10];
char sex;
float score;
};

struct stu boy1,boy2 = {1001, "zhao", 'M', 85.5};

// 可以整体赋值,直接将boy2的值直接赋给boy1
boy1 = boy2;

结构体类型的数组

1
2
3
4
5
struct stu ss[3];

ss[0].num = 1001;
strcpy(ss[1].name, "Qian");
ss[2].score=92.0;

结构指针变量

结构体变量用 .

结构体指针变量用 ->

(*结构体指针变量)用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct stu boy1;
struct stu *p;
p=&boy1;

p -> num=100;
// 相当于
boy1.num=1001;

strcpy(p->name, "Zhang");
// 相当于
strcpy(boy1.name, "Zhang");

// 此处()不可省略,点和->的优先级都最该(与括号相当)
(*p).score=92.0;
// 相当于
boy1.score=92.0;

结构体类型数据做函数参数

用法与传普通变量类似,

结构体类型变量做函数参数,不会改变原来的实参的值。

结构体指针做函数参数,可以修改原来实参的值。

结构体类型的嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct date
{
int year;
int month;
int day;
};
struct stu
{
int num;
char name[10];
char sex;
struct date birthday;
float score;
};

第二种

1
2
3
4
5
6
7
8
9
10
11
12
13
struct stu
{
int num;
char name[10];
char sex;
struct
{
int year;
int month;
int day;
} birthday;
float score;
};

类型定义语句typedef

为类型起“绰号”,并不产生新的数据类型,只是给已有的类型增加新名

1
2
3
4
5
typedef double real;

real a,b;
// 相当于
double a,b;

C语言常用内存管理库函数(包含stdlib.h

函数名介绍备注
malloc分配1块长度为size字节的连续内存空间(不清零),函数返回该空间的首地址;如分配失败函数返回0(类型说明符*)malloc(size)
calloc分配n块、每块长度为size字节的连续内存空间(共size×n字节),并将该空间中的内容全部清零,函数返回该空间的首地址;如分配失败函数返回0(类型说明符*)calloc(n,size)
free释放ptr所指向的一块内存空间,ptr是由malloc或calloc函数所分配空间的地址,即是这两个函数的返回值(或类型转换后的返回值)free(ptr)(ptr为任意基类型的指针)

链表

以链接方式存储的线性表,链表各元素的逻辑结构与存储结构一般不一致。

链表类型:单向链表,双向链表,循环链表。。。

链表介绍

单向链表

一个单向链表的节点分为两个部分,第一部分保存或者显示节点的相关信息,第二部分存储下一个节点的地址。单向链表只向一个方向遍历。

单向链表

链表的一种,特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始;列表有节点构成,head指针指向第一个称为表头节点,而终止于最后一个指向NULL的指针。

双向链表

比单向链表多一个的是,双向链表每个节点有两个链接,一个指向前一个节点,而另一个指向下一个节点,当为最后一个节点时,指向空值或空列表。

双向链表

循环链表

在一个循环链表中,首节点和末节点被连接在一起。这种方式,单向和双向都可实现。

循环链表

链表的创建与输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 结构体
struct nodelist
{
// 数据
int score;
// 指向下一个链表的指针
struct nodelist *next;
};

// 自定义结构体类型
typedef struct nodelist SNODE;

// 创建链表
SNODE *create_list(int data)
{
SNODE *head=NULL, *node=NULL, *end=NULL;
int i;

head = (SNODE *)malloc(sizeof(SNODE));
end = head;

for(i=0; i<data; i++)
{
node = (SNODE *)malloc(sizeof(SNODE));
node->score = i;
end->next = node;
end = node;
}
end->next = NULL;

return head;
}

// 输出链表
void out_list(SNODE *h)
{
SNODE *p;
p = h->next;
while(p)
{
printf("%d\n", p->score);
p = p->next;
}
}




int main()
{
int data = 3;
SNODE *head;
head = create_list(data);
out_list(head);

system("pause");
}

链表节点的插入和删除

节点的插入
1
2
3
4
p = new struct node;
p->data = 95.5;
p->next = q->next;
q->next = p;
节点的删除
1
2
q->next = p->next;
delete p;

第八章 指针(下)

指针变量做函数参数

如果要改变a, b的值,要使用指针指向a,b

输入两个数,如果第一个数大于第二个数,则交换两个数,否则直接输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
int a,b;
printf("请输入两个数字\n");
scanf("%d%d", &a, &b);
if(a<b) swap(&a, &b);
printf("%d, %d\n", a, b);
return 0;
}

void swap(int *p, int *q)
{
int temp;
temp = *p;
*p = *q;
*q = temp;
}

数组函数参数

1
2
3
4
5
6
7
8
int a[] == int *a;
int a[N] == int *a;

int a[N][4] = int (*a)[4];
int a[][4] = int (*a)[4];

int *a[N] == int *a;
int *a[] == int *a;
1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
double d[3] = {1.1, 2.2, 4.3};
fun_add(d);
printf("%f\n", d[2]);
}

void fun_add(double *p)
{
p[1] = p[0] + p[2];
// 上面一行代码与下一行相同
//*(p+1) = *p + *(p+2);
}

函数的指针

字符串的指针

字符串结尾会有\0来标识当前字符串的结束

初值个数不得超过数组大小

1
char ch[20] = "HelloWorld";

以char *型指针变量保存字符串首地址

1
2
3
4
5
6
7
8
9
char *ps = "iPhone";
// 只是将ps的指向地址改变,而不保存字符串内容本身。
ps = "iPad";

char s[] = "iTouch";
// 可以
ps = s;
// 错误,s的值不可以改变
s = "iMac";

字符串的输入输出

printf(“%s”, 一级地址);

其含义时从改地址开始逐个字符输出,直到遇到'\0'为止(\0不输出,也不自动换行)

输出字符串其他略有不同,后面要接地址,而不是直接接数据

1
2
3
4
printf("%d", 数据);
printf("%c", 数据);
printf("%f", 数据);
printf("%s", 地址);

puts(一级地址)

其含义是从地址开始逐个字符输出,直至\0为止(\0不输出);然后自动换行(即最后再多输出一个'\n'

1
2
3
puts(ps);
// 相当于
printf("%s\n", ps);

scanf(“%s”, 一级地址)

其含义是:读入键盘键入的一个字符串(最后要键入回车表示结束,但不键入’\0’),存入“一级地址”开始的一顿内存空间(回车符不存入),并自动在最后一个字符的后面添加'\0'

如果字符串中含有空格或Tab符,只能读入空格或Tab符之前的部分(不读空格和Tab符)。

1
2
3
4
5
char a[30];
scanf("%s", a);
printf("%s", a);
// 此时输入 how are you? <回车>
// 只会输出 how

gets(一级地址)

其含义是:读入从键盘键入的一行字符(最后一定要键入回车表示结束,但不键入'\0',存入“一级地址”开始的一顿内存空间(回车符不存入),并自动在最后一个字符的后面添加'\0'。如果字符串中含有空格、Tab符也一起读入并不中断。)

1
2
3
4
5
char b[30];
scanf("%s", b);
printf("%s", b);
// 此时输入 how are you? <回车>
// 会整句输出

字符串处理(常用套路)

数组法

1
2
3
4
5
6
7
// 第一种
for(i=0; s[i]!='\0'; i++)
printf(s[i]);

// 或者
for(i=0; s[i];i++)
printf(s[i]);

指针法

1
2
3
4
5
6
7
char *p;
p = 字符串起始地址;
while(*p != '\0')
{
用*p访问或处理每个字符;
p++; //使p指向字符串的下一个字符
}

'\0'和末尾字符定位

让p指向字符串的最后一个字符

1
2
3
while(*p)
p++;
p--;

字符串的连接

1
2
3
4
5
6
7
8
9
10
11
12
13
char s1[20] = "Hello";
char s2[] = "World";
char *ps1 = s1;
while(*ps1) ps1++;

char *ps2 = s2;
while(*ps2)
{
*ps1 = *ps2;
ps1++;
ps2++;
}
*ps1='\0';

字符串倒序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void reverse(char *s)
{
char *p, *q, t;

while(*q) q--;

while(*p)
{
t = *q;
*q = *p;
*p = t;
p++;q++;
}
}

int main()
{
char st[80];
scanf("%s", st);
printf("正序: %s", st);
reverse(st);
printf("倒序: %s", st);
}

常用字符串函数

使用以下函数要包含头文件 #include <string.h>

函数含义功能详细说明
strlen(地址)求字符串长度函数返回值为字符串的长度,即从地址开始到’\0’的字符个数(不计’\0’,但其中空格、Tab符、回车符等都计数)
strcat(串1地址, 串2地址)字符串连接 “串1=串1+串2”把从串2地址开始到’\0’的内容,连接到串1的后面(删去串1最后的’\0’),结果仍存入串1地址开始的空间中,并在结果字符串末尾自动添加新’\0’(串1地址的空间应足够大)
strcpy(串1地址, 串2地址)字符串拷贝 “串1=串2”把从串2地址开始到’\0’的内容,拷贝到串1地址开始的空间中,’\0’也一同拷贝(串1地址的空间应足够大)
strcmp(串1地址, 串2地址)字符串比较 “串1>串2” “串1<串2” “串1==串2”两个字符串的大小结果由函数返回值说明: 若函数返回值 > 0,说明 串1 > 串2 若函数返回值 < 0,说明 串1 < 串2 若函数返回值 == 0,说明 串1 = = 串2

sizeof

求占用多少字节,结果为整数

sizeof 是运算符,不是函数,同时也是关键字

sizeof(类型 或表达式)

常用字符函数

需要包含头文件 #include <ctype.h>

isalpha(ch)检查ch是否为字母字符,是返回1,否返回0
isdigit(ch)检查ch是否为数字字符,是返回1,否返回0
isspace(ch)检查ch是否为空白分隔符,即是否为空格、跳格(Tab, ‘\t’)、换行符(‘\n’)、回车符(‘\r’)、换页符(‘\f’)5种之一。 如是这些字符返回1,否则返回0
islower(ch)检查ch是否为小写字母字符,是返回1,否返回0
isupper(ch)检查ch是否为大写字母字符,是返回1,否返回0
tolower(ch)将ch转换为小写字母,函数返回转换后的字符
toupper(ch)将ch转换为大写字母,函数返回转换后的字符

第八章 指针(上)

地址和指针的基本概念

内容可变,地址不可变

有了变量的地址,存取变量就可以通过两种方式

  1. 通过变量名
  2. 通过变量的地址
1
2
3
4
5
// 指针变量
int *p;

// 变量类型 int *; 变量名为 p
// 指针变量一律占4个字节

基类型

指针变量所指向的变量的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int a = 1;
double b = 8.2;

int *p;
double *q;
void *r;

// p为int *型,只能指向int型
p = &a;
// q为double *型,只能指向double型
q = &b;
// r为void *型,可以指向任何类型
r = &a;
r = &b;

指针变量的赋值

指针变量值为0(地址值为0)时,表示什么也不指向 - 空指针。【地址值为NULL也表示空指针】

1
2
int a = 1;
int *p;

赋值语句的方法

1
p = &a;

定义指针变量时初始化(定义时赋初值)

1
2
int b = 2;
int *q = &b;

允许指针变量之间赋值,但另两个指针变量必须基类型相同

1
2
3
q = p;
int *r;
r = q;

特殊情况

1
2
3
4
5
int *p;
// 下面两种情况都表示空指针
p = 0;
// NULL(全大写),系统定义的宏 #define NULL 0
p = NULL;

两运算符(&, *)

&取地址运算符

获取变量的地址,用法:&变量名

*指针运算符(间接访问运算符)

获取或改写以p为地址的内存内容

*p*不是获得或改变指针变量本身的值,而是获得或改写它所指向单元的值。

int *p*运算只能用于指针变量,不能用于普通变量(如*a错误);

&*互为逆运算

1
2
3
4
5
6
7
8
9
10
11
int *p = &a;

p == &a;

*p == *&a == a;

&*p == p;

&*&*&*p == p;

*&*&*&p == a;

数组的指针

指针变量的运算

指向向前或向后移动n个单元,

p+n=p中的地址编号 + (每元素字节数*n)

p-n=p中的地址编号 - (每元素字节数*n)

概运算应只针对指向数组的指针变量进行,否则毫无意义

1
2
3
4
5
6
7
8
int a[5] = {1,2,3,4,5};
int *p;
p = &a[1];
// p++运算后与&a[2]相等
p++;

// p+=2 运算后与&a[4]相等
p+=2;

每个 char 型变量占1个字节,对基类型为char的指针变量+n,恰好是地址+n

1
2
char c[4] = {'a', 'b', 'c', 'd'}
char *p = &c[2];

void 类型的指针不能做+(-)n的运算(也不能++, –的运算)

指针的变量相减

两指针变量的加减乘除运算时没有意义的。

p1 - p2 = (p1中的地址编号 - p2中的地址编号)/镁元素字节数

关系运算

该运算一般只对指向同一数组的元素的两个指针变量进项,表示他们所指向元素的先后位置

1
2
3
4
5
6
7
8
// 表示p1和p2指向数组的同一个元素
p1 == p2

// 表示p1所指元素位于p2所指元素之后;
p1 > p2

// 表示p1所指元素位于p2所指元素之前;
p1 < p2
1
2
3
4
5
6
7
8
9
// 与0和NULL比较

// 表示p为空指针
p == 0;
p == NULL;

// 表示p不是空指针
p != 0;
p != NULL;
数组元素的地址转换为元素下标
1
2
3
4
5
6
int a[5] = {1,2,3,4,5};
int *p, *q;
q = &a[0];
p = &a[2];

p - a 则得2

两个重要公式

语法糖

编译时编译系统将a[i]全部变为*(a+i).

a[i]就是语法糖,便于我们理解而设计的成为语法糖。

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

a + i == &a[i];

a[i] == *(a+i)
指针变量可写为数组的形式
1
2
3
4
5
int *p = x[0];

x[i] == *(x+i);

&x[i] == x+i;

指针变量与一位数组名的统一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int a[5];
int *p;

p = a;
p = &a[0];

p[0] == *p == a[0];

p[i] == a[i];
*(p+i) == *(a+i);

p 和 a 不能互相转换
p的值(地址)可被修改,a的值(地址)不能被修改
p是变量,a是假想的“指针变量”(是常量)

指针运算的优先级

&, *的优先级和++, --相同,都非常高,仅次于()

++, --统计运算时,从右至左结合

二维数组的指针

1
2
3
4
5
// 行指针
int (*q)[4];

int a[3][4];
q = a;

一个表格、两句法则

草稿写法±1效果定义
二级指针[[]]移动一行int (*q)[4]; int b[3][4]; 中的b
二级指针[[]]必移动4字节int **r; int *s[3]; 中的s
一级指针[]移动一个单位(int型4字节,char型1字节…)int *p; int a[2]; 中的a
普通变量变量值±1int x;

法则:

  1. 定义变量时:一个*或一个[]升一级
  2. 使用时:一个*或一个[]降一级;一个&升一级

两个重要公式

二维数组元素的写法 a[i][j]*(*(a+i)+j)的语法糖

1
2
3
4
5
6
7
8
9
10
11
x[i] == *(x+i);

&x[i] == x+i;

a[i] == *(a+i) == q[i] == *(q+i);

q[i][j] == *(a[i]+j) == *(*(a+i)+j)==q[i][j] == *(q[i]+j) == *(*(q+i)+j);

特殊的 i=0,j=0
a[0][0] == **a;
&a[0][0] == *a;

第七章 函数

C源程序有函数组成:一个main函数和若干个其他函数组成,main函数调用其他函数

函数概述

参数

0多个,有的函数有1多个参数,有的函数没有参数

返回值

0~1个,有的函数有1个返回值,有的函数没有返回值

函数分两种

系统函数

又称为标准函数或库函数,如sqrt(), fabs(), rand()

需要包含对应的头文件才能调用: #include <xxx.h>

自定义函数

我们自己编写的函数,需要定义后才能调用

函数的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
函数的返回值类型 函数名(参数类型1 参数1, 参数类型2 参数2 ...)
{
语句...
}

例如:
// 无返回值的
void fun()
{
printf("This is a function");
}

// 有返回值的
int fun2()
{
return 1;
}

// 有参数,参数不可以简写成 int a,b
int max(int a, int b)
{
if(a>b)
return a;
else if(a<b)
return b;
else
return 0;
}

// 返回值类型包括(int, float, double, char...)

函数的调用

调用就使用,对有参数的函数,还要同时给出参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

void func(int p)
{
int d=2;
p=d++;
printf("%d\n", p);
}

int main()
{
int a=1;
fun(a);
printf("%d\n", a);
}

形式参数(简称形参)

在函数定义头部中的参数,在函数未被调用时,此参数无具体值。

1
2
3
4
5
// 这里的a,b就是形参
void max(int a, int b)
{
...
}

实际参数(简称实参)

调用函数时给出的具体参数,是有具体的值。

1
2
// 这里的1,2就属于实参
max(1,2);

函数的返回值

return调用返回

函数的好处

程序功能被细分为若干函数,每个函数负责一个小功能,main函数负责调度、指挥个函数的工作。

有return语句无return语句
有返回值的函数(名前非void)返回表达式的值,函数结束,返回系统默认值,函数结束函数中语句执行结 束后(执行到最后的}),返回系统默认 值,函数结束
无返回值的函数(名前有void)函数结束(无返回值) ;return 表达式 这是错误的函数中语句执行结 束后(执行到最后 的 } ),函数结束(无返回值)

函数的声明

C语言是按照顺序编译,所以要将其他函数放在main函数前面

1
2
3
4
5
6
7
8
9
void fun()
{
...
}

int main()
{
fun();
}

也可提前定义函数,避免出错,注意:在文件头部声明函数的时候,只可省略形参名,;不可省

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

void fun(int p);

int main()
{
fun();
}

void fun(int p)
{
...
}

递归

函数直接或间接的自己调用自己,称为递归。

递归程序,结构简练,可读性强。但执行时其时间和空间上的开销都比较大。

为防止自调用无休止的进行,在函数内必须设置调用终止的条件,否则程序将无法终止,被称为死递归。

递归的分析方法

与嵌套调用类似。

尽管每次调用的都是资深(同一函数),弹药把每次所调用的函数都看作是不同的函数,这些函数都具有相同的参数、返回值和语句。

经典题型

求阶乘

1
2
3
4
5
int recursion(int loop)
{
if(loop == 1 || loop == 0)return 1;
return loop*recursion(loop-1);
}

函数的作用域和存储类别

变量的作用域

局部变量

只在本函数内有效

若未赋初值,值为随机数

不同函数中可使用同名变量,形参和实参也可同名(形参也是函数内的变量)

在复合语句中定义变量(只在复合语句“块”内有效)

全局变量

全局变量在函数外定义的变量、

作用域:从变量定义处开始,到本程序文件末尾均有效(其中所有函数都能用)。

初始值自动为0。

全局变量可在多个函数中同时起作用,应尽量少用或不用全局变量。

不同存储类别的变量的特点

变量定义前加autostaticregister
存储位置内存动态存储区内存静态存储区CPU寄存器(没在内存)
作用域所在函数内,或所在复合语句 { } 内有效同auto同auto
生存期离开函数或{ }就消失永久保留同auto
初值随机数,初值重新赋值值为0,初值只赋一次同auto

编译预处理

宏定义,只会替换文本,不会计算

1
2
3
4
5
6
#define N 13

int main()
{
printf("%d", N);
}

先预处理,再编译;预处理部分不编译

预处理阶段将宏定义字符串替换

1
2
3
4
5
6
7
#define M (y*y+3*y)

语句为
s=3*M + 4*M;

预处理后
s=3*(y*y+3*y) + 4*(y*y+3*y)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#define N 3+5
main()
{
printf("%d", 2*N);
}

// 预处理后
#include <stdio.h>

main()
{
printf("%d", 2*3+5 );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 此例为错误示例
#include <stdio.h>
#define N 3+5;
main()
{
printf("%d", 2*N);
}

// 预处理后,这种情况是错的,不能这么写
#include <stdio.h>

main()
{
printf("%d", 2*3+5; );
}
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#define PRINT printf("*");
main()
{ PRINT
}

// 预处理后
#include <stdio.h>

main()
{ printf("*");
}

头文件

预处理后会将头文件替换掉

1
2
#include <头文件名.h>
#include "头文件名.h"

c源程序 -> 预处理 -> 编译 -> 运行

第六章 数组

一维数组

1
2
3
4
5
6
7
8
9
10
11
// 定义数组的长度
int a[5];

// 数组也是变量,必须先定义,后使用,不能与其他变量名、数组名重名

// 定义时赋初值
int x[5] = {1,2,3,4,5};

// 常用编程套路,数组与循环结合
for(i=0; i<元素个数; i++)
对a[i]的操作

一维数组的运用

已知10名同学的考试成绩,请编程统计及格人数,并计算10名同学成绩的平均分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#inlcude <stdio.h>
#include <stdlib.h>
#define N 5

int main()
{
float b[N] = {58.0, 69.0, 92.1, 33.3, 50.1, 66.5, 12.5, 63.4, 68.4, 10.1}, sum, avg;
int i, pass;
sum = 0; pass=0;
for(i=0; i<N; i++)
{
sum += b[i];
if(b[i]>= 60)
pass++;
}
avg = sum / N;

printf("通过考试人数为: %d\n", pass);
printf("平均分数为: %5.1f", avg);
}
1
printf("%5.1f", fa);

5表示输出数字长度为五位

1表示输出数字的小数后1位

注意

1
2
3
4
5
6
int a[5];int i=3;
a[i]=1; 引用数组时,下标可以是常量
int x[i]; 定义数组时不能用变量表示元素个数
a[i++]=2; a[3]=2;之后i由3变为4
下标必须为整数
不能一次引用整个数组

一位数组的初始化

1
2
3
4
5
6
7
8
9
10
11
int a[5] = {5, 10};
// 如果数组长度多于初始化值得时候,数组中未赋值的元素将默认赋值为0,相当于
int a[5] = {5, 10, 0, 0, 0};

// 对字符型数组,后面元素自动赋'\0'实质上仍是赋值0
char s[4] = {'a', 'b'};

// 数组初始化另一种写法,C会自动计算数组长度
int a[] = {1,2,3,4,5};
// 相当于
int a[5] = {1,2,3,4,5};

程序例

1
2
3
4
1. 编程删除数组b中下标为2的元素
b[5] = {99, 60, 75, 86, 92};
修改后
b[5] = {99, 60, 86, 92, 92};
1
2
3
4
2. 编程在数组b中的下标为2的元素之前,插入新元素100
b[10] = {99, 60, 75, 86, 92};
修改后
b[10] = {99, 60, 100, 75, 86, 92};
1
2
3
4
3. 选择排序,将数组b从小到大排序
b[5] = {99, 60, 75, 86, 92};
排序后
b[5] = {60, 75, 86, 92. 99};

二维数组

二维数组又为特殊的一维数组,它的每个元素又是一维数组

1
2
3
4
二维数组的定义
数组类型 数组名[行数][列数]
int a[2][3];
int型的变量名为a的数组,23

二维数组在内存中的存储形式

硬件存储器(内存)是连续的和线性的,二维表形式的二维数组在内存中是线性存储和按行排列

注意

任何情况下,都不能省略列数(即不能省略第二个[] 内的数);

在能判断出行数时,可省略行数(即可省略第一个[]内的数);

1
2
3
int a[2][3];
a = {1,2,3,4,5}
// 只有定义时赋初值才能用 ={} 的形式,故上述写法错误

第五章 循环结构

You are more powerful than any other person.

你比任何人都强大。

程序的三种基本结构

顺序结构

选择结构

循环结构

while语句

先判断后执行,表达式为真,

注意:如果表达式的值一直为真,没有改变,并且没有break;,则会陷入死循环

1
2
3
4
while(表达式)
{
语句
}

do-while

先执行后判断

1
2
3
4
5
do{
语句
}while(表达式);

// 注意:do-while的while号后有分号。

for

for 语句中的3个表达式均可省略,但;不可省略。

1
2
3
4
5
6
7
8
9
10
for(表达式1; 表达式2; 表达式3)
{
语句
}

// 可以看成
for(循环变量赋初值; 循环条件; 循环变量增量)
{
语句
}

执行顺序

[括号内为循环]

表达式1 -> 表达式2 -> [语句 -> 表达式2 -> 表达式3 ->]

for循环内还可以使用逗号表达式

1
2
3
4
5
int sum, i;
for(sum=0, i=0; i<100; sum+=i, i++)
{
语句
}

循环嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main()
{
int i,j;
for(i=1; i<=2; i++)
{
for(j=1; j<=3; j++)
{
printf("*");
}
printf("\n");
}
}

break和continue

break:跳出整个循环。

continue :结束本次循环,转到循环的开始判断是否执行下一次循环。

穷举法

穷举法也成为了枚举法,将在此范围内的所有可能情况逐一验证,知道全部情况验证完毕。

素数

输入一个数,判断其是否为素数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
int input, i, k;
printf("请输入大于1的数\n");
scanf("%d", &input);

k = sqrt(input);

for(i=2; i<k; i++)
if(input%i == 0) break;

if(i>k)
printf("%d是素数\n", input);
else
printf("%d不是素数\n", input);
}

第四章 选择机构

Everthing ending is just a new beginning.

每次结束都是新的开始

关系运算符

1
< > <= >= == !=

逻辑运算符

1
2
3
4
5
6
7
8
&&	逻辑与(且)
两者都为真时结果才为真有一个为假,结果为假
|| 逻辑或
有一个为真,结果为真
两者都为假时,结果才为假
! 逻辑或
运算量为真时,结果为假
运算量为假时,结果为真
1
2
3
// 1为真 0为假
5 > 0 1
5 < 3 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 优先级 从高到低
0. ()
1. 逻辑非(!) ++ -- ~ -
2. 算术运算
* / % + -
<< >> [左右移]
3. 关系运算
< <= > >=
== !=
4. && 和 ||
&& 优先级大于 ||
5. 条件 ? :
6. 赋值运算 += -= *= ...
7. 逗号
1
2
5 && 3;
// 表达式的值为 1

条件运算符

1
2
?	三目运算符
表达式1 ? 表达式2 : 表达式3

if 语句

1
2
3
4
5
6
7
8
9
// 表达式值为非0,执行语句;0不执行语句
if(表达式)
{
// 执行语句
}

// if 单语句要注意(if后无大括号),只能跟一条语句
if(a>2) a = 3;

交换两个变量的值

1
2
3
t = a;
a = b;
b = t;

switch 语句

1
2
3
4
5
6
7
8
9
10
11
12
switch(表达式)
{
case 表达式1:
语句1;
break;
case 表达式2:
语句2;
break;
...
default:
语句n;
}

第三章 顺序结构

You only get one shot, do not miss your chance to blow.

你只有一发子弹,不要错过引爆全场的机会。

语句

1
2
3
4
5
6
7
// 大括号是程序块的分界符,后不跟分号(;)
{
int a;
a = 1;
}

// 每条语句后要加分号,语句也可以也在同一行,但要用分号分割

输入输出

C语言程序的 输入/输出 都要通过库函数完成

库函数,就是“仓库“里的函数

在C语言要使用“库函数”,首先要用 #include 将一些文件包含到程序中,文件后缀一般为 .h ,称为头文件。

1
2
3
#include <stdio.h>
// 或者是
#include "stdio.h"

putchar()

单字符输出

1
putchar('a');

getchar()

单字符输入

1
getchar();

printf()

格式输出函数

1
2
3
4
5
6
#include <stdio.h>

int main()
{
printf("Hello World");
}

格式字符串控全体,

数据替换百分比(%)

字符c,正数d

小数f,指数e

(o)八叉(x)u无号

字符串s要牢记

间数全宽点小数

负号表示左对齐

scanf()

格式输入函数

1
scanf("格式控制字符串", 变量1的地址, 变量2的地址, ...)
1
2
3
4
5
6
7
8
#include <stdio.h>
int main
{
int a,b,c;
printf("input a,b,c\n");
scanf("%d%d%d", &a, &b, &c);
printf("a=%d, b=%d, c=%d", a, b, c);
}

scanf, 键盘输入

后为地址,不能输出。

间数宽度,%c全读。

非格式符,麻烦用户。

函数功能用法举例
sqrt(x)求x的算术平方根,x≥0sqrt(2)
abs(x)求x(整数)的绝对值abs(-5)
fabs(x)求x(实数)的绝对值fabs(-2.5)
log(x)求自然对数ln(x)log(2)
exp(x)求ex的值exp(2)
pow(x,y)求xy的值,注意x^y是“按位异或”不是求xypow(2, 3)
sin(x)求x的正弦值,x单位为弧度sin(30*3.14/180)
cos(x)求x的余弦值,x单位为弧度cos(3.14)
tan(x)求x的正切值,x单位为弧度tan(1.3)
asin(x)求sin-1(x)的值(弧度),-1≤x≤1asin(1)
acos(x)求cos-1(x)的值(弧度),-1≤x≤1acos(0)
atan(x)求tan-1(x)的值(弧度)atan(-82.24)

第一章 概述

Good things are worth the wait.

美好的事物都值得等待。

标识符

命名规则:

标识符名很简单,字母数字下划线。

字母区分大小写,非数打头非关键。

常量

程序运行过程中值不会变化的成为常量。

C语言编译过程

C语言源程序 -> 目标程序 -> 可执行程序

进制

二进制转换十进制

十进制转换二进制

第二章 数据类型运算符和表达式

整形常量

1
2
3
4
5
6
7
// 十进制
a = 10;
// 八进制
a = 012;
// 十六进制
a = 0xA;
// 不允许写为二进制形式。

整型常量表示法,十进制数直接打。

数前添零进制八,十六进制再加叉。

数值类型

整型类型

int, short int, long int, long long int

类型类型备注
short整数型2个字节
long长整型4个字节
signed有符号型可以保存负数的整型变量(signed可省略)
unsigned无符号型可保存正数和0,但不能保存负数的整型变量
signed short int a有符号短整型2字节,-32768 ~ 32767
int整数型4个字节

浮点类型(也称为实型)

float, double, 双精度浮点型

根据精度不同,克表示数据范围不同,根据实际情况选用。

类型类型备注
float单精度型4个字节,最大7位有效数字
double双精度型8个字节,最大15位有效数字
小数形式
1
2
3
4
5
6
7
8
float f;
f = 12.;
f = .36;
f = 1.0;

// 可以省略正数部分的0
// 也可以省略小数末尾的0
// 但必须有小数点。
指数形式
1
2
3
4
5
// E(或e)代表 x10
2.1E5 == 2.1 * pow(10,5);
-0.8E-2 = -0.8 * pow(10, -2);
...
// E(e)后面必须是正数,前后两边紧相连

整型变量和实型变量

整型变量:只能保存整数,不能保存实数。(例如:int)

实型变量:只能保存实数,不能保存正数。(例如:float, double)

常量后缀

L(或l)

整数后加为长整型(long),实数后加为长双精度型(long double)

1
0L, 12L, 1.2L

U(或u)

只能加在整数后,表示常量为无符号型,不能表示负数。

1
2
3
4
0U, 6U

// 也可既用L又用U
12LU, 6LU
F(或f)

表示常量为单精度型,加在整数后,则此数不为整数。

1
1.23f, 356F

字符类型

单引号包裹的一个字符,称为字符常量。

单引号内必须为英文,只能有一个字符,每个字符常量占一个字节(8个bit位),单引号是定界符不属于字符内容。

ASCII码

标准ASCII码0~127,字符和整数是混用的,字符可边做对应的ASCII码

ASCII码详情
0~31, 127控制字符(不可显示)
32空格字符
48~57数字’0’~’9’
65~90‘A’~’Z’,
97~122‘a’~’z’,小写字母比大写多32

转义字符 '\'

1
2
3
4
5
6
\n 换行符
\a 响铃符
\t 制表符
\\ 表示 \ 字符
\' 表示 ' 字符

字符串

末尾 \0 表示字符串结束

字符常量字符串常量
引号单引号双引号
字符个数只能占一个字符可含0~多个字符
能否将值赋值给char型变量可以不可以
有无对应变量有字符型变量无字符型变量
占用内存字符数一律一个字节字符串字符数(长度)+1

算数运算符

优先级:

先乘除,后加减

* / %优先级相同,高于 + -

1
+ - * / %

自动类型转换

表达式的类型:

两个运算量类型一致时,与两个运算量的类型一致。

两个运算量类型不一致时,表达式的类型与其中较高类型运算量的类型一致(较低类型自动转换为高类型再运算)

1
2
char -> int -> float -> double
特殊:所有浮点数必然转换为 double 再运算(即使两个 float 型量的运算也会先转换为 double

取整数的个、十、百、千…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int a;
a = 123456;
// 个位
a%10;

// 十位
(a/10)%10

// 百位
(a/100)%10

...

// 取最高位时,a/100000 ,只会有一位数,不%10 也可以

习题

  1. 一个数位78378 取它的所有千位和百位
  2. 水仙花

赋值运算符

= 赋值运算符优先级再C语言中排倒数第二位

1
2
3
4
5
6
7
8
9
int n;
n = 8;
n += n *= n - 2;
// 拆分运算
n += n *= 6;
n += 48;
n = n + 48;
n = 96;
// 所以表达式的值为 96,表达式求值后的n的值为 96

自增和自减

i++

单目运算符 优先级很高,仅次于括号()

只能用于变量

i++ 计算后会修改i 自身的值

i++++i 的区别

++在先,先加后用;

++在后,后加先用;

--在先,先减后用;

--在后,后减先用;

1
2
3
4
5
6
7
int a=1, b;
b = 5 - a++;
// 计算后,b=4 a=2

int a=1, b;
b = 5 - ++a;
// 计算后,b=3 a=2

逗号运算符和逗号表达式

逗号运算符优先级最低(倒数第一),顺序自左到右

1
2
3
4
5
6
// 运算符
int x=1, y=2, z;
z = (x++, y++);
z = 3;

// 表达式亦相同

位运算

2个条件

数据必须转换为二进制才能进行

只能对整型数据和字符型数据进行

按位与 & 和逻辑 && 不同

按位与 &

按位或 |

按位异或 ^

性质:

任何数与他的本身按位异或,结果都为0

a^k^k = a,可根据这个性质对数据加密

按位求反 ~

~是单目运算符,将数据转换为二进制后,将个二进制位反过来,0变1,1变0

左移 << 右移 >>

左移 <<

<<左边的数的各二进制整体左移若干位,由 <<右边的数指定移动的位数。移出的位将被舍弃。

一个数左移i位,相当于乘以2的i次方

1
2
3
3 << 4
// 相当于
3 * (2*2*2*2)
右移

>>左边的数的各二进制整体右移若干位,由 >>右边的数指定移动的位数。移出的位将被舍弃。

一个数左移i位,相当于乘以2的i次方

1
2
3
15 >> 2
// 相当于
15 / (2*2)

空类型

void

派生类型

  1. 指针类型 *
  2. 数组类型 []
  3. 结构体类型 struct
  4. 共同体类型 union
  5. 函数类型

Visual Stdio 快捷键

注释: Ctrl + K, Ctrl + C

取消注释:Ctrl + K , Ctrl + U

0%