第一本C语言笔记(下)
11. 数组
(1)数组初始化时,如果初始化数字个数超过存储区个数,就忽略多余数字。如果初始化数字个数少于存储区个数,则后面的存储区自动被初始化为0。
(2)数组名称可以代表数组里第一个存储区的地址。可以对数组名称进行sizeof计算,结果是数组所包含的总字节数。
(3)变长数组:C99规范允许声明数组时使用变量表示数组里包含的存储区个数。但是变长数组不能初始化。
(4)一维、二维少数族名称不可被赋值。对二维数组名进行sizeof计算可以得到数组里面所有存储区的总大小。可以在二维数组名称后加一个下标,这个写法中的下标作为组下标使用。它表示组下标对应组中第一个存储区的地址。
int arr[3][4]; //arr[1] 表示二维数组中 arr[1][0]的地址, 这个写法有时候可以代表一组中的所有存储区(看做一个一维数组)
(5)产生7个1~36之间的整数
#include <stdio.h>
void Product7Numbers(int arr[7])
{
int count = 0, i = 0;
srand(time(0));
//改循环中,负责产生随机数
do{
arr[count] = rand()%36 + 1;
//该循环中,用于检查新产生的随机数是否和之前产生的所有随机数重复
for(i=0; i<=count-1; ++i)
{
if(arr[count] == arr[i])
break;
}
//如果新产生的随机数和之前的所有随机数都不重复,则count加1,表示成功产生一个随机数
if(i == count)
{
count++;
}
}while(count<7);
}
void PrintNumbers(int arr[7])
{
int i = 0;
for(i=0; i<7; i++)
{
printf("arr[%d]: %d\n",i, arr[i]);
}
}
int main()
{
int arr[7] = {0};
PrintNumbers(arr);
Product7Numbers(arr);
PrintNumbers(arr);
return 0;
}
[root@localhost 0701]# vim RandomNum.c
[root@localhost 0701]# gcc RandomNum.c
[root@localhost 0701]# ./a.out
arr[0]: 0
arr[1]: 0
arr[2]: 0
arr[3]: 0
arr[4]: 0
arr[5]: 0
arr[6]: 0
arr[0]: 17
arr[1]: 1
arr[2]: 2
arr[3]: 8
arr[4]: 24
arr[5]: 6
arr[6]: 31
12. 函数
(1)多函数程序执行的模式如下:
a. 整个程序的执行时间被话费成多个端,每个段分给一个函数使用;
b. 任何两个时间段不能相互重叠,并且所用时间段必须连续;
c. 如果函数A把自己的时间分给函数B使用,则函数B结束后必须把后面的时间还给函数A
(2)变量不可以跨函数使用,不用函数内部的变量可以重名。
(3)如果函数多次执行,则每次变量对应的存储区可能不同,volative 声明变量的存储区可以在多个程序中使用。
(4)用于实现数据传递的存储区必须由调用函数提供。
(5)调用函数只有一次机会获得返回值,得到后必须立刻使用或者转存到某个存储区里。
(6)函数名前什么都不写,表示函数有一个整数类型的返回值。(C99中不支持)
(7)只要能当数字使用的内容都可以作为实际参数。
(8)如果小括号里什么都不写,表示函数形参的个数和类型是任意的。
(9)数组是可以作为形参使用的。
(10)使用数组形参可以实现双向数据传递,这种参数叫做输入输出参数。
(11)数组形参声明中可以省略存储区个数。
12. 静态变量
(1)静态变量初始化只在程序开始的时候执行一次。
(2)可以跨函数使用静态变量的存储区。
(3)静态全局变量的作用域只包含声明它的文件内部的所有语句,其生命周期为程序执行期间。
13. 指针
(1)指针变量可以用来记录地址数据,也可以根据记录的地址数据找到来源的存储区。
(2)指针和存储区的捆绑关系在程序执行过程中可能不断变化。
(3)可以把指针看做变量的某种身份,可以使用指针实现针对身份的编程。
#include <stdio.h>
void Sort3Numbers(int* p_a, int* p_b, int* p_c)
{
if(NULL == p_a |NULL == p_b | NULL == p_c)
{
return;
}
int t = 0;
if(*p_c < *p_b)
{
t = *p_c; *p_c = *p_b; *p_b = t;
}
if(*p_b < *p_a)
{
t = *p_b; *p_b = *p_a; *p_a = t;
}
printf("%d %d %d\n", *p_a, *p_b, *p_c);
}
int main()
{
int a=3, b=20, c=1;
Sort3Numbers(&a, &b, &c);
return 0;
}
[root@localhost 0701]# vim Sort3Numbers.c
[root@localhost 0701]# gcc Sort3Numbers.c
[root@localhost 0701]# ./a.out
1 3 20
#include <stdio.h>
void Sort3Numbers1(int** pp_a, int** pp_b, int** pp_c)
{
if(NULL == pp_a |NULL == pp_b | NULL == pp_c)
{
return;
}
int* p_t = NULL;
if(**pp_c < **pp_b)
{
p_t = *pp_c; *pp_c = *pp_b; *pp_b = p_t;
}
if(**pp_b < **pp_a)
{
p_t = *pp_b; *pp_b = *pp_a; *pp_a = p_t;
}
printf("%d %d %d\n", **pp_a, **pp_b, **pp_c);
}
int main()
{
int a=3, b=20, c=1;
int *p_a = &a;
int *p_b = &b;
int *p_c = &c;
Sort3Numbers1(&p_a, &p_b, &p_c);
printf("the mininum is %d\n", *p_a);
return 0;
}
[root@localhost 0701]# vim Sort3Numbers1.c
[root@localhost 0701]# gcc Sort3Numbers1.c
[root@localhost 0701]# ./a.out
1 3 20
the mininum is 1
13. 函数声明
(1)C语言中,函数形参的个数可以不固定,这种参数叫做变长参数(例如 printf 和 scanf )。
(2)函数的隐式声明:如果编译器首先编译函数调用的语句,就会猜测函数的格式,猜测结果里认为函数有一个整数类型存储区记录返回值,有任意多个不确定类型的形参。
(3)隐式声明中形式参数的类型只能是整数类型或者双精度浮点类型。
(4)如果隐式声明的格式和函数真实的格式不一致则编译时会出错。
14. 递归函数
(1)C语言中一个函数可以调用自己,这种函数叫递归函数。如果一个问题可以拆分成 n 个小问题,其中至少有一个小问题和原来的问题本质是一样的,这种问题就可以采用递归函数解决。
(2)递归函数编写步骤
a. 编写语句描述问题的分解方式(假设递归函数已经完成)
b. 在递归函数的开头编写分支处理无法分解的情况(这个分支必须保证函数可以结束)
(3)采用递归函数解决问题的思路叫递归,采用循环解决同样问题的思路叫递推。
(4)没有初始化的全局变量自耦东被初始化为 0,没有初始化的静态变量被自动初始化为 0。
(5)递归算1+2+3+...+n = ?
#include <stdio.h>
int RecursionAdd(int num)
{
if(num == 1)
{
return 1;
}
return RecursionAdd(num-1) + num;
}
int main(int argc, char** argv)
{
int num = atoi(*(argv+1));
printf("Sun is %d\n", RecursionAdd(num));
return 0;
}
[root@localhost 0701]# vim RecursionAdd.c
[root@localhost 0701]# gcc RecursionAdd.c
[root@localhost 0701]# ./a.out 2
Sun is 3
15. 指针与字符串
(1)地址数据只能参与如下计算过程:
a. 地址 +/- 整数 实际上加减的是 n 个捆绑存储区的大小
b. 地址 - 地址 结果是一个整数,这个整数表示两个地址之间包含的捆绑存储区个数
(2)计算机里对数组下标的处理:
arr[num] ------> *(arr+num)
(3)所有跨函数使用存储区都必须通过指针实现。
(4)函数可以把一个存储区里的地址作为返回值使用,这个时候它需要提供一个指针类型的存储区记录这个返回值。
(5)不可以把非静态局部变量的地址作为返回值使用。
(6)前置 const 和后置 const
const int* p_num; //表示 p_num 所指的存储区中的数字不能修改
int const *p_num1; //表示 p_num1 这个指针的值不能修改
(7)无类型指针通常作为函数的形式 参数使用,可以通过它把任意类型的存储区传递给被调用函数。
(8)字符串 —— C语言中所有的文字信息必须记录在一组连续的字符类型存储区里,所有文件信息必须以 '\0'(ASCII是整数0)字符作为结尾。
(9)所有的字符串都可以采用一个字符类型指针表示。(一般使用字符串字面值或字符数组表示)
(10)编译器在编译时会自动在字符串字面值的末尾增加一个 '\0' 字符。编译器会把字符串字面值替换成第一个字符所在存储区的地址。(程序中内容一样的字符串字面值只有 1 个,多个并列的字符串会被合并成一个)。
(11)只有包含 '\0' 的字符数组才能当做字符串使用。
(12)可以使用字符串字面值对字符数组进行初始化,这个时候 '\0' 字符也会被初始化到字符数组里。
(13)可以使用 %s 作占位符把字符串内容显示在屏幕上。
(14)将一个字符颠倒
#include <stdio.h>
#include <string.h>
char* ReversalCharString(char* p_char, int size)
{
char* p_start = p_char, *p_end = p_char + size - 1;
char temp = 0;
while(p_start < p_end)
{
temp = *p_start;
*p_start = *p_end;
*p_end = temp;
p_start++;
p_end--;
}
return p_char;
}
int main(int argc, char** argv)
{
char* p_test = *(argv+1);
ReversalCharString(p_test, strlen(p_test));
printf("%s\n", p_test);
return 0;
}
[root@localhost 0701]# vim ReversalCharString.c
[root@localhost 0701]# gcc ReversalCharString.c
[root@localhost 0701]# ./a.out ILoveYou
uoYevoLI
(15)指针数组里每个存储区都是一个指针类型的存储区,字符指针数组包含多个字符指针,每个字符指针可以代表一个字符串。所以可以采用字符指针数组带保镖多个相关字符串。例如 int main(int argc, char* argv[])。
16. 字符串相关处理
(1)处理字符串的函数
strlen 统计有效字符个数('\0'之前的字符),和sizeof不同。
strcat 合并两个字符串(把参数2中的内容追加到参数1的数组中),如果参数1所指的存储区不够大,则会造成严重的错误
strncat 功能和 strcat 一样,它比 strcat 多了一个整数类型参数表示数组里空余存储区的个数
strcmp 比较两个字符串的大小(长度),根据 ASCII 码比较,ASCII 码大则这个字符大。
返回值: 1 —— 前一个字符串大
-1 —— 后一个字符串大
0 —— 相等
strncmp 只比较前 n 个字符
strcpy 把一个字符串复制到字符串数组里(替换原有内容)
memset 把字符数组里多个存储区填充成同一个字符,无返回值。
strstr 在大字符串里查找小字符串的位置,返回值为找到字符串的指针,找不到的时候返回值是NULL。
(2)字符串输入输出函数(#include<stdio.h>)
sprintf 把数字按照格式放在字符数组里形成字符串
例如: sprintf(str, "%d %c %g\n", num, ch, fnum); //把num、ch、fnum 按照 "%d %c %g\n"的geshi8输出到字符数组 str 中。
sscanf 从字符串里获得数字并记录在缓存区里。
例如: sscanf(str, "%d %c %g", &num, %ch, &fnum); //把str字符串中的数字按 "%d %c %g"格式分别保存到 num、ch、fnum 中。
(3)字符串转换成数字
atoi 将字符串中不带小数点的数字字符 转换成 整数类型
atof 将字符串中带小数点的数字字符 转换成 双精度浮点型
17. 宏与程序编译
(1)编译器会在预编译阶段把程序中所有宏名称替换成它所代表的数字。
(2)可以在编译命令中使用 -D 选项指定宏名称所代表的的数字。
gcc -DPI = 3.14f 01macro.c //只有在编译时才知道的数字就应该用宏表示
(3)宏不可以使用自己的存储区和函数进行数据传递。宏也没有形式参数和返回值。
(4)能到数字使用的宏必须写成表达式
#define ABS(n) n >= 0 ? n | 0 - n //宏的参数可以直接代表函数的存储区
(5)宏没有形式参数,所以不能保证优先计算参数内部的操作符。宏里面所有能当数字使用的参数都应该写在小括号里。
(6)条件编译可以让编译器在编译时从 n 组语句中选择一组编译而或略其他组。
#ifdef / #ifndef ... #else ... #endif
#if ... #elif(任意多次) ... #else ... #endif
(7)多文件编译步骤
a. 把所有函数分散在多个不同的源文件里(主函数通常单独占一个文件夹)。
b. 为每个源文件编写配对的头文件(主函数所在的源文件不需要头文件)。
- 所有不分配内存的内容都可以写在头文件里
- 头文件里至少应该包含配对源文件里所有函数的声明
c. 在每个源文件里使用 #include 预处理指令包含所有必要的头文件
- 配对头文件是必需头文件
- 如果源文件里使用了头文件里声明的函数,则这个头文件也是必要头文件
(8)头文件中采用的宏名称应该根据文件路径变化得到,例如 _ADD_H_。
(9)如果希望在一个源文件里使用另一个源文件里声明的全局变量,就需要使用 extern 关键字再次声明这个全局变量。
- 使用 extern 关键字声明变量的语句不会分配内存,所以通常放在头文件里。
(10)不可以在一个源文件里使用另一个源文件中的静态全局变量
18. 结构体
(1)结构体声明中包含的变量声明语句不会分配内存。
(2)结构体变量会分配存储区,它可以用来记录数字。
(3)结构体中,在捆绑过的结构体指针后使用 -> 再加上某个子存储区名称,这个写法可以表示那个子存储区。
p_Person->age 等价于 (*p_person).age
(4)我们的计算机中,所有的指针都占 4 个字节。
19. 枚举(enum)和联合(union)
(1)计算机把从 0 开始的一组连续的整数分配给枚举类型中的所有名称。
(2)声明枚举时,可以指定某个名称代表的整数,后面的整数也会发生变化。
(3)联合存储区可以当做多种不同类型存储区使用,联合的每个子存储区代表联合存储区一种可写的类型。
(4)联合的所有子存储区开始地址一样,他们所占的位置互相重叠。
(5)联合存储区的大小是最大子存储区的大小
20. 函数指针
(1)二级指针通常作为函数的形式参数使用,这个时候可以让被调用函数使用调用函数的一级指针存储区。
(2)C 语言里,函数也有地址,函数名称可以代表函数的地址。
(3)函数指针可以用来记录函数的地址,函数指针也需要先声明,然后才能使用
- 函数指针声明可以根据函数声明变化得到
- 例如: int add(int, int) --> int (*p_func)(int, int)
(4)函数指针也分类型,不同类型的函数指针适合于不同类型的函数捆绑。
(5)函数指针一旦和函数捆绑,就可以用来调用那个函数。
(6)函数指针可以用作函数的形式参数,会作为实际参数使用的函数叫做回调函数。
21. 动态内存分配
(1)malloc 函数可以动态分配一组连续的字节
它的返回值表示分配好的第一个字节的地址
如果分配失败则返回值为 NULL
malloc 函数用一个无类型指针存储区记录返回值,需要首先强制类型转换成有类型指针然后才可以使用
动态分配内存使用完成后必须释放 free(p_num);
一个函数动态分配内存可以给任何其他函数使用(即延长了动态内存的生命周期)。
22. 文件操作
(1)所有 文件都采用二进制方式记录数字
如果文件里的所有二进制内容都对应字符,则这种文件叫文本文件
除了文本文件以外的所有文件叫做二进制文件
文本文件可以当做二进制文件使用
(2)文件操作的基本步骤
a. 打开文件 fopen("a.txt", "w");
b. 操作文件 fread/fwrite (以二进制方式操作)
c. 关闭文件 fclose(p_file);
(3) fopen 函数
fopen 函数有两个参数,第一个为文件名字符串,第二个为打开文件方式标识
打开文件的方式:
"r" 只能查看文件内容,只能从文件头开始查看。如果文件不存在,打开会失败
"r+" 比"r"多了修改功能
"w" 只能修改文件内容不能查看,只能从文件头开始修改,如果文件不存在就创建文件,如果已存在就删除文件内容
"w+" 比"w"多了查看功能
"a" 只能修改文件内容不能查看,在文件原有内容后面追加新内容,如果我呢间不存在就创建文件,如果文件存在不许修改文件原有内容
"a+" 比"a"多了查看功能
"b" 也是一种文件打开方式,它可以和上面任何一种打开方式混用,这个打开方式表示程序中只能采用二进制方式操作文件
(4)fopen 函数的返回值是一个地址,这个地址应该记录在文件指针里。
程序中只能使用文件指针代表打开的文件
fopen 函数如果打开文件失败则返回值为 NULL
(5)一旦完成对文件的操作之后就必须使用 fclose 函数关闭文件。
fclose 函数需要文件指针作为参数
关闭文件后文件指针成为野指针,必须恢复成空指针
(6)文件操作分两种:
a. 把内存中一组连续存储区的内容拷贝到文件里(写文件)
b. 把文件中一组连续字节的内容拷贝到内存里(读文件)
(7)fread 函数采用二进制方式读文件内容,fwrite 函数采用二进制方式写文件内容。这两个函数都需要四个参数
第一个参数: 内存中第一个存储区的地址
第二个参数: 内存中单个存储区的大小
第三个参数: 希望操作的存储区个数
第四个参数: 文件指针
这两个函数的返回值都表示实际操作的存储区个数。
(8)fprintf 函数可以把数据按照格式记录在文本文件里。这个函数的参数就是在 printf 函数参数的前面加一个文件指针。
(9)fscanf 函数可以按照格式从文本文件里落去数字并记录在存储区里。该函数的参数就是在 scanf 函数参数的前面增加一个文件指针。
(10)ftell 函数可以得到位置指针的数值。
(11)计算机里为每个打开的文件保留一个整数,这个整数表示下一次读写操作的开始位置。这个位置一定在两个相邻的字节之间。这个正式表示文件头开始到这个位置之间包含的字节个数。这个整数叫做文件的位置指针。
(12)每当从文件中获得 N 个字节或者向文件里写入 N 个字节后,位置指针向后移动 N 个位置。
(13)rewind 函数可以把位置指针设置成文件开头。
(14)fseek 函数可以把位置指针设置成文件里的任何位置。如果目标位置在基准位置后则用非负数表示,否则用负数表示。
(15)示例:以结构体为单位读写文件
[root@rockman 0706]# cat filewrite.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct{
int id;
float salary;
char name[10];
}Person;
void filewrite(char filepath[16])
{
FILE* pfile = fopen(filepath, "ab");
int i=0, cnt=0;
Person ps;
if(pfile)
{
for(i=0; i<2; i++)
{
printf("Please input your ID:");
scanf("%d", &ps.id);
printf("Please input your name:");
scanf("%s", &ps.name);
printf("Please input your salary:");
scanf("%f", &ps.salary);
cnt = fwrite(&ps, sizeof(ps), 1, pfile);
if(cnt)
{
printf("the record was writen successfully!\n");
}
else{
printf("fwrite error!\n");
}
}
}
fclose(pfile);
pfile = NULL;
}
int main()
{
filewrite("person.bin");
return 0;
}
[root@rockman 0706]# ./write.out
Please input your ID:1
Please input your name:rock
Please input your salary:1111
the record was writen successfully!
Please input your ID:2
Please input your name:mary
Please input your salary:2222
the record was writen successfully!
[root@rockman 0706]# cat fileread.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct{
int id;
float salary;
char name[10];
}Person;
void fileread(char filepath[16])
{
FILE* pfile = fopen(filepath, "rb");
int cnt=0;
Person ps;
if(pfile)
{
while(1){
cnt = fread(&ps, sizeof(ps), 1, pfile);
//printf("cnt:%d\n", cnt);
if(cnt)
{
printf("ID:%d, name:%s, salary:%f\n",ps.id, ps.name, ps.salary);
}
else{
break;
}
}
}
fclose(pfile);
pfile = NULL;
}
int main()
{
fileread("person.bin");
return 0;
}
[root@rockman 0706]# ./read.out
ID:1, name:rock, salary:1111.000000
ID:2, name:mary, salary:2222.000000
(16)编程实现文件拷贝功能,类似 cp 命令
[root@rockman 0706]# cat mycp.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void mycp(char* psrc, char* pdest)
{
char buf[100] = {0};
int size = 0;
FILE* pfsrc = NULL, *pfdest = NULL;
pfsrc = fopen(psrc, "rb");
if(!pfsrc)
{
printf("resouse file open error!\n");
fclose(pfsrc);
pfsrc = NULL;
return;
}
pfdest = fopen(pdest, "wb");
if(!pfdest)
{
printf("destination file open error!\n");
fclose(pfdest);
pfdest = NULL;
return;
}
while(1)
{
size = fread(buf, sizeof(char), 100, pfsrc);
if(!size)
{
break;
}
fwrite(buf, sizeof(char), size, pfdest);
}
fclose(pfdest);
pfdest = NULL;
fclose(pfsrc);
pfsrc = NULL;
return;
}
int main(int argc, char** argv)
{
mycp(*(argv+1), *(argv+2));
return 0;
}
[root@rockman 0706]# gcc mycp.c -o cp.out
[root@rockman 0706]# ./cp.out person.bin person_copy.bin
[root@rockman 0706]# ls -l per*
-rw-r--r--. 1 root root 40 Jul 6 10:45 person.bin
-rw-r--r--. 1 root root 40 Jul 6 11:08 person_copy.bin