第一本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
posted @ 2018-07-05 18:05  洛克十年  阅读(155)  评论(0编辑  收藏  举报