光光的文章

  • C语言提高笔记

    C提高一 基本概念强化1、头文件函数声明
    分文件时,头文件防止头文件重复包含
    #pragma once//兼容C++编译器//如果是C++编译器,按照C标准编译#ifdef __cplusplusextern "c"{#endif//#ifdef __cplusplus}#endif数组作为函数参数会退化为一级指针:

    数组做函数参数时,应该把数组元素个数也传递给函数;
    形参中的数组,编译器把它当做指针处理,这是C语言的特色;
    实参中的数组,和形参中数组本质不一样;

    void print_array(int a[], int n)数据类型的本质:是固定内存大小的别名
    数据类型的作用:编译器预算对象(变量)分配的内存空间大小
    int a;//告诉C编译器分配4个字节的内存数据类型可以通过typedef起别名:typedef unsigned int u32;typedef struct student{ int a; char b;}STU;可以通过sizeof()测类型大小;Void类型 (无类型)

    如果函数没有参数,定义函数时,可以用void修饰:int fun(void);
    如果函数没有返回值,必须用void修饰:void fun(int a);
    不能定义void类型的普通变量,void a;//err,无法确定类型,不同类型分配空间不一样
    可以定义void 变量:void p;//ok,32位永远是4个字节,64位8字节
    void *p;万用指针,函数返回值,函数参数

    变量的本质:内存空间的别名
    必须通过数据类型定义变量
    变量相当于门牌号,内存相当于房间,通过门牌号找到房间,通过变量找到所对应的内存
    变量的赋值:1. 直接赋值 2. 间接赋值
    int a;a=100;//直接赋值int *p=0;p=&a;//指针指向谁,就把谁的地址赋值给指针*p=22;//间接赋值重点:没有变量,哪来内存,没有内存,哪里来内存首地址
    变量三要素(名称、大小、作用域),变量的生命周期
    内存四区模型

    栈区:系统分配空间,系统自动回收,函数内部定义的变量,函数参数,函数结束,其内部变量生命周期结束;
    堆区:程序员动态分配空间,由程序员手动释放,没有手动释放,分配的空间一直可用;
    静态区(全局区):(包括全局变量和静态变量,里面又分为初始化区和未初始化区,文字常量区:字符常量):整个程序运行完毕,系统自动回收;
    代码区,内存四区模型图

    栈区地址生长方向:地址由上往下递减;堆区地址生长方向:地址由下往上递增;数组buf,buf+1地址永远递增。

    函数调用模型
    程序各个函数运行流程(压栈弹栈,入栈出栈,先进后出)
    二 指针强化指针也是一种数据类型,指针变量也是一种变量,和int a本质是一样的
    1)指针变量也是一种变量,也有空间,32位程序大小为4个字节 int *p = 0x1122; 2)*操作符,*相当于钥匙,通过*可以找到指针所指向的内存区域 int a = 10; int *p = NULL; p = &a; //指针指向谁,就把谁的地址赋值给指针 *p = 22; //*放=左边,给内存赋值,写内存 int b = *p; //*放=右边,取内存的值,读内存 3)指针变量,和指向指向的内存是两个不同的概念 char *p = NULL; char buf[] = "abcdef"; //改变指针变量的值 p = buf; p = p + 1; //改变了指向变量的值,改变了指针的指向 *p = 'm'; //改变指针指向的内存,并不会影响到指针的值 4)写内存时,一定要确保内存可写 char *buf2 = "sadgkdsjlgjlsdk"; //文字常量区,内存不可改 //buf2[2] = '1'; //err间接赋值(*p)是指针存在最大意义
    1)间接赋值三大条件 a) 两个变量 b) 建立关系 c) 通过 * 操作符进行间接赋值 1) int a; int *p; //两个变量 p = &a; //建立关系 *p = 100; //通过 * 操作符进行间接赋值 2) int b; fun(&b); //两个变量之一:实参,给函数传参时,相当于建立关系 //p = &b void fun(int *p) //两个变量之一:形参参 { *p = 100; //通过 * 操作符进行间接赋值 } 2)如何定义合适类型的指针变量 //某个变量的地址需要定义一个怎么样类型的变量保存 //在这个类型的基础上加一个* int b; int *q = &b; int **t = &q;重要:如果想通过函数形参改变实参的值,必须传地址1、值传递,形参的任何修改不会影响到实参2、地址传递,形参(通过*操作符号)的任何修改会影响到实参用1级指针形参,去间接修改了0级指针(实参)的值。用2级指针形参,去间接修改了1级指针(实参)的值。用3级指针形参,去间接修改了2级指针(实参)的值。用n级指针形参,去间接修改了n-1级指针(实参)的值。 int a = 10; fun(a); //值传递 void fun(int b) { b = 20; } fun2(&a);//地址传递 void fun2(int *p) { *p = 20; //通过*操作内存 } int *p = 0x1122; void fun3(p);//值传递 void fun3(int *p) { p = 0x2233; } void fun4(&p);//地址传递 void fun4(int **p) { *p = 0xaabb; //通过*操作内存 }3、不允许向NULL和未知非法地址拷贝内存
    char *p = NULL; //给p指向的内存区域拷贝内容 strcpy(q, "1234"); //err //静态 char buf[100]; p = buf; strcpy(q, "1234"); //ok //动态 p = (char *)malloc(sizeof(char) * 10 ); strcpy(q, "1234"); //ok char *q = "123456"; strcpy(q, "abcd"); //?4、void *指针的使用
    void *p = 0x1122; //可以这么做,不建议,一般赋值为NULL char buf[1024] = "abcd"; p = (void *)buf; //指向 char printf("p = %s\n", (char *)p); //使用时转化为实际类型指针 int a[100] = { 1, 2, 3, 4 }; p = (void *)a; //指向 int int i = 0; for (i = 0; i < 4; i++) { //使用时转化为实际类型指针 int *tmp = (int *)p; printf("%d ", *(tmp+i)); printf("%d ", tmp[i]); printf("%d ", *( (int *)p + i ) ); } void * 常用于函数参数:memset(), memcpy()5、栈区返回变量的值和变量的地址区别
    int fun() { int a = 10; return a; } int *fun2() { int a = 10; return &a; } int *fun3() { static int a = 10; return &a; } int b = fun(); //ok, b的值为10 //也ok, p确实也保存了fun2()内部a的地址 //但是,fun2完毕,a就释放,p就指向未知区域 int *p = fun2(); //ok,fun3()中的a在全局区,函数运行完毕,a的空间不释放 int *q = fun3();6、.c -> 可执行程序过程 预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法 编译:检查语法,将预处理后文件编译生成汇编文件 汇编:将汇编文件生成目标文件(二进制文件) 链接:将目标文件链接为可执行程序 程序只有在运行才加载到内存(由系统完成),但是某个变量具体分配多大,是在编译阶段就已经确定了,换句话说,在编译阶段做完处理后,程序运行时系统才知道分配多大的空间,所以,很多时候说,这个变量的空间在编译时就分配(确定)了。
    7、指针做函数参数的输入输出特性输入:主调函数分配内存输出:被调用函数分配内存//结合内存四区模型图
    main() { char buf[100] = "123456"; fun1(buf); //输入 char *p = NULL; fun2(&p); //输出 //因为p在fun2()动态分配空间了,使用完毕应该释放 if(p != NULL) { free(p); p = NULL; } } void fun1(char *p /*in*/) { strcpy(p, "1234") } void fun2(char **p /*out*/) { char *tmp = malloc(100); strcpy(tmp, "1234"); *p = tmp; //间接赋值是指针存在最大意义,通过*操作内存 }8、变量内存的值和变量的地址int a = 0;a变量内存的值为0a变量的地址(&a)绝对不为0,只要定义了变量,系统会自动为其分配空间(一个合法不为0的地址)
    三 字符串操作1、字符串基本操作 1)字符串初始化 / C语言没有字符串类型,用字符数组模拟 C语言字符串以数字0,或字符 ‘\0’ 结尾,数字 0 和字符 ‘\0’ 等价 / char str1[100] = { ‘a’, ‘b’, ‘c’, ‘d’ }; //没赋值的自动以数字0填充 char str2[] = { ‘a’, ‘b’, ‘c’, ‘d’ }; //数组长度为4,结尾没有数字0 char str4[] = “abcdef”; //常用赋值方式,栈区 char p = “abcdef”; //文字常量区,内容不允许被修改 char buf = (char *)malloc(100); //堆区 strcpy(buf, “abcd”); //“abcd”拷贝到buf指向的内存区域中
    2)sizeof和strlen区别 //使用字符串初始化,常用 char buf8[] = “abc”; //strlen: 测字符串长度,不包含数字0,字符’\0’ //sizeof:测数组长度,包含数字0,字符’\0’ printf(“strlen = %d, sizeof = %d\n”, strlen(buf8), sizeof(buf8)); 3 4 char buf9[100] = “abc”; printf(“strlen = %d, sizeof = %d\n”, strlen(buf9), sizeof(buf9)); 3 100
    3)’\0’ 后面最好别跟数字,因为几个数字合起来有可能是一个转义字符 //\012相当于\n char str[] = “\0129”; printf(“%s aaaa\n”, str);
    4)字符’\0’, 数字0, 字符’0’, NULL的区别 a) 字符’\0’ ASCII码值为 0 的字符 字符’\0’ 和 数字 0 是等级的,’\0’中’\’是转义字符 char buf[100]; //下面是等级,在数组第10个位置设置字符串结束符 buf[10] = 0; buf[10] = ‘\0’; b) 字符’0’是字符串的某个字符内容为’0’, ASCII码值为 48 的字符 char buf[100]; buf[0] = ‘0’; //第0个字符为字符 ‘0’ c) NULL 标准头文件(stdio.h)的宏 其值为数字 0
    5)数组法、指针法操作字符串 char buf[] = “abdgdgdsg” char p = buf; //buf是数组首元素地址,它也是指针 for (i = 0; i < strlen(buf); i++) { //[ ] 和 操作是等价的,也是操作指针指向内存 printf(“%c “, buf[i]); //符合程序员习惯 printf(“%c “, p[i]); //符合程序员习惯 printf(“%c “, (p+i)); printf(“%c “, (buf + i)); } 注意:数组名也是指针,数组首元素地址,但是,它是一个只读常量 p++; //ok buf++; //err
    6)字符串拷贝函数 //成功为0,失败非0 //1 判断形参指针是否为NULL //2 最好不要直接使用形参 int my_strcpy(char dst, char src) { if (dst == NULL || src == NULL) { return -1; } //辅助变量把形参接过来 char to = dst; char from = src;
    //*dst = *src //dst++, src++ //判断 *dst是否为0, 为0跳出循环 while (*to++ = *from++) ; return 0;}2、项目开发常用字符串应用模型 1、利用strstr标准库函数找出一个字符串中substr出现的个数 1)do-while模型: char p = “11abcd111122abcd333abcd3322abcd3333322qqq”; int n = 0; do { p = strstr(p, “abcd”); if (p != NULL) { n++; //累计个数 //重新设置查找的起点 p = p + strlen(“abcd”); } else //如果没有匹配的字符串,跳出循环 { break; } } while (p != 0); //如果没有到结尾
    2)while模型: char p = “11abcd22222abcd33333abcd444444qqq”; int n = 0; while( (p = strstr(p, “abcd”)) != NULL ) { //能进来,肯定有匹配的子串 //重新设置起点位置 p = p + strlen(“abcd”); n++; if(p == 0)//如果到结束符 { break; } } printf(“n = %d\n”,n);
    3)函数封装实现 int my_strstr(char p,int n) { //辅助变量 int i = 0; char tmp = p; while((tmp = strstr(tmp, “abcd”)) != NULL) { //能进来,肯定有匹配的子串 //重新设置起点位置 tmp = tmp + strlen(“abcd”); i++; if(tmp == 0)//如果到结束符 { break; } } //间接赋值 n = i; return 0; } int main(void) { char p = “11abcd22222abcd33333abcd444444qqq”; int n = 0; int ret = 0; ret = my_strstr(p,&n); if(ret != 0) { return ret; } printf(“n = %d\n”,n); return 0;}
    2、两头堵模型 char *p = “ abcddsgadsgefg “; int begin = 0; int end = strlen(p) - 1; int n = 0; if(end < 0){ return; } //从左往右移动,如果当前字符为空,而且没有结束 while (p[begin] == ‘ ‘ && p[begin] != 0) { begin++; //位置从右移动一位 } //从右往左移动,如果当前字符为空 while (p[end] == ‘ ‘) { end—; //往左移动 } n = end - begin + 1; //非空元素个数 strncpy(buf, p + begin, n); buf[n] = 0;
    //如何证明strncpy()拷贝不会自动加字符串结束符'\0'char dst[] = "aaaaaaaaaaaaaaa";strncpy(dst, "123", 3);printf("dst = %s\n", dst); //dst = "123aaaaaaaaaaaa"四 const的使用1)const声明变量为只读 //const修饰的变量,定义时初始化 const int a = 10; //a = 100; //error int q = &a;
    q = 22; char buf[100] = “abcdef”;//从左往右看,跳过类型,看修饰那个字符 //如果是修饰,说明指针指向的内存不能改变 //如果是修饰指针变量,说明指针的指向不能改变,指针的值不能修改 const char p = buf; //类似于文字常量区 char p = “123445”; char const p = buf; //修饰,指针指向能变,指针指向的内存不能变 //p[0] = ‘1’; //error p = “123456”; //ok char const p1 = buf; //修饰指针变量,指针指向的内存,指针指向不能变 //p1 = “123456”; //error p1[0] = ‘1’; //ok const char * const p2 = buf; //p2, 只读
    2)如何引用另外.c中的const变量 extern const int a;不能再赋值,只能声明
    五 多级指针1)如何定义合适类型的指针变量 //某个变量的地址需要定义一个怎么样类型的变量保存 //在这个类型的基础上加一个 int b; int q = &b; //一级指针 int t = &q; //二级指针 int *m = &t; //三级指针
    2)二级指针做输出 输入:主调函数分配内存 输出:被调用函数分配内存 char *p1 = NULL; //没有分配内存 int len = 0; getMem(&p1, &len); //要想通过函数的形参改变实参的值,必须地址传递
    void getMem(char **p1 /*out*/, int *plen /*in*/){ //间接赋值,是指针存在最大的意义。 *p1 = malloc(100); *plen = 100;} 指针做参数输出特性3)二级指针做输入的三种内存模型 1、//指针数组,数组的每个元素都是指针类型 // [] 的优先级比 高,它是数组,每个元素都是指针类型(char ) char myArray[] = {“aaaaaa”, “ccccc”, “bbbbbb”, “111111”}; //char **p = {“aaaaaa”, “ccccc”, “bbbbbb”, “111111”}; //err void fun(int a[]); void fun(int a); // a[] 等价于 a void printMyArray(char myArray[], int num); // char 代表类型,myArray[]等价于 myArray // char myArray[] -> char myArray void printMyArray(char myArray, int num); void sortMyArray(char *myArray, int num); 如果排序,交换的是指针的指向,因为原来指针指向是文字常量区,文字常量区的内存一旦分配,内存就不能变。
    2、//二维数组 10行30列,10个一维数组a[30] //总共能容量10行字符串,这个用了 4 行 //每行字符串长度不能超过29,留一个位置放结束符:数字0 char myArray[10][30] = {“aaaaaa”, “ccccc”, “bbbbbbb”, “1111111111111”}; void printMyArray(char myArray[10][30], int num); void sortMyArray(char myArray[10][30], int num); //定义二维数组,不写第一个[ ]值有条件,必须要初始化 char a[][30] = {“aaaaaa”, “ccccc”, “bbbbbbb”, “1111111111111”};//ok char a[][30]; //err,定义时必须初始化
    二维数组的数组名代表首行地址(第一行一维数组的地址)首行地址和首行首元素地址的值是一样的,但是它们步长不一样首行地址+1,跳过一行,一行30个字节,+30首行首元素地址+1,跳过一个字符,一个字符为1个字节,+1sizeof(a): 有4个一维数组,每个数组长度为30,4 * 30 = 120sizeof(a[0]): 第0个一维数组首元素地址,相当于测第0个一维数组的长度:为30char b[30];&b代表整个一维数组的地址,相当于二维数组首行地址b代表一维数组首元素地址,相当于二维数组首行首元素地址&b 和 b 的值虽然是一样,但是,它们的步长不一样&b + 1: 跳过整个数组,+30b+1: 跳过1个字符,+1//不能通过 char ** 作为函数形参,因为指针+1步长不一样// char **,指针+1步长为 4 个字节// char a[][30],指针+1步长为 1 行的长度,这里为 30 个字节void printMyArray(char **buf, int num);3、int a[3];int *q = (int *)malloc(3 * sizeof(int)); //相当于q[3]//动态分配一个数组,每个元素都是char * //char *buf[3]int n = 3;char **buf = (char **)malloc(n * sizeof(char *)); //相当于 char *buf[3]if (buf = = NULL){ return -1;} for (i = 0; i < n; i++) { buf[i] = (char )malloc(30 sizeof(char)); }
    char **myArray = NULL;char **getMem(int num); //手工打造二维数组void printMyArray(char **myArray, int num);void sortMyArray(char **myArray, int num);void arrayFree(char **myArray, int num);第三种内存模型:
    char **getMem(int n){ int i = 0; char **buf = (char **)malloc(n * sizeof(char *)); //char *buf[3] if (buf == NULL) { return NULL; } for (i = 0; i < n; i++) { buf[i] = (char *)malloc(30 * sizeof(char)); char str[30]; sprintf(str, "test%d%d", i, i); strcpy(buf[i], str); } return buf;}void print_buf(char **buf, int n){ int i = 0; for (i = 0; i < n; i++) { printf("%s, ", buf[i]); } printf("\n");}void free_buf(char **buf, int n){ int i = 0; for (i = 0; i < n; i++) { free(buf[i]); buf[i] = NULL; } if (buf != NULL) { free(buf); buf = NULL; }}int main(void){ char **buf = NULL; int n = 3; buf = getMem(n); if (buf == NULL) { printf("getMem err\n"); return -1; } print_buf(buf, n); free_buf(buf, n); buf = NULL; printf("\n"); system("pause"); return 0;}1、一维数组的初始化 int a[] = { 1, 3, 5 }; //3个元素 int b[5] = { 1, 2, 3 }; //a[3], a[4]自动初始化为0 int c[10] = { 0 }; //全部元素初始化为0 memset(c, 0, sizeof(c)); //通过memset给数组每个元素赋值为0
    2、数组类型 int a[] = { 1, 3, 5 }; //3个元素 a: 数组首行首元素地址,一级指针 &a: 整个数组的首地址,二级指针
    首行首元素地址和首行(整个一维数组)地址值虽然是一样,但是它们的步长不一样a+1: 跳过1元素,一元素为4字节,步长4个字节&a+1: 跳过整个数组,整个数组长度为 3*4 = 12,步长 3 * 4 = 12sizeof(a): 传参为:数组首行首元素地址,测数组(int [3])的长度,3 * 4 = 12sizeof(a[0]): 传参为:数组首元素(不是地址),每个元素为int类, 4字节sizeof(&a):传参为:一维数组整个数组的地址(首行地址),编译器当做指针类型,4字节(重要)首行地址 --> 首行首元素地址(加*)&a:首行地址*&a -> a : 首行首元素地址//数组也是一种数据类型,类型本质:固定大小内存块别名//由元素类型和内存大小(元素个数)共同决定 int a[5] int[5]//可以通过typedef定义数组类型//有typedef是类型,没有typedef是变量typedef int ARRARY[5]; //定义了一个名字为ARRARY的数组类型//等价于typedef int (ARRARY)[5];//根据数组类型,定义变量//ARRARY的位置替代为d,去掉typedef,int d[5]ARRARY d; //相当于int d[5];3、指针数组(它是数组,每个元素都是指针) 1)指针数组的定义 //指针数组变量 //[]优先级比高,它是数组,每个元素都是指针(char ) char *str[] = { “111”, “2222222” };
    char **str = { "111", "2222222" }; //err2)指针数组做形参void fun(char *str[]);void fun(char **str); //str[] -> *str3)main函数的指针数组//argc: 传参数的个数(包含可执行程序)//argv:指针数组,指向输入的参数int main(int argc, char *argv[]);: demo.exe a b testint argc = 4char *argv[] = {"demo.exe", "a", "b", "test"}4、数组指针变量(它是指针变量,指向数组的指针) //定义数组变量 int a[10]; //有typedef:类型 //没有typedef:变量 1、根据数组类型,定义指针变量,数组指针变量 typedef int ARRARY[10]; //定义了一个名字为ARRARY的数组类型 //等价于typedef int (ARRARY)[10];
    ARRARY *p; //数组指针变量//编译会有警告,但不会出错,因为 a 和 &a的值一样//就算p = a这样赋值,编译器内部也会自动转换为 p = &a//不建议这么做p = a; //p 指向a数组,指向一维数组的指针p = &a; //如何操作数组指针变量 pint i = 0;for (i = 0; i < 10; i++){ (*p)[i] = i + 1; //p = &a //*p -> *(&a) -> a //(*p)[i] -> a[i]}2、直接定义数组指针变量(常用)//()[]同级,从左往右看//()有*,它是一个指针,[]代表数组//指向数组的指针变量,[]中的数字代表指针+1的步长int(*p)[10]; //p 指向a数组,指向一维数组的指针p = &a;3、先定义数组指针类型,再根据类型定义指针变量(常用)//和指针数组写法很类似,多了()//()和[]优先级一样,从左往右//()有指针,它是一个指针,[]//指向数组的指针,它有typedef,所有它是一个数组指针类型//数组指针类型,加上typedeftypedef int(*Q)[10];Q p; //根据类型定义变量,p是数组指针变量p = &a; //p指向数组a5、多维数组本质 1)二维数组初始化 int a1[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; int a2[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; int a3[][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    2)内存中并不存在多维数组,多维数组在内存中都是线性存储int a[3][5] = { 0 };int *b = (int *)a;int i = 0;for(i = 0; i < 15; i++){ printf("%d ", b[i]);}printfA((int *)a,sizeof(a)/sizeof(a[0][0]));3)多维数组名//学会类比int b[5] = {0};b: 首行首元素地址, +1,跳 4 个字节&b:首行地址,+1,跳 4*5 = 20个字节//二维数组实际上就是 N 个一维数组//把二维数组第一个[]的值看做标志位,0 -> 2//第0个一维数组a[5] -> 第2个一维数组a[5]int a[3][5] = { 0 };a:二维数组首元素地址代表首行地址,相当于一维数组整个数组的地址,相当于上面的 &b,本来就是一个二级指针//(重要)首行地址 --> 首行首元素地址(加*)*a:首行首元素地址,相当于一维数组首元素地址,相当于上面的 ba + i -> &a[i]: 第i行地址//(重要)某行地址 --> 某行首元素地址(加*)*(a+i) -> *&a[i] -> a[i]: 第i行首元素地址//第i行j列元素的地址,某行首元素地址 + 偏移量*(a+i)+j -> a[i]+j -> &a[i][j]: 第i行j列元素的地址//第i行j列元素的值,第i行j列元素的地址的基础上(加 *)*(*(a+i)+j) -> a[i][j]: 第i行j列元素的值int a[3][5] = { 0 };sizeof(a): 二维数组整个数组长度,4 * 3 * 5 = 60sizeof(a[0]):a[0]为第0行首元素地址,相当于测第0行一维数组的长度:4 * 5 = 20sizeof(a[0][0]):a[0][0]为第0第0列元素(是元素,不是地址),测某个元素长度:4字节4)多维数组名,实际上是一个数组指针,指向数组的指针,步长为一行字节长度int a[3][5] = { 0 };//定义一个数组指针类型的变量int(*p)[5];//编译会有警告,但不会出错,因为 a 和 &a的值一样//但是&a代表整个二维数组的首地址//就算p = &a这样赋值,编译器内部也会自动转换为 p = a//不建议这么做p = &a;//a 本来就是第0个一维数组整个数组的地址,所以,无需加&p = a;5)二维数组做形参的三种形式//一维数组做函数参数退化为一级指针//二维数组(多维数组)做函数参数,退化为数组指针int a[3][5] = { 0 };void print_array1(int a[3][5]);//第一维的数组,可以不写//第二维必须写,代表步长,确定指针+1的步长 5*4void print_array2(int a[][5])//形参为数组指针变量,[]的数字代表步长void print_array3(int (*a)[5]);//a+1和二维数组的步长不一样//这里的步长为4//上面二维数组的步长为 5 * 4 = 20void print_array3(int **a); //err6、小结 typedef int A[10];//A:数组类型 A b;//int b[10],数组类型变量,普通变量 A *p;//数组类型定义数组指针变量
    typedef int (*P)[10];//数组指针类型P p;//数组指针变量int (*q)[10];//数组指针变量六 结构体1、结构体类型基本操作 1)结构体类型定义 //struct结构体关键字 //struct STU合起来才是类型名 //{}后面有个分号 struct Stu { char name[32]; char tile[32]; int age; char addr[50]; }; //通过typedef把struct Stu改名为Stu typedef struct Stu { int a; }Stu;
    2)结构体变量的定义//1)先定义类型,再定义变量,最常用struct Stu a;//全局变量、局部变量//2)定义类型的同时,定义变量struct _Stu{ char name[32]; char tile[32]; int age; char addr[50];}c;struct{ char name[32]; char tile[32]; int age; char addr[50];}e, f;3)结构体变量初始化//定义变量同时时初始化,通过{}struct Stu g = { "lily", "teacher", 22, "guangzhou" };4)变量和指针法操作结构体成员//变量法, 点运算符struct Stu h;strcpy(h.name, "^_^");(&h)->name//指针法, ->//结构体指针变量,没有指向空间,不能给其成员赋值struct Stu *p;p = &h;strcpy(p->name, "abc");(*p).name结构体也是一种数据类型,复合类型,自定义类型5)结构体数组
    //结构体类型 typedef struct Teacher { char name[32]; int age; }Teacher; //定义结构体数组,同时初始化 Teacher t1[2] = { { "lily", 18 }, { "lilei", 22 } }; //静态数组 Teacher t1[2] = {"lily", 18, "lilei", 22 }; int i = 0; for(i = 0; i < 2; i++) { printf(“%s, %d\n”, t1[i].name, t1[i].age);} //动态数组 Teacher *p = (Teacher *)malloc(3 * sizeof(Teacher)); if(p ==NULL) { return -1; } char buf[50]; int i; for(i = 0; i < 3; i++) { sprintf(buf,"name%d%d%d",i,i,i); strcpy(p[i].name,buf); p[i].age = 20 + i;}2、结构体赋值 //定义结构体类型是不要直接给成员赋值 //结构体只是一个类型,还没有分配空间 //只有根据其类型定义变量时,才分配空间,有空间后才能赋值 Teacher t1 = { “lily”, “teacher”, 18, “beijing” }; //相同类型的结构体变量,可以相互赋值 //把t1每个成员的内容逐一拷贝到t2对应的成员中 t1和t2没有关系 Teacher t2 = t1;
    3、结构体套指针
    1)结构体嵌套一级指针类型 typedef struct Teacher { char *name; int age; }Teacher; Teacher *p = NULL; p = (Teacher *)malloc(sizeof(Teacher)); p->name = (char *)malloc(30); strcpy(p->name,”lilei”); p->age = 22; 2)结构体嵌套二级指针类型 typedef struct Teacher { char *name; char **stu; int age; }Teacher; //1 Teacher t; //t.stu[3]; //char *t.stu[3];int n = 3; int i = 0; t.stu = (char **)malloc(n * sizeof(char *)); for(i = 0; i < n; i++) { t.stu[i] = (char *)malloc(30); strcpy(t.stu[i],”lily”); } //2 Teacher *p = NULL; //p->stu[3] p = (Teacher *)malloc(sizeof(Teacher)); //char *p->stu[3]p->stu = (char **)malloc(n * sizeof(char *));//3 Teacher *q = NULL; //Teacher *q[3] //q[i].stu[3] q = (Teacher *)malloc(sizeof(Teacher) * 3) for (i = 0; i < 3; i++) { //q[i].stu //(q+i)->stu q[i].stu = (char **)malloc(3 * sizeof(char *)); //char *stu[3] for(j = 0; j < 3; j++) { q[i].stu[j] = (char *)malloc(30); } }4、结构体做函数参数
    int getMem(Teacher **tmp, int n) { if(tmp == NULL) { return -1; } Teacher *p = (Teacher *)malloc(sizeof(Teacher) * 3); //Teacher q[3]; int i = 0; char buf[30]; for(i = 0; i < n; i++) { p[i].name = (char *)malloc(30); sprintf(buf,”name%d%d%d”, i, i, i); strycpy(p[i].name, buf); p[i].age = 20 + 2 * i;}*tmp = p;retrun 0; }5、浅拷贝和深拷贝 typedef struct Teacher { char name; int age; }Teacher; //结构体中嵌套指针,而且动态分配空间 //同类型结构体变量相互赋值 //不同结构体成员指针变量指向同一块内存 Teacher t1; t1.name = (char )malloc(30); strcpy(t1.name, “lily”); t1.age = 22;
    Teacher t2;t2 = t1;//深拷贝,人为增加内存,重新拷贝一下t2.name = (char *)malloc(30);strcpy(t2.name, t1.name);6、结构体偏移量(了解) //结构体类型定义下来,内部的成员变量的内存布局已经确定 typedef struct Teacher { char name[64]; //64 int age; //4 int id; //4 }Teacher;
    Teacher t1;Teacher *p = NULL;p = &t1;int n1 = (int)(&p->age) - (int)p; //相对于结构体首地址int n2 = (int)&((Teacher *)0)->age; //绝对0地址的偏移量7、结构体字节对齐(以空间换时间),详情请看《结构体字节对齐规则.doc》 原则1:数据成员的对齐规则(以最大的类型字节为单位)。原则2:结构体作为成员的对齐规则。 注意:

    结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。不是直接将结构体A的成员直接移动到结构体B中 原则3:收尾工作结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。 struct A{ int a; double b; float c;};struct{ char e[2]; int f; int g; short h; struct A i;}B;//对齐单位 8 个字节sizeof(B) = 40//普通成员偏移量e: 2*0 = 0f: 4*1 = 4g: 4*2 = 8h: 2*6 = 12结构体起点坐标: 8*2 = 16//结构体成员偏移量a: 16 + 4*0 = 16b: 16 + 8*1 = 24c: 16 + 4*4 = 32
    七 文件7.1 基本概念7.1.1 文件分类 普通文件:存放在硬盘中的文件 设备文件:屏幕、键盘等特殊文件
    文本文件:ASCII文件,每个字节存放一个字符的ASCII码,打开文件看到的是文本信息二进制文件:数据按其在内存中的存储形式原样存放,打开文件看到的是乱码7.1.1文件缓冲区(了解) ANSI C(标准C语言库函数)标准采用“缓冲文件系统”处理数据文件。
    写文件(设备文件除外),并不会直接写到文件中,会先放在缓冲区,默认情况下,关闭文件或缓冲区满了才写到文件。如果没有关闭文件,缓冲区也没有满,可以通过程序正常结束,或者人为刷新缓冲区fflush(fd)来把缓冲区的内容写到文件中。缓冲区了解一下即可,增加缓冲区只是为了提高效率,减少频繁交互的次数,我们写程序基本上不用关心。7.2 读写文件步骤7.2.1 打开文件 //定义文件指针 FILE *fp = NULL; fopen(“c:\demo.txt”, “w+”); //“c:\demo.txt” windows有效 //“c:/demo.txt”: 文件路径,可以是绝对路径和相对路径 //“w+”: 打开权限,读写方式打开,文件不存在则创建,写内容时,会清空原来内容再写 //“r+”:读写方式打开,文件不存在则报错 fp = fopen(“./demo.txt”, “w+”); // 45度 “c:/demo.txt” linux windows都可用 if (fp == NULL) { perror(“fopen”); //打印错误信息 return; }
    默认情况下,VS, Qt相对路径说明:1)编译代码时,相对路径相对于工程目录2)直接点击可执行程序,相对路径相对于可执行程序//”C:\\Users” windows的写法//”C:/Users” Linux,windows都支持,建议”/”c语言中有三个特殊的文件指针无需定义、打开可直接使用:stdin: 标准输入 默认为当前终端(键盘)我们使用的scanf、getchar函数默认从此终端获得数据stdout:标准输出 默认为当前终端(屏幕)我们使用的printf、puts函数默认输出信息到此终端stderr:标准出错 默认为当前终端(屏幕)当我们程序出错或者使用: perror函数时信息打印在此终端fputc('a', stdout); //stdout -> 屏幕, 打印普通信息char ch;ch = fgetc(stdin); //std -> 键盘printf("ch = %c\n", ch);//fprintf(stderr, "%c", ch ); //stderr -> 屏幕, 错误信息fputc(ch, stderr);printf 标准输出sprintf 字符输出fprintf 文件输出7.2.2 读写文件 1、输出,即为写,把buf中的内容写到指定的文件中 2、输入,即为读,把文件中的内容取出放在指定的buf
    7.2.3 关闭文件 fclose(fp);
    if(fp != NULL){ fclose(fp); fp = NULL;}
    7.3 读写文件7.3.1 库函数的学习 1)包含所需头文件 2)函数名字 3)功能 4)参数 5)返回值
    7.3.2 按照字符读写文件:fgetc()、fputc() 1)写文件 char *str = “111abcdefg12345678”; int i = 0; for (i = 0; i < (int)strlen(str); i++) { //功能:往文件fp中写str[i],一个字符一个字符写 //参数:str[i]:写入文件的字符,fp:文件指针 //返回值:成功写入文件的字符,失败:-1 fputc(str[i], fp); }
    2)读文件char ch;//feof(fp)判断文件是否到结尾,已经到结尾返回值为非0,没有到结尾返回值为0while ( !feof(fp) ) //如果文件没有结尾{ //返回值:成功读取的字符 ch = fgetc(fp); printf("%c", ch);}7.3.4 按照行读写文件:fputs()、fgets() 1)写文件 char *buf[] = { “11111111\n”, “aaaaaaaaaaaa\n”, “bbbbbbbbbbbb\n” }; //指针数组 int i = 0; for (i = 0; i < 3; i++) { //功能:往文件fp写入一行内容buf[i] //参数:buf[i]:字符串首地址,fp:文件指针 //返回值:成功:0,失败:非0 fputs(buf[i], fp); }
    2)读文件char buf[512] = {0};//从文件中读取一行内容(以"\n"作为标志),放在buf中//一次最大只能读sizeof(buf)-1,如果小于sizeof(buf)-1,则按实际大小读取//然后在字符串结尾自动加上字符‘\0’(转换为C风格字符串)//返回值:成功:读出的字符串,失败:NULLif (fgets(buf, sizeof(buf), fp) != NULL) { printf("buf = %s", buf);}7.3.5 按照块读写文件:fread()、fwirte() typedef struct Stu { char name[50]; int id; }Stu; Stu s[3];
    1)写文件//写文件,按块的方式写//s:写入文件内容的内存首地址//sizeof(Stu):块数据的大小//3:块数, 写文件数据的大小 sizeof(Stu) *3//fp:文件指针//返回值,成功写入文件的块数目,不是数据总长度int ret = fwrite(s, sizeof(Stu), 3, fp);printf("ret = %d\n", ret);2)读文件//读文件,按块的方式读//s:放文件内容的首地址//sizeof(Stu):块数据的大小//3:块数, 读文件数据的大小 sizeof(Stu) *3//fp:文件指针//返回值,成功读取文件内容的块数目,不是数据总长度int ret = fread(s, sizeof(Stu), 3, fp);printf("ret = %d\n", ret);7.3.6 按照格式化进行读写文件:fprintf()、fscanf() 1)写文件 //格式化写文件 int a = 250; int b = 10; int c = 20; //和printf()用法一样,只是printf是往屏幕(标准输出)写内容 //fprintf往指定的文件指针写内容 //返回值:成功:写入文件内容的长度,失败:负数 fprintf(fp, “Tom = %d, just like %d, it is %d”, a, b, c);
    2)读文件int a, b, c;fscanf(fp, "Tom = %d, just like %d, it is %d", &a, &b, &c);printf("a = %d, b = %d, c = %d\n", a, b, c);7.3.7 随机读写 //文件光标移动到文件结尾 //SEEK_SET:文件开头 //SEEK_CUR:文件当前位置 //SEEK_END:文件结尾 fseek(fp, 0, SEEK_END);
    //获取光标到文件开头文件的大小ftelllong size = ftell(fp);//文件光标恢复到开始位置rewind(fp);typedef struct Stu{ char name[50]; int id;}Stu;Stu tmp; //读第3个结构体//假如文件中写了三个结构体//从起点位置开始,往后跳转2个结构体的位置fseek(fp, 2*sizeof(Stu), SEEK_SET);//从结尾位置开始,往前跳转一个结构体的位置//fseek(fp, -1 * (int)sizeof(Stu), SEEK_END);int ret = 0;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);7.4 综合案例1)加密文件读写(使用别人写好的接口) 加密 解密2)配置文件读写(自定义接口)
    八 链表1、数组和链表的区别 数组:一次性分配一块连续的存储区域 优点: 随机访问元素效率高 缺点: 需要分配一块连续的存储区域(很大区域,有可能分配失败) 删除和插入某个元素效率低
    链表:现实生活中的灯笼 优点: 不需要一块连续的存储区域 删除和插入某个元素效率高 缺点: 随机访问元素效率低2、相关概念 节点:链表的每个节点实际上是一个结构体变量,节点,既有 数据域 也有 指针域 typedef struct Node { int id; //数据域 struct Node *next; //指针域 }SLIST;
    尾结点:next指针指向NULL3、结构体套结构体 typedef struct A { int b; }A; / 1)结构体可以嵌套另外一个结构体的任何类型变量 2)结构体嵌套本结构体普通变量(不可以) 本结构体的类型大小无法确定,类型本质:固定大小内存块别名 3)结构体嵌套本结构体指针变量(可以) 链表 指针变量的空间能确定,32位, 4字节, 64位, 8字节
    / typedef struct B { int a; A tmp1; //ok A p1; //ok //struct B tmp2; err struct B next; //32位, 4字节, 64位, 8字节 }B;
    4、链表的分类 1)单向带头链表和不带头链表
    2)双向链表带头链表和不带头链表
    3)双向循环链表带头链表和不带头链表
    5、链表的使用实际上是指针的拓展应用:指向指向谁,就把谁的地址赋值给指针。 typedef struct Stu { int id; //数据域 char name[100]; struct Stu *next; //指针域 }Stu;
    (1)静态链表//初始化三个结构体变量Stu s1 = { 1, "mike", NULL };Stu s2 = { 2, "lily", NULL };Stu s3 = { 3, "lilei", NULL };s1.next = &s2; //s1的next指针指向s2s2.next = &s3;s3.next = NULL; //尾结点Stu *p = &s1; while (p != NULL){ printf("id = %d, name = %s\n", p->id, p->name); //结点往后移动一位 p = p->next; //&s2}(2)动态链表//Stu *p1 = NULL;//p1 = (Stu *)malloc(sizeof(Stu));Stu *p1 = (Stu *)malloc(sizeof(Stu));Stu *p2 = (Stu *)malloc(sizeof(Stu));Stu *p3 = (Stu *)malloc(sizeof(Stu));p1->next = p2;p2->next = p3;p3->next = NULL; //尾节点Stu *tmp = p1;while(tmp != NULL){ printf("id = %d, name = %s\n", tmp->id, tmp->name); //结点往后移动一位 tmp = tmp->next;}6、链表的增、删、改、查操作
    1)单向链表基本操作#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct Node{ int id; //数据域 struct Node *next; //指针域}Node;//创建头结点//链表的头结点地址由函数值返回。Node *SListCreat(){ Node *head = NULL; //头结点作为标志,不存储有效数据 head = (Node *)malloc(sizeof(Node)); if (head == NULL) { return NULL; } //给head的成员变量赋值 head->id = -1; head->next = NULL; Node *pCur = head; Node *pNew = NULL; int data; while (1) { printf("请输入数据:"); scanf("%d", &data); if (data == -1) //输入-1,退出 { break; } //新结点动态分配空间 pNew = (Node *)malloc(sizeof(Node)); if (pNew == NULL) { //continue; break; } //给pNew成员变量赋值 pNew->id = data; pNew->next = NULL; //链表建立关系 //当前结点的next指向pNew pCur->next = pNew; //pNew下一个结点指向NULL pNew->next = NULL; //把pCur移动到pNew,pCur指向pNew pCur = pNew; } return head;}//链表的遍历int SListPrint(Node * head){ if (head == NULL) { return -1; } //取出第一个有效结点(头结点的next) Node *pCur = head->next; printf("head -> "); while (pCur != NULL) { printf("%d -> ", pCur->id); //当前结点往下移动一位,pCur指向下一个 pCur = pCur->next; } printf("NULL\n"); return 0;}//在值为x的结点前,插入值为y的结点;若值为x的结点不存在,则插在表尾。int SListNodeInsert(Node * head, int x, int y){ if (head == NULL) { return -1; } Node *pPre = head; Node *pCur = head->next; while (pCur != NULL) { if (pCur->id == x) //找到了匹配结点 { break; } //pPre指向pCur位置 pPre = pCur; pCur = pCur->next; //pCur指向下一个结点 } //2种情况 //1. 找匹配的结点,pCur为匹配结点,pPre为pCur上一个结点 //2. 没有找到匹配结点,pCur为空结点,pPre为最后一个结点 //给新结点动态分配空间 Node *pNew = (Node *)malloc(sizeof(Node)); if (pNew == NULL) { return -2; } //给pNew的成员变量赋值 pNew->id = y; pNew->next = NULL; //插入指定位置 pPre->next = pNew; //pPre下一个指向pNew pNew->next = pCur; //pNew下一个指向pCur return 0;}//删除第一个值为x的结点int SListNodeDel(Node *head, int x){ if (head == NULL) { return -1; } Node *pPre = head; Node *pCur = head->next; int flag = 0; //0没有找,1找到 while (pCur != NULL) { if (pCur->id == x) //找到了匹配结点 { //pPre的下一个指向pCur的下一个 pPre->next = pCur->next; free(pCur); pCur = NULL; flag = 1; break; } //pPre指向pCur位置 pPre = pCur; pCur = pCur->next; //pCur指向下一个结点 } if (0 == flag) { printf("没有值为%d的结点\n", x); return -2; } return 0;}//清空链表,释放所有结点int SListNodeDestroy(Node *head){ if (head == NULL) { return -1; } Node * tmp = NULL; int i = 0; while (head != NULL) { //保存head的下一个结点 tmp = head->next; free(head); head = NULL; //head指向tmp head = tmp; i++; } printf("i = %d \n", i); return 0;}//删除值为x的所有结点int SListNodeDelPro(Node *head, int x){ if (head == NULL) { return -1; } Node *pPre = head; Node *pCur = head->next; int flag = 0; //0没有找,1找到 while (pCur != NULL) { if (pCur->id == x) //找到了匹配结点 { //pPre的下一个指向pCur的下一个 pPre->next = pCur->next; free(pCur); pCur = NULL; flag = 1; pCur = pPre->next; //break; continue; //跳出本次循环,重要 } //pPre指向pCur位置 pPre = pCur; pCur = pCur->next; //pCur指向下一个结点 } if (0 == flag) { printf("没有值为%d的结点\n", x); return -2; } return 0;}//链表节点排序int SListNodeSort(Node *head){ if(head == NULL || head->next == NULL) { return -1; } Node *pPre = NULL; Node *pCur = NULL; Node tmp; // pPre->next != NULL,链表倒数第2个结点 for (pPre = head->next; pPre->next != NULL; pPre = pPre->next) { for (pCur = pPre->next; pCur != NULL; pCur = pCur->next) { //注意,排序,除了数据域需要交换,next指针还需要交换 if (pPre->id > pCur->id) //升序 { //只交换数据域 tmp.id = pCur->id; pCur->id = pPre->id; pPre->id = tmp.id; } } } return 0;}//假如原来链表是升序的,升序插入新节点//不能插入节点后再排序,是升序插入新节点xint SListNodeInsertPro(Node *head, int x){ //保证插入前是有序的 int ret = SListNodeSort(head); if (ret != 0) { return ret; } if (head == NULL) { return -1; } Node *pPre = head; Node *pCur = head->next; //1 2 3 5 6, 插入4 //3:pre, 5: cur while (pCur != NULL) { if (pCur->id > x) //找到了匹配结点 { break; } //pPre指向pCur位置 pPre = pCur; pCur = pCur->next; //pCur指向下一个结点 } //给新结点动态分配空间 Node *pNew = (Node *)malloc(sizeof(Node)); if (pNew == NULL) { return -2; } //给pNew的成员变量赋值 pNew->id = x; pNew->next = NULL; //插入指定位置 pPre->next = pNew; //pPre下一个指向pNew pNew->next = pCur; //pNew下一个指向pCur return 0; return 0;}//翻转链表的节点(不是排序,是翻转)//把链表的指向反过来int SListNodeReverse(Node *head){ if (head == NULL || head->next == NULL || head->next->next == NULL) { return -1; } Node *pPre = head->next; Node *pCur = pPre->next; pPre->next = NULL; // head->next->next = NULL; Node *tmp = NULL; while (pCur != NULL) { tmp = pCur->next; pCur->next = pPre; pPre = pCur; pCur = tmp; } //head->next->next = NULL; head->next = pPre; return 0;}int main(void){ Node *head = NULL; head = SListCreat();//创建头结点 SListPrint(head); SListNodeInsert(head, 5, 4); printf("在5的前面插入4后\n"); SListPrint(head); SListNodeDelPro(head, 5); printf("删除所有5结点后\n"); SListPrint(head); SListNodeSort(head); printf("排序后\n"); SListPrint(head); SListNodeInsertPro(head, 6); printf("升序插入6后\n"); SListPrint(head); SListNodeReverse(head); printf("链表翻转后\n"); SListPrint(head); SListNodeDestroy(head); head = NULL; printf("\n"); system("pause"); return 0;}九 函数指针1、指针函数,它是函数,返回指针类型的函数 //指针函数 //()优先级比高,它是函数,返回值是指针类型的函数 //返回指针类型的函数 int fun() { int p = (int )malloc(sizeof(int)); return p; }
    2、函数指针,它是指针,指向函数的指针,(对比数组指针的用法) 一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址。
    函数指针变量,它也是变量,和int a变量的本质是一样的。int fun(int a){ printf("a ========== %d\n", a); return 0;}//定义函数指针变量有3种方式:(1)先定义函数类型,根据类型定义指针变量(不常用)//有typedef是类型,没有是变量typedef int FUN(int a); //FUN是函数类型,类型模式为: int fun(int);FUN *p1 = NULL; //函数指针变量p1 = fun; //p1 指向 fun 函数fun(5); //传统调用p1(6); //函数指针变量调用方式(2)先定义函数指针类型,根据类型定义指针变量(常用)//()()优先级相同,从左往右看//第一个()代表指针,所以,它是指针//第二个括号代表函数,指向函数的指针typedef int(*PFUN)(int a); //PFUN是函数指针类型PFUN p2 = fun; //p2 指向 funp2(7);(3)直接定义函数指针变量(常用)int(*p3)(int a) = fun;p3(8);int(*p4)(int a);p4 = fun;p4(9);3、函数指针数组,它是数组,每个元素都是函数指针类型 void add() {} void minus() {} void multi() {} void divide() {} void myexit() {}
    //函数指针变量,fun1指向add()函数void(*fun1)() = add;fun1(); //调用add()函数//函数指针数组void(*fun[5])() = { add, minus, multi, divide, myexit };//指针数组char *buf[] = { "add", "min", "mul", "div", "exit" };char cmd[100];int i = 0;while (1){ printf("请输入指令:"); scanf("%s", cmd); for (i = 0; i < 5; i++) { if (strcmp(cmd, buf[i]) == 0) { fun[i](); break; //跳出for()循环,最近的循环 } }}4、回调函数,函数的形参为:函数指针变量 int add(int a, int b) { return a + b; }
    int minus(int a, int b){ return a - b;}//int(*p)(int a, int b), p 为函数指针变量//框架,固定变量//多态,多种形式,调用同一种接口,不一样表现void fun(int x, int y, int(*p)(int a, int b) ){ int a = p(x, y); //回调函数 printf("a = %d\n", a);}typedef int(*Q)(int a, int b); //Q 为函数指针类型void fun2(int x, int y, Q p)//p 为函数指针变量{ int a = p(x, y); //回调函数 printf("a = %d\n", a);}//fun()函数的调用方式fun(1, 2, add);fun2(10, 5, minus);5、函数的递归 递归:函数可以调用函数本身(不要用main()调用main(),不是不行,是没有这么做,往往得不到你想要的结果)
    (1)普通函数调用(栈结构,先进后出,先调用,后结束)void funB(int b){ printf("b = %d\n", b); return;}void funA(int a){ funB(a-1); printf("a = %d\n", a);}调用流程:funA(2) -> funB(1) -> printf(b) (离开funB(),回到funA()函数)-> printf(a)(2)函数递归调用(调用流程和上面是一样,换种模式,都是函数的调用而已)void fun(int a){ if(a == 1) { printf("a == %d\n", a); return; //中断函数很重要 } fun(a-1); printf("a = %d\n", a);}fun(2);(3)递归实现累加 1+2+3+……+100#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <string.h>int fun(int n){ if (n == 1) { return n; } else { return fun(n - 1) + n; }}int main(void){ int i = 0; int sum = 0; for (i = 1; i <= 100; i++) { sum += i; } printf("sum1 = %d\n", sum); sum = fun(100); printf("\nsum2 = %d\n", sum); printf("\n"); system("pause"); return 0;}(4)函数递归字符串反转十 预处理1、C编译器提供的预处理功能主要有以下四种: 1)文件包含 #include 2)宏定义 #define 3)条件编译 #if #endif .. 4)一些特殊作用的预定义宏
    2、#include< > 与 #include “”的区别“”表示系统先在file1.c所在的当前目录找file1.h,如果找不到,再按系统指定的目录检索。< >表示系统直接按系统指定的目录检索。注意:
    1. #include <>常用于包含库函数的头文件2. #include ""常用于包含自定义的头文件3. 理论上#include可以包含任意格式的文件(.c .h等) ,但我们一般用于头文件的包含。3、宏定义
    #define 宏名 字符串#define PI 3.14#define TEST(a,b) (a)*(b)宏的作用域取消宏定义#undef 宏名4、宏定义函数
    #define MAX2(a,b) (a) > (b) ? (a) : (b)#define MAX3(a,b,c) (a) > (MAX2(b,c)) ? (a) : (MAX2(b,c))5、条件编译 防止头文件被重复包含引用//#pragma once
    //_FUN_H_ 自定义宏,每个头文件的宏都不一样//假如test.h->_TEST_H_#ifndef _FUN_H_#define _FUN_H_ //函数的声明 //宏定义 //结构体#endif //!_FUN_H_6、动态库的封装和使用 socketclient
    7、日志打印 FILE LINE
    8、内存泄漏检查 memwatch
    0  留言 2019-07-30 16:00:39
  • C语言学习笔记

    1 愉快的开始hello world1.1 include头文件包含include是要告诉编译器,包含一个头文件;
    在C语言当中,任何库函数调用都需要提前包含头文件;

    <头文件>,代表让C语言编译器去系统目录下寻找相关的头文件;
    “头文件”, 代表让C语言编译器去用户当前目录下寻找相关的头文件;

    如果是使用了一个C语言库函数需要的头文件,那么一定是#include < >;
    如果是使用了一个自定义的h文件,那么一定是#include“ ”。
    1.2 main函数main函数是C语言中的主函数,一个C语言的程序必须有一个主函数,也只能有一个主函数。
    int main() //一个函数在C语言里面,如果只是空(),代表这个函数可以有参数,也可以没有参数int main(void) //如果是(void),就是明确的表达没有任何参数1.3 注释
    // 单行注释,代表注释,就是一个文字说明,没有实质的意义,单行注释是C++语言的注释方法;
    / / 多行注释,多行注释是标准C语言的注释方法。

    1.4 {}括号,程序题和代码块C语言所有的函数的代码都是在{}里包着的
    1.5 声明int a;
    声明一个变量名字叫a,对于C语言,变量的名称是可以自定义的
    1.6 C语言自定义名字的要求可以使用大小写字母,下划线,数字,但第一个字母必须是字母或者下划线(Linux命名法、匈牙利命名法)

    字母区分大小写
    不能用C语言的关键字作为变量名称
    每一行,必须是;结尾

    1.7 printf函数printf是向标准输出设备输出字符串的如果要输出一个字符串:例如:printf(“hello world”);如果要输出一个整数,例如:printf(“%d”,整数);Printf(“\n”);会输出一个回车换行
    1.8 return语句一个函数遇到return语句就终止了,return是C语言的关键字
    1.9 System系统调用System库函数的功能是执行操作系统的命令或者运行指定的程序
    2 C语言中的数据类型2.1 常量常量就是在程序中不可变化的量,常量在定义的时候必须给一个初值。
    2.1.1 #define#define MAX 10 //定义一个宏常量2.1.2 constConst int a=20; //定义一个const常量
    2.2 字符串常量#define STRING “hello world\n”对于#define类型的常量,C语言的习惯是常量名称为大写,但对于普通const常量以及变量,一般为小写结合大写的方式
    2.3 二进制数、位、字节与字我们习惯于十进制的数:10,12等

    一个位只能表示0,或者1两种状态,简称bit
    一个字节为8个二进制,称为8位,简称BYTE,8个比特是一个字节
    一个字位2个字节,简称WORD
    两个字为双字,简称DWORD

    2.4 八进制八进制为以8为基数的数制系统,C语言当中表示八进制0
    2.5 十六进制
    十六进制值16为基数的数制系统,C语言当中用0x表示十六进制
    十进制转化为八进制,用十进制数作为被除数,8作为除数,取商数和余数,直到商数为0的时候,将余数倒过来就是转化后的结果
    十进制转化为十六进制,用十进制数作为被除数,16作为除数,取商数和余数,直到商数为0的时候,将余数倒过来就是转化后的结果

    2.6 原码将最高位作为符号位(0代表正,1代表负),其余各位代表数值本身的绝对值
    2.7 反码
    一个数如果值为正,那么反码和原码相同
    一个数如果为负,那么符号位为1,其他各位与原码相反

    2.8 补码
    正数:原码、反码和补码都相同
    负数:最高位为1,其余各位原码取反,最后对整个数+1
    补码符号位不动,其他位求反,最后整个数+1,得到原码
    用补码进行运算,减法可以通过加法实现

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

    2.10 int类型2.10.1 int常量,变量int就是32位的一个二进制整数,在内存当中占据4个字节的空间
    2.10.2 printf输出int值
    %d,输出一个有符号的10进制整数
    %u,代表输出一个无符号的10进制整数

    2.10.3 printf输出八进制和十六进制
    %o,代表输出八进制整数
    %x,代表输出16进制整数
    %X,用大写字母方式输出16进制数

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

    2.10.5 整数溢出计算一个整数的时候超过整数能够容纳的最大单位后,整数会溢出,溢出的结果是高位舍弃。当一个小的整数赋值给大的整数,符号位不会丢失,会继承。
    2.10.6 大端对齐小端对齐对于ARM,intel这种x86构架的复杂指令CPU,整数在内存中是倒着存放的,低地址放地位,高地址放高位,小端对齐;
    但对于unix服务器的CPU,更多是采用大端对齐的方式存放整数。
    2.11 char类型2.11.1 char常量,变量
    Char c; //定义一个char变量
    ‘a’,char的常量
    char的本质就是一个整数,一个只有1个字节大小的整数

    2.11.2 printf输出char%c意思是输出一个字符,而不是一个整数
    2.11.3 不可打印char转义符\a 警报\b 退格\n 换行\r 回车\t 制表符\\ 斜杠\’ 单引号\” 双引号\?问号2.11.4 char和unsigned char
    Char取值范围为-128到127
    Unsigned char为0-255

    2.12 浮点float,double,long double类型2.12.1 浮点常量,变量
    Float在32位系统下是4个字节,double在32位系统下是8个字节
    小数的效率很低,避免使用,除非明确的要计算一个小数。

    2.12.2 printf输出浮点数
    %f是输出一个double
    %lf输出一个long double

    2.13 类型限定2.13.1 constConst是代表一个不能改变值的常量
    2.13.2 volatile代表变量是一个可能被CPU指令之外的地方改变的,编译器就不会针对这个变量去优化目标代码。
    2.13.3 register变量在CPU寄存器里面,而不是在内存里面,但register是建议型的指令,而不是命令型的指令。
    3 字符串格式化输出和输入3.1 字符串在计算机内部的存储方式字符串是内存中一段连续的char空间,以‘\0’结尾
    “”是C语言表达字符串的方式
    3.2 printf函数,putchar函数Printf格式字符
    字符 对应数据类型 含义d int 接受整数值并将它表示为有符号的十进制整数hd short int 短整数hu unsigned shor int 无符号短整数o unsigned int 无符号8进制整数u unsigned int 无符号10进制整数x/X unsigned int 无符号16进制整数,x对应的是abcdf,X对应的是ABCDEFf float或double 单精度浮点数或双精度浮点数e/E double 科学计数法表示的数,此处“e”的大小写代表在输出是用“e”的大小写c char 字符型,可以把输入的数字按照ASC11码相应转换为对应的字符s/S char */wchar_t * 字符串,输出字符串中的字符直至字符串的空字符(字符串以‘\0’结尾,这个‘\0’即空字符)p void 以16进制形式输出指针% % 输出一个百分号Printf附加格式
    字符 含义| 附加在d,u,x,o前面,表示长整数- 左对齐m(代表一个整数) 数据最小宽度0 将输出的前面补上0,直到占满指定列宽为止(不可以搭 配使用“-”)N(代表一个整数) 宽度至少为n位,不够以空格填充。Putchar是显式一个字符的函数
    long l = 100;Printf(“-6ld”,l);3.3 scanf函数与getchar函数Scanf通过键盘读取用户输入,放入变量中,记得参数一定是变量的地址(&)
    // #define _CRT_SECURE_NO_WARNINGS#pragma warning(disable:4996)Int a=0;Scanf(“%d”,&a);//一定要用&取变量的地址getchar得到用户键盘输入的字符char a = 0;a = getchar();//得到用户键盘的按键printf(“%c”,a);printf(“please input a:”);scanf(“%d”,&a);getchar(); //通过getchar这个函数将之前输入a时候用户按的回车键先收到4 运算符表达式和语句4.1 基本运算符4.1.1 =
    数据对象:泛指数据在内存的存储区域
    左值:表示可以被更改的数据对象
    右值:能赋给左值的量

    4.1.2 +加
    4.1.3 –减
    4.1.4 *乘
    4.1.5 /除
    4.1.6 %取余数
    4.1.7 + =加等于

    a+=5;
    4.1.8 - =减等于
    4.1.9 * =乘等于
    4.1.10 /=除等于
    4.1.11 %=取余等于
    4.1.12 ++自加1

    i++先计算表达式的值,然后再++
    ++i是先++,再计算表达式的值

    4.1.13 —自减1
    4.1.14 逗号运算符int a = 2;int b = 3;int c = 4;int d = 5;int I = (a = b, c + d);逗号表达式先求逗号左边的值,然后求右边的值,整个语句的值是逗号右边的值。
    4.1.15 运算符优先级优先级 运算符 结合性 1 ++(后缀),--(后缀),()(调试函数), 从左到右 {}(语句块),-> 2 ++(前缀),--(前缀),+(前缀),-(前缀), 从右到左 ! (前缀),~(前缀),sizeof,*(取指针值), &(取地址),(type)(类型转化) 3 *,/,% 从左到右 4 +,- 从左到右 5 <<,>> 从左到右 6 < ,>,<=,>= 从左到右 7 = =,!= 从左到右 8 & 从左到右 9 ^ 从左到右 10 | 从左到右 11 && 从左到右 12 || 从左到右 13 ? 从右到左 14 =,*=,%=,+=,-=,<<=,>>=,&=, 从右到左 |=,^= 15 ,(逗号运算符) 从左到右4.2 复合语句{}代码块
    for(i = 0; i < 3; i++) //循环语句,代表复合语句内部的代码要执行3次{ printf(“hello\n”);}4.3 空语句只有一个;号的语句就是空语句,空语句在C语言里面是合法的,并且是在某些场合必用的
    4.4 类型转化double f = (double) 3 / 2; //(double)3意思是将整数3强制转化为double型 ()为强制类型转化运算符double f = 3 /2; //C语言两个整数相除的结果自动转化为一个整数double f = 3.0 / 2;5 条件分支语句5.1 关系运算符在C语言中0代表false,非0代表真
    5.1.1 <小于
    5.1.2 <=小于等于
    5.1.3 >大于
    5.1.4 >=大于等于
    5.1.5 = =等于
    5.1.6 !=不等于
    5.2 关系运算符优先级前四种相同,后两种相同,前四种高于后两种优先级
    5.3 逻辑运算符5.3.1 &&与
    当运算符左右都是真的时候,那么整个表达式的结果为真;只有左右有一个值为假,那么整个表达式的结果为假。
    5.3.2 | |或
    当运算符左右只要有一个值是真的时候,那么整个表达式的结果为真;除非左右两个值都是假,那么整个表达式的结果为假。
    5.3.3 !非
    当值为真的时候,表达式为假;当值为假的时候,表达式为真。
    5.4 if单分支
    if(条件){ //复合语句}当条件是真的时候,复合语句才能被执行,如果条件为假的时候,复合语句不执行
    5.5 if else双分支
    if(条件){ 复合语句1;}else{ 复合语句2;}如果条件为真,那么执行复合语句1,否则执行复合语句2
    5.6 if else if多重if
    if(条件1){ 复合语句1;}else if(条件2){ 复合语句2;}else if(条件3){ 复合语句3;}else { 复合语句4;}当有多个else的时候,else总是和上方最近的那个if语句配对
    5.7 switch与break,default多重选择
    switch(i){case 0: break; //跳出switch的复合语句块……default: //如果所有条件都不满足,那么执行default语句}什么时候用if,什么时候用switch?当条件很复杂,一个条件有&&,||,!存在,那么用if语句如果条件很简单,但分支很多,那么适合用switch
    5.8 条件运算符?一个求绝对值的例子
    int i = -8;int x = (i < 0) ? –i : i;先求?左边的条件,如果条件为真,那么等于:左边的值,否则等于:右边的值一个求最大值的例子
    int c = ( a > b ) ? a : b ;5.9 goto语句与标号无条件跳转goto
    goto end; //无条件的跳转到一个标号去执行……end://标号不建议使用goto语句,goto语句会使你的程序可读性变得很差
    6 循环语句6.1 whilewhile(条件),如果条件为真,循环继续,条件为假,循环结束
    while(1) //是死循环的写法{ 复合语句;}6.2 continue循环遇到continue语句,不再执行continue下面代码,而是直接返回到循环起始语句处继续执行循环
    6.3 break循环遇到break语句,立刻中断循环,循环结束
    6.4 do whiledo{ 复合语句;}while(条件);对于do while来讲,循环的复合语句至少可以被执行一次对于while来讲,有可能复合语句一次执行的机会都没有
    6.5 for
    先执行i=0,对于一个for循环,第一步只执行一次;
    判断i是否小于10,如果i小于10,那么循环继续,否则循环中断
    i++,第一次执行for的时候,不执行i++

    for(int i = 0 ; i < 10 ; i++){ 复合语句;}等同于:
    int = 0;while(i < 10){ i++;}6.6 循环嵌套打印三角形
    * *** ***** ****************int main(){ int i,j; for(i=1;i<7;i++) { for(j=1;j<7-i;j++) { printf(“ ”);}for(j=0;j<(i*2-1);j++){ printf(“*”);}printf(“\n”);}return 0;}7 数组数组的本质就是可以一次定义多个类型相同的变量,同时一个数组中所有的元素在内存中都是顺序存放的。但要记得在C语言中如果定义了如下数组:
    Char s[100] ;//s[0] – s[99],切记没有s[100]这个元素,而且C语言编译器不会帮你检查数组的下标是否有效。Char array[2][3][4] = {};//原则,数组维数越多,代码的可读性就越差,所以要尽可能的用维数少的数组7.1 一维数组定义与使用int array [10]; //定义一个一维数组,名字叫array,一共有10个元素,每个元素都是int类型的array[0] = 20 ;array[1] = 30 ;array[9] = 90 ;//array[10] = 100 ; //错误,没有array[10]这个元素7.2 数组在内存的存储的方式数组在内存中就是一段连续的空间,每个元素的类型是一样的
    7.3 一维数组初始化int array[10] = {1,2,3,4,5,6,7,8,9,10} ;//定义数组的同时为数组的成员初始化值int array[10] = {3,4,5} ;//将数组的前三个元素赋值,其余元素置为0int array[10] = {0} ;//将数组所有的元素都置为0int i;for (i = 0; i < 10; i++){ array[i] = 0 ;//通过循环遍历数组的每个元素,将元素的值置为0 //scanf(“%d”,&array[i]);}求数组中最大元素的值
    int main(){ int array[10] = {32,5,67,98,12,54,8,78,457,10};int max = 0;int i;for (i = 0; i < 10; i++) //想找最大的值,一定要把数组先遍历一遍{ if(max < array[i]) max = array[i]; } printf(“max = %d\n”,max); return 0;}求数组中最小元素的值,和最小值对应的数组下标
    int main(){ int array[10] = {32,5,67,98,12,54,8,78,457,10};int min = array[0];int index = 0; //在没有遍历数组之前,默认数组的第0号元素就是最小的元素int i;for (i = 1; i < 10; i++) //想找最小的值,一定要把数组先遍历一遍{ if(min > array[i]) { index = i; min = array[i]; } } printf(“min = %d index = %d\n”, min , index); return 0;}求数组中所有元素的和
    int main(){ int array[10] = {1,2,3,4,5,6,7,8,9,10};int i;int sum = 0;//存放数组和的变量for (i = 0; i < 10; i++) //想找最大的值,一定要把数组先遍历一遍{ sum += array[i]; } printf(“sum = %d\n”,sum); return 0;}将数组元素逆置
    int main(){ int array[10] = {32,5,67,98,12,54,8,78,457,10};/*int tmp = array[1]; //中间变量实现两个值的互换array[1] = array[0];array[0] = tmp;*/int min = 0; //数组最小下标int max = 9; //数组最大下标while (min < max) //两头往中间堵{ int tmp = array[min]; array[min] = array[max]; array[max] = tmp; min++; max--;} printf(“max = %d min = %d\n”, max , min); return 0;}求100到999之间的水仙花数
    int main(){ int i; for(i = 100 ;i < 1000 ;i++) { Int i1=i%10; // Int i2=i/10%10; // Int i3=i/100; // If((i1*i1*i1+i2*i2*i2+i3*i3*i3) = = i) Printf(“%d\n”,i);}return 0;}求一个int数组中,所有奇数元素的和
    int main(){ int array[10] = {1,2,3,4,5,6,7,8,9,10};int i;int sum = 0;for (i = 0; i < 10; i++) { if((array[i]%2) = = 1) { sum += array[i]; } } printf(“sum = %d\n”,sum); return 0;}求从3到100之间所有素数打印出来 3 5 7 11 13 17 ……
    int main(){int i; //素数是除了1和自己以外,不能被其他整数整除的整数for (i = 3; i < 100; i++) { int j; int ststus = 0; for(j =2 ;j < i ; j++) //判断i是否为素数 { if((i %j) = = 0) { status = 1; break;} } if(status= = 0) //代表这是个素数 { printf(“%d\n”,i);} } return 0;}7.4 二维数组定义与使用int array[2][3];//定义了一个二维数组,有两个array[3]int array[2][3] = { {1,2,3},{4,5,6} };//定义一个二维数组的同时初始化成员7.5 二维数组初始化int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};int array[2][3] = {0};//将二维数组中每个元素的值都初始化为0 int array[2][3] = { {1,2,3},{4,5,6} }; int i,j; for(j=0;j<3;j++) { int sum=0; for(i=0;i<2;i++) { sum += array[i][j]; } printf("%d\n",sum);//打印列的和 }#include<stdio.h>int main()//冒泡排序{ int array[10] = {34,14,8,54,23,89,56,4,45,22}; int i; int j; for(i = 0;i < 10; i++) { for(j = 1; j < 10 - i; j++) { if(array[j-1] > array[j])//如果前面的元素大于后面的元素,那么就让这两个元素交换位置 { int tmp = array[j]; array[j] = array[j - 1]; array[j-1] = tmp; } } } for(i=0;i<10;i++) { printf("array[%d]=%d\n",i,array[i]); } return 0;}8 字符串与字符数组字符串一定是在内存中以0结尾的一个char数组。
    8.1 字符数组定义char array[100];8.2 字符数组初始化char array[100] = {'a','b','c','d'};char array[100] = ”abcd”;char array[100] = {0};char array[] = “abcd”;8.3 字符数组使用#include<stdio.h>//字符串倒序int main(){ char buf[100] = "hello world"; int len = 0; while (buf[len++]); len--; int i = 0; int j = len -1; while (i < j) { char tmp = buf[i]; buf[i] = buf[j]; buf[j] = tmp; i ++; j --; } printf("%s\n",buf); return 0;}ASCII一个字节存放一个字符。GBK用两个字节存放一个汉字。
    8.4 随机数产生函数rand与srand头文件stdilb.hrand是伪随机数产生器,每次调用rand产生的随机数是一样的。如果调用rand之前先调用srand就出现任意的随机数。只要能保证每次调用srand函数的时候,参数的值是不同的,那么rand函数就一定会产生不同的随机数。
    Srand() //随机数种子发生器Rand() //随机数产生器#include<stdio.h>#include<time.h>#include<stdlib.h>int main(){ int t = (int)time(NULL); srand(t); for(int i=0; i<10; i++) { printf("%d\n",rand()); } return 0;}8.5 用scanf输入字符串“%s”的作用就是输入一个字符串的,scanf是以回车键作为输入完成标志的,但回车键本身并不会作为字符串的一部分如果scanf参数中的数组长度小于用户在键盘输入的长度,那么scanf就会缓冲区溢出,导致程序崩溃。
    8.6 字符串的结束标志scanf将回车、空格都认为是字符串输入结束标志。
    8.7 字符串处理函数8.7.1 getsgets(s);Gets认为回车是输入结束的标志,空格不是输入结束的标志,所以用gets这个函数就可以实现输入带空格的字符串,但是gets和scanf一样存在缓冲区溢出的问题。
    Gets不能用类似“%s”或者“%d”之类的字符转义,只能接受字符串的输入。
    8.7.2 fgets函数gets函数不检查预留缓冲区是否能够容纳用户实际输入的数据。多出来的字符会导致内存溢出,fgets函数改进了这个问题。由于fgets函数是为读取文件设计的,所以读取键盘时没有gets那么方便。
    Char s[100] = {0};fgets(s ,sizeof(s) ,stdin);//第一个参数是char的数组,第二个参数是数组的大小,单位:字节,第三个参数stdin代表标准输入的意思fgets是安全的,不存在缓冲区溢出的问题,调用fgets的时候,只要能保证第二个参数小于等于数组实际的大小,那么就可以避免缓冲区溢出的。
    8.7.3 puts函数Puts函数打印字符串,与printf不同,puts会在最后自动添加一个’\n’
    Char s[] = “hello world”;Puts(s);8.7.4 fputs函数fputs是puts的文本操作版本
    char s[] = “hello world”;fputs(s,stdout);8.7.5 strlen,字符串长度size_t strlen(const char * _str);返回不包含字符串结尾’\n’的字符串长度
    Char s[100] = “hello world”;Int len = strlen(s);Printf(“len = %d\n”,len);8.7.6 strcat,字符串追加size_t strcat(char * _str1,const char * _str2);将参数_str2追加到_str1后尾
    Char s1[100] = “fsfgg”;Strcat(s,s1);Strcat也存在缓冲区溢出的问题
    8.7.7 strncat,字符串有限追加size_t strncat(char * _str1,const char * _str2,size_t len);Strcat(s,s1,3); //合并的时候可以限制追加多少个字符8.7.8 strcmp,字符串比较int strcmp(const char * _str1,const char * _str2);比较两个字符串是否相等,相等返回0,不相等返回非0
    If(strcmp(s1,s2)) = = 0){ Printf(“相同\n”);}8.7.9 strncmp,字符串有限比较If(strncmp(s1,s2,5) = = 0) //strncmp的意思是只比较指定数量的字符8.7.10 strcpy字符串拷贝Char *strcpy(char * _str1,const char * _str2);将参数_str2拷贝到参数_str1中
    Strcpy(s1,s2);8.7.11 strncpy字符串有限拷贝Strncpy(s1,s2,3);8.7.12 sprintf格式化字符串和printf函数功能类似,printf函数将格式化结果输出到屏幕,sprintf将格式化结果输出到字符串。
    int i = 200;char s[100] = {0};sprintf(s, “I = %d”,i);8.7.13 Sscanf函数Sscanf类似于scanf函数,scanf从键盘读取用户输入,scanf从指定格式化字符串读取输入。
    8.7.14 strchr查找字符Char * strchr(char * _Str, int _Ch);在参数_str中查找参数_ch指定字符,找到返回字符_ch在_str中所在位置,没有找到返回NULL;
    8.7.15 strstr查找子串Char * strstr(char * _Str,const char * _SubStr);在参数_str中查找参数_SubStr指定子串,找到返回子串在_Str中所在位置,没有找到返回NULL;
    8.7.16 strtok分割字符串字符在第一次调用时strtok()必需给予参数s字符串,往后的调用则将参数s设置成NULL每次调用成功则返回指向被分割出片段的指针。
    如果strtok没有找到指定的分割符号,那么返回NULL
    Char buf [] = “abcd@efg@h”;Char *p = strtok(buf,”@”);While(p){ Printf(“%s\n”,p); P = strtok(NULL,”@”);}8.7.17 atoi转化为intint i1 = atoi(a); //将字符串转化为一个整数需要包含头文件stdlib.h
    8.7.18 atof转化为float8.7.19 atol转化为long#include <string.h> //计算字符串int calc_string(const char *s){ char buf1[100] = {0}; char oper1 = 0; char buf2[100] = {0}; int len = strlen(s);//得到字符串的长度 int i; int start; for(i=0;i<len;i++) { if(s[i] == '+'|| s[i] == '-' || s[i] == '*' || s[i] == '/') { strncpy(buf1, s, i); oper1 = s[i]; break; } } start = i + 1; for(;i<len;i++) { if(s[i] == '=') { strncpy(buf2,&s[start],i-start); } } printf("buf1 = %s,oper1 = %c,buf2 = %s\n",buf1,oper1,buf2); switch(oper1) { case '+': return atoi(buf1) + atoi(buf2); case '-': return atoi(buf1) - atoi(buf2); case '*': return atoi(buf1) * atoi(buf2); case '/': { int a = atoi(buf2); if(a) return atoi(buf1) / atoi(buf2); else return 0; } }}int main(){ const char *s = "32 + 56 ="; printf("%d\n",calc_string(s)); return 0;}9 函数9.1 函数的原型和调用在使用函数前必须定义或者声明函数
    9.2 函数的形参与实参在调用函数的时候,函数大多数都有参数,主调函数和被调用函数之间需要传递数据。在定义函数时函数名后面括弧中的变量名称为“形式参数”,简称形参。在调用函数时,函数名后面括号中的变量或表达式称为“实际参数”,简称实参。

    形参在未出现函数调用时,他们并不占用内存单元,只有在发生函数调用的时候形参才被分配内存,函数调用完成后,形参所占的内存被释放;
    实参可以是变量,常量或者表达式;
    在定义函数时,一定要指定形参的数据类型;
    形参与实参的数据类型一定要可兼容;
    在C语言中,实参与形参的数据传递是“值传递”,即单向传递,只由实参传递给形参,而不能由形参传递给实参。

    如果函数的参数是个数组,那么是可以通过形参修改实参的值的
    9.3 函数的返回类型与返回值
    函数的返回值通过函数中的return获得,如果函数的返回值为void可以不需要return语句;
    函数return语句中的返回值数据类型应该与函数定义时相同;
    如果函数中没有return语句,那么函数将返回一个不确定的值。

    如果C语言一个函数没有明确的标明函数的返回类型,那么函数的返回类型就是int;如果一个函数没有返回值,那么函数的返回类型是void;
    9.4 main函数与exit函数与函数的return语句exit(0); //在子函数中调用exit同样代表程序终止,但在子函数中调用return只是子函数终止,程序正常执行。exit是C语言的库函数,调用exit的结果就是程序终止,在main函数中调用exit与调用return是一样的;
    main函数return代表程序终止。
    9.5 多个源代码文件程序的编译9.5.1 头文件的使用如果把main函数放在第一个文件中,而把自定义函数放在第二个文件中,那么就需要在第一个文件中声明函数原型。如果把函数原型包含在一个头文件里,那么就不必每次使用函数的时候都声明其原型了。把函数声明放入头文件是很好的习惯。
    9.5.2 #include与#define的意义#include就是简单的文件内容替换#define就是简单的文件替换而已9.5.3 #ifndef 与#endif在头文件.h中,
    #ifndef _宏名_#define _宏名_//具体宏的名字是自定义的//函数的声明#endif作用:防止多次include的同一个头文件的时候,重复预编译头文件内容防止头文件被重复包含#ifndef的意思就是条件预编译,如果#ifndef后面的条件成立,那么就预编译从#ifndef开始到#endif之间的代码,否则不会去预编译这段代码。在#ifndef中的宏,一定要大写和下划线,必要的时候加数字,目的是为了避免和其他头文件中的宏名字冲突。#ifdef,#ifndef叫条件编译语句;#ifdef 宏,如果宏被定义了,那么编译语句;#ifndef 宏,如果宏被定义了,那么就不编译语句。9.6 函数的递归函数可以调用自己,这就叫函数的递归。
    #include <stdio.h>void test(int n){ if(n > 0) { n --; printf("先序n = %d\n",n);//先序递归,如果是先序递归,那么代码是顺序执行的 test(n);//函数自己调用自己,就叫函数的递归 printf("后序n = %d\n",n);//后序递归,如果是后序递归,那么代码是逆序执行的 }}int main(){ int i = 3; test(i); return 0;}9.6.1 递归的过程分析案例:将十进制转换为二进制
    #include <stdio.h>void test(int n){ int i = n % 2; printf("%d\n",i); if(n > 0) { test(n / 2); }}int main(){ int i = 11; test(i); return 0;}斐波那契数列例子:斐波那契数列指的是这样一个数列0,1,1,2,3,5,8,13,21,34,55,89,144,…第0项是0,第1项是第一个1;这个数列从第2项开始,每一项都等于前两项之和。
    int fib(int n){ if (n == 0) return 0; if (n == 1) return 1; else { return fib(n - 1) + fib(n - 2); }}9.6.2 递归的优点递归给某些编程问题提供了最简单的方法。
    9.6.3 递归的缺点一个有缺陷的递归会很快耗尽计算机的资源,递归的程序难以理解和维护。
    10 指针10.1 指针10.1.1 指针的概念指针变量也是一个变量;指针存放的内容是一个地址,该地址指向一块内存空间
    10.1.2 指针变量的定义可以定义一个指向一个变量的指针变量
    Int *p; //表示定义一个指针变量*p; //代表指针所指内存的实际数据切记,指针变量只能存放地址,不能将一个int型变量直接赋值给一个指针。Int *p = 100;Int *p = &a; //得到变量a的地址,将这个地址赋值给变量pInt *p1;//定义一个变量,名字叫p1,它可以指向一个int的地址P1=&b;//指针变量的值一般不能直接赋值一个整数,而是通过取变量地址的方式赋值10.1.3 &取地址运算符&可以取得一个变量在内存当中的地址
    10.1.4 无类型指针定义一个指针变量,但不指定它指向具体哪种数据类型。可以通过强制转化将void 转化为其他类型指针,也可以用(void )将其他类型指针强制转化为void类型指针。
    Void *p;10.1.5 NULLNULL在C语言中的定义为(void *)0;当一个指针不指向任何一个有效内存地址的时候,我们应该把指针设置为NULL。
    10.1.6 空指针与野指针指向NULL的指针叫空指针,没有具体指向任何变量地址的指针叫野指针。空指针是合法的,但野指针是危险的,是导致程序崩溃的主要原因。
    10.1.7 指针的兼容性指针之间赋值比普通数据类型赋值检查更为严格,例如:不可以把一个double *赋值给int *;原则上一定是相同类型的指针指向相同类型的变量地址,不能用一种类型的指针指向另一种类型的变量地址。
    10.1.8 指向常量的指针与指针常量Const char * p; //定义一个指向常量的指针Char *const p;//定义一个指针常量,一旦初始化之后其内容不可改变。10.1.9 指针与数组的关系一个变量有地址,一个数组包含若干个元素,每个元素在内存中都有地址。
    Int a[10];Int *p = a;比较p和&a[0]的地址是否相同。
    10.1.10 指针运算指针运算不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数作为倍数的运算。
    Char *p;P++; //移动了sizeof(char)这么多的字节数int *p1;P1++; //移动了sizeof(int)这么多的字节数赋值:int *p = &a;求值:int i = *p;取指针地址int **pp = &p;将一个整数加(减)给指针:p+3;p-3;增加(减少)指针值 p++,p--求差值,p1-p2,通常用于同一个数组内求两个元素之间的距离比较p1= =p2,通常用来 比较两个指针是否指向同一个位置10.1.11 通过指针使用数组元素P+1代表&a[1],也可以直接使用p[1]表示a[5]P +5代表&a[5];P++
    寻找数组第二大元素第一步:假设数组中前2个元素就是最大的和第二大的MaxSmax第二步:从数组的第2号元素开始遍历数组当有元素大于max的时候,Smax = maxMax= 最大的那个元素,如果当前元素小于max,并且大于smax,那么就让smax等于当前那个元素
    int smax(int *s)//求数组中第二大元素{ int max = 0; int s_max = 0; int i; if (*s > *(s +1)) { max = *s; s_max = *(s + 1); } else { max = *(s + 1); s_max = *s; }//将max等于s[0]和s[1]中大的那个元素的值 for(i = 2;i < 10;i++)//从第3个元素开始遍历数组 { if(max < *(s + i))//如果遇到大于max的元素,那么让s_max等于max,让max等于这个元素 { s_max = max; max = *(s + i); } else if(max > *(s + i) && *(s + i) > s_max)//如果这个元素是介于max和s_max之间,那么就让这个元素等于s_max { s_max = *(s + i); } } return s_max;//返回s_max的值}int main(){ int buf[10] = {34,21,56,4,87,90,15,65,72,48}; printf("%d\n",smax(buf)); return 0;}#include<string.h>//通过指针将字符串逆置int main(){ char str[100]="you"; char *str_start = &str[0]; char *str_end = &str[strlen(str) - 1]; while(str_start < str_end) { char *tmp = * str_start; * str_start = * str_end; * str_end = tmp; str_start ++; str_end --; } printf("%s\n",str); return 0;}对于VS的汉字是GBK编码,一个汉字2个字节;对于QT汉字是UTF8编码,一个汉字是3个字节。
    #include<string.h>//通过指针将汉字字符串逆置int main(){ char str[100]="我爱你"; short *str_start = &str[0]; short *str_end = &str[strlen(str) - 2]; while(str_start < str_end) { short *tmp = * str_start; * str_start = * str_end; * str_end = tmp; str_start ++; str_end --; } printf("%s\n",str); return 0;}10.1.12 指针数组int *a[10];//定义了一个指针数组,一共10个成员,其中每个成员都是int *类型10.1.13 指向指针的指针(二级指针)指针就是一个变量,既然是变量就也存在内存地址,所以可以定义一个指向指针的指针。通过二级指针修改内存的值;
    Int I = 10;Int *p1 = &I;Int **p2 = &p1;Printf(“%d\n”,**p2);以此类推可以定义3级甚至多级指针。C语言允许定义多级指针,但是指针级数过多会增加代码的复杂性,考试的时候可能会考多级指针,但是实际编程的时候最多用到3级指针,但是3级指针也不常用,一级和二级指针是大量使用的。
    10.1.14 指向二维数组的指针Int buf[3][5] 二维数组名称,buf代表数组首地址Int (*a)[5] 定义一个指向int[5]类型的指针变量aa[0],*(a+0),*a 0行,0列元素地址a+1 第1行首地址a[1],*(a+1) 第1行,0列元素地址a[1]+2,*(a+1)+2,&a[1][2] 第1行,2列元素地址*(a[1]+2),*(*(a+1)+2),a[1][2] 第1行,2列元素的值//二维数组的指针计算二维数组行列的平均值int main(){ int buf[3][5] = {{2,4,3,5,1},{7,2,6,8,1},{7,3,9,0,2}}; int i; int j; int sum; for(i = 0;i < 3;i ++) { sum = 0; for(j = 0;j < 5;j ++) { sum += (*(*(buf + i) + j)); //sum += buf[i][j]; } printf("%d\n",sum / 5); } for(i = 0;i < 5;i ++) { sum = 0; for(j = 0;j < 3;j ++) { sum += (*(*(buf + j) + i)); //sum += buf[j][i]; } printf("%d\n",sum / 3); } return 0;}10.1.15 指针变量作为函数的参数函数的参数可以是指针类型 *,它的作用是将一个变量的地址传送给另一个函数。通过函数的指针参数可以间接的实现形参修改实参的值。
    10.1.16 一维数组名作为函数参数当数组名作为函数参数时,C语言将数组名解释为指针
    int func(int array[10]);//数组名代表数组的首地址10.1.17 二维数组名作为函数参数二维数组做函数参数时可以不指定第一个下标。
    int func(int array[][10]);将二维数组作为函数的参数用例不是特别多见。
    10.1.18 const关键字保护数组内容如果讲一个数组作为函数的形参传递,那么数组内容可以在被调用函数内部修改,有时候不希望这样的事情发生,所以要对形参采用const参数。
    func(const int array[]);10.1.19 指针作为函数的返回值return NULL;10.1.20 指向函数的指针指针可以指向变量、数组,也可以指向一个函数。一个函数在编译的时候会分配一个入口地址,这个入口地址就是函数的指针,函数名称就代表函数的入口地址。函数指针的定义方式:
    int (*p)(int);//定义了一个指向int func(int n)类型函数地址的指针。
    定义函数指针变量的形式为:函数返回类型(*指针变量名称)(参数列表)
    函数可以通过函数指针调用

    int(* p)()代表指向一个函数,但不是固定哪一个函数Void *p(int ,char *);//声明了一个函数,函数的名字叫p,函数的返回值是void *,函数的参数是int和char *Void (*p)(int ,char *);//定义了一个指向参数为int和char *,返回值为void的函数指针Int *(*p)(int *);//定义一个参数为int *,返回值为int *的指向函数的指针Int *p(int *);//声明了一个函数,返回值是int *,参数是int *Int (*p)(int , int );//定义了一个指向函数的指针,可以指向两个参数,都是int,返回值也是int类型在回调函数和运行期动态绑定的时候大量的用到了指向函数的指针。
    10.1.21 把指向函数的指针作为函数的参数将函数指针作为另一个函数的参数称为回调函数。
    int max(int a,int b){ if(a > b) return a; else return b;}int add(int a,int b){ return a + b;}int func(int (*p)(int,int),int a,int b)//第一个参数是指向函数的指针{ return p(a,b);//通过指向函数的指针调用一个函数}int main(){ int i = func(add,6,9);//add函数在这里就叫回调函数 printf("i = %d\n",i); return 0;}10.1.22 memset,memcpy,memmove函数这三个函数分别实现内存设置,内存拷贝,内存移动使用memcpy的时候,一定要确保内存没有重叠区域。
    memset(buf, 0, sizeof(buf));//第一个参数是要设置的内存地址,第二个参数是要设置的值,第三个参数是内存大小,单位:字节memcpy(buf2, buf1, sizeof(buf1));//将buf1的内存内容全部拷贝到buf2,拷贝大小为第三个参数:字节memmove(buf2, buf1, sizeof(buf1));//并没有改变原始内存的值
    10.1.23 指针小结定义 说明Int I 定义整形变量Int *p 定义一个指向int的指针变量Int a[10] 定义一个int数组Int *p[10] 定义一个指针数组,其中每个数组元素指向一个int型变量的地址Int (*p)[10] 定义一个数组指针,指向int[10]类型的指针变量Int func() 定义一个函数,返回值为int型Int *func() 定义一个函数,返回值为int *型Int (*p)() 定义一个指向函数的指针,函数的原型为无参数,返回值为intInt **p 定义一个指向int的指针的指针,二级指针11 字符指针与字符串11.1 指针和字符串在C语言中,大多数字符串操作其实就是指针操作。
    Char s[] = “hello world”;Char *p = s;P[0] = ‘a’;11.2 通过指针访问字符串数组char buf[100] = "hello world";char *p = buf;//*(p + 5) = 'a';//p[5] = 'b';p += 5;*p = 'c';p[3] = ' ';printf("buf = %s\n",buf);11.3 函数的参数为char *void print_array(int *p,int n)//如果参数是一个int数组,那么就必须传递第二个参数用来标示数组的长度{ int i; for(i = 0; i <n; i++) { printf("p[%d] = %d\n", i, p[i]); }}void print_str(char *s)//如果参数是个字符串,那么就不需要包含第二个参数//因为字符串是明确的以‘\0’结尾的,所以在函数内部是有条件来作为循环终止依据的{ int i = 0; while(s[i]) { printf("%c",s[i++]); }}11.4 指针数组作为main函数的形参Int main(int argc, char *argv[]);main函数是操作系统调用的,所以main函数的参数也是操作系统在调用时候自动填写的argc代表命令行参数的数量argv代表命令行的具体参数,是char *类型的
    12 内存管理12.1 作用域一个C语言变量的作用域可以是代码块作用域,函数作用域或者文件作用域。代码块是{}之间的一段代码。出现在{}之外的变量,就是全局变量。
    12.1.1 auto自动变量一般情况下代码块内部定义的变量都是自动变量。当然也可以显示的使用aotu关键字
    12.1.2 register寄存器变量通常变量在内存当中,如果能把变量放到CPU的寄存器里面,代码执行效率会更高register int i;//建议,如果有寄存器空闲,那么这个变量就放到寄存器里面使用对于一个register变量,是不能取地址操作的
    12.1.3 代码块作用域的静态变量静态变量是指内存位置在程序执行期间一直不改变的变量,一个代码块内部的静态变量只能被这个代码块内部访问。static int i = 0;//静态变量,只初始化一次,而且程序运行期间,静态变量一直存在
    12.1.4 代码块作用域外的静态变量代码块之外的静态变量在程序执行期间一直存在,但只能被定义这个变量的文件访问Static int a=0;//一旦全局变量定义static,意思是这个变量只是在定义这个变量的文件内部全局有效
    12.1.5 全局变量全局变量的存储方式和静态变量相同,但可以被多个文件访问
    12.1.6 外部变量与extern关键字extern int i;
    12.1.7 全局函数和静态函数在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态
    12.2 内存四区12.2.1 代码区代码区code,程序被操作系统加载到内存的时候,所有的可执行代码都加载到代码区,也叫代码段,这块内存是不可以运行期间修改的。
    12.2.2 静态区所有的全局变量以及程序中的静态变量都存储到静态区,比较如下两段代码的区别:
    int a=0; int a=0;int main() static int b=0;{ int main() static int b=0; {printf(“%p,%p\n”,&a,&b); printf(“%p,%p\n”,&a,&b);return 0; retrun 0;} }int *getb() //合法的{ static int a=0; return &a;}12.2.3 栈区栈stack是一种先进后出的内存结构,所有的自动变量,函数的形参都是由编译器自动放出栈中,当一个自动变量超出其作用域时,自动从栈中弹出。
    对于自动变量,什么时候入栈,什么时候出栈,是不需要程序控制的,由C语言编译器实现栈不会很大,一般都是以K为单位的
    int *geta()//错误,不能将一个栈变量的地址通过函数的返回值返回{ int a = 0; return &a;}12.2.4 栈溢出当栈空间已满,但还往栈内存压变量,这个就叫栈溢出。对于一个32位操作系统,最大管理4G内存,其中1G是给操作系统自己用的,剩下的3G都是给用户程序的,一个用户程序理论上可以使用3G的内存空间。
    12.2.5 堆区堆heap和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。堆是一个大容器,它的容量要远远大于栈,但是在C语言中,堆内存空间的申请和释放需要手动通过代码来完成。
    int *geta()//可以通过函数的返回值返回一个堆地址,但记得一定要free{ int *p=malloc(sizeof(int ));//申请了一个堆空间 return p;}Int main(){ Int *getp=geta(); *getp=100; free(getp);}12.3 堆的分配和释放操作系统在管理内存的时候,最小单位不是字节,而是内存页。
    12.3.1 mallocvoid * malloc(size_t _Size);int *p=(int *)malloc(sizeof(int)*10);//在堆中间申请内存,在堆中申请了一个10个int这么大的空间malloc函数在堆中分配参数_Size指定大小的内存,单位:字节,函数返回void *指针。Void getheap(int *p){ P=malloc(sizeof(int) * 10);}//getheap执行完以后,p就消失了,导致它指向的具体堆空间的地址编号也随之消失了Void getheap1(int **p){ *p=malloc(sizeof(int) * 10);}Int main(){ Int *p=NULL; Printf(“p=%p\n”,&p); //getheap(p);//实参没有任何改变 getheap1(&p);//得到了堆内存的地址}12.3.2 freeVoid free(void *p);free(p);//释放通过malloc分配的堆内存free负责在堆中释放malloc分配的内存。参数p为malloc返回的堆中的内存地址。Int *array = malloc(sizeof(int) * i);//在堆当中动态创建一个int数组free(array);12.3.3 callocVoid * calloc(size_t _Count, size_t _Size);Calloc与malloc类似,负责在堆中分配内存。
    第一个参数是所需内存单元数量,第二个参数是每个内存单元的大小(单位:字节),calloc自动将分配的内存置0
    Int *p=(int *)calloc(100,sizeof(int));//分配100个int12.3.4 realloc重新分配用malloc或者calloc函数在堆中分配内存空间的大小。
    Void * realloc(void *p, size_t _NewSize);第一个参数p为之前用malloc或者calloc分配的内存地址,_NewSize为重新分配内存的大小,单位:字节。成功返回新分配的堆内存地址,失败返回NULL;如果参数p等于NULL,那么realloc与malloc功能一致。
    Char *p = malloc(10);//分配空间,但原有数据没做清洁Char *p1 = calloc(10, sizeof(char));//分配空间以后,自动做清洁Char *p2 =realloc(p1,20);//在原有内存基础之上,在堆中间增加连续的内存//如果原有内存没有连续空间可扩展,那么会新分配一个空间,将原有内存copy到新空间,然后释放原有内存。//realloc和malloc,只分配内存,但不打扫Char *p2 = realloc(NULL,5);//等于malloc(5)13 结构体,联合体,枚举与typedef13.1 结构体13.1.1 定义结构体struct和初始化Struct man{ Char name[100]; Int age;};Struct man m = {“tom”,12};Struct man m = {.name = “tom”, .age = 12};#include <string.h>#pragma warning(disable:4996)struct student{ char name[100]; int age; int sex;};//说明了一个结构体的数据成员类型int main(){ struct student st;//定义了一个结构体的变量,名字叫st; st.age = 20; st.sex = 0; strcpy(st.name,"刘德华"); printf("name = %s\n",st.name); printf("age = %d\n",st.age); if(st.sex == 0) { printf("男"); } else { printf("女"); } return 0;}13.1.2 访问结构体成员.操作符
    13.1.3 结构体的内存对齐模式编译器在编译一个结构的时候采用内存对齐模式。
    Struct man{ Char a; Int b; Shor c; Char d; Long long e;};13.1.4 指定结构体元素的位字段定义一个结构体的时候可以指定具体元素的位长;
    Struct test{ char a : 2;//指定元素为2位长,不是2个字节长};13.1.5 结构数组Struct man m[10] = {{“tom”,12},{“marry”,10},{“jack”,9}};Int I;for(i=0; i<5; i++){ Printf(“姓名=%s,年龄=%d\n”,m[i].name,m[i].age);}#include <stdio.h>#include <string.h>#pragma warning(disable:4996)//结构体数组排序struct student{ char name[100]; int age; int score; char classes[100];};void swap(struct student *a,struct student *b){ struct student tmp = *a; *a = *b; *b = tmp;}int main(){ struct student st[5] = {{"chen",12,78,"A"},{"li",10,90,"B"},{"wang",13,59,"C"},{"fei",12,91,"D"},{"bai",9,59,"E"}}; int i; int j; for(i = 0; i < 5; i++) { for(j = 1; j < 5-i; j++) { if(st[j].age < st[j - 1].age) { swap(&st[j],&st[j - 1]); } else if(st[j].age == st[j - 1].age) { if(st[j].score <st[j-1].score) { swap(&st[j],&st[j - 1]); } } } } for(i = 0; i < 5; i++) { printf("姓名=%s,年龄=%d,成绩=%d,班级=%s\n",st[i].name,st[i].age,st[i].score,st[i].classes); } return 0;}13.1.6 嵌套结构一个结构的成员还可以是另一个结构类型
    Struct names{ Char a; Int b;};Struct man{ Struct names name; Int c;};Struct man m = {{“wang”,10},20};13.1.7 结构体的赋值Struct name m = b;
    结构体赋值,其实就是结构体之间内存的拷贝
    13.1.8 指向结构体的指针—>操作符Struct A a;Struct A *p = &a;p->a = 10;13.1.9 指向结构体数组的指针13.1.10 结构中的数组成员和指针成员一个结构中可以有数组成员,也可以有指针成员,如果是指针成员结构体成员在初始化和赋值的时候就需要提前为指针成员分配内存。
    Struct man{ Char name[100]; Int age;};Struct man{ Char *name; Int age;};13.1.11 在堆中创建的结构体如果结构体有指针类型成员,同时结构体在堆中创建,那么释放堆中的结构体之前需要提前释放结构体中的指针成员指向的内存。
    Struct man{ Char *name; Int age;};Struct man *s = malloc(sizeof(struct man) * 2);S[0].name = malloc(10 * sizeof(char));S[1].name = malloc(10 * sizeof(char));13.1.12 将结构作为函数参数将结构作为函数参数将结构指针作为函数参数
    Struct student{ Char name[10]; Int age;};Void print_student (struct student s)//一般来讲,不要把结构变量作为函数的参数传递,因为效率比较低,一般用指针来代替//void print_student(const struct student *s){ Printf(“name = %s, age = %d\n”,s.name,s.age);}Int main(){ Struct student st = {“tom”,20}; Print_student(st);}13.1.13 结构,还是指向结构的指针在定义一个和结构有关的函数,到底是使用结构,还是结构的指针?指针作为参数,只需要传递一个地址,所以代码效率高。
    Void set_student (struct student *s, const char *name, int age){ Strcpy(s->name, name); s->age = age;}Int main(){ Set_student(&st, “maik”,100); Print_student(st);}结论:当一个结构作为函数的参数时候,尽量使用指针,而不是使用结构变量,这样代码效率很高。
    13.2 联合体联合union是一个能同一个存储空间存储不同类型数据的类型。联合体所占的内存长度等于其最长成员的长度,也有叫做共用体。联合体虽然可以有多个成员,但同一时间只能存放其中一种。
    union A{ Int a; Char b; Char *c; //注意};Int main(){ Union A a; a.a = 10;a.c = malloc(100);//c指向了一个堆的地址 free(c);//如果联合体中有指针成员,那么一定要使用完这个指针,并且free指针之后才能使用其他成员 a.b = 20;}13.3 枚举类型13.3.1 枚举定义可以使用枚举声明代表整数常量的符号名称,关键字enum创建一个新的枚举类型。实际上,enum常量是int类型的。枚举是常量,值是不能修改的。
    enum spectrum{ red,yellow,green,blue,white,black};enum spectrum color;color = black;if(color != red)13.3.2 默认值默认时,枚举列表中的常量被指定为0,1,2等
    enum spectrum{ red,yellow,green,blue,white,black};printf(“%d,%d\n”,red,black);指定值可以指定枚举中具体元素的值
    enum spectrum{ red=1,yellow=2,green=3,blue,white,black};13.4 typedeftypedef是一种高级数据特性,它能使某一类型创建自己的名字。
    typedef unsigned char UBYTE;typedef char BYTE;
    与#define不同,typedef仅限于数据类型,而不是能是表达式或具体的值
    typedef是编译器处理的,而不是预编译指令;
    typedef比#define更灵活

    直接看typedef好像没什么用处,使用BYTE定义一个unsigned char。使用typedef可以增加程序的可移植性。
    typedef struct{ Int a;}A2;A2 a;13.5 通过typedef定义函数指针typedef const char *(*SUBSTR)(const char *,const char *);const char *getsubstr(const char *src, const char *str){ return strstr(src,str);}Const char *(*p[3])(const char *,const char *);14 文件操作14.1 fopenChar s[1024] = {0};FILE *p = fopen(“D:\\temp\\a.txt”,”w”);//用写的方式打开一个文件fputs(“hello world”,p);//向文件写入一个字符串//feof(p);//如果已经到了文件结尾,函数返回真While(!feof(p))//如果没有到文件结尾,那么就一直循环{ memset(s,0,sizeof(s));//fgets(s,sizeof(s),p);//第一个参数是一个内存地址,第二个参数是这块内存的大小,第三个参数是fopen返回的文件指针printf(“%s”,s);}fclose(p);//关闭这个文件r以只读方式打开文件,该文件必须存在r+ 以可读写方式打开文件,该文件必须存在rb+ 读写打开一个二进制文件,允许读写数据,文件必须存在rw+ 读写打开一个文本文件,允许读和写w 打开只写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件;w+ 打开可读写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件;a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)a+ 以附加的方式打开可读写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(原来的EOF符不保留)。
    Void code(char *s){ While(*s)//遍历一个字符串 { (*s)++; s++;}}Int main()//文件加密{ Char s[1024] = {0};FILE *p = fopen(“D:\\temp\\a.txt”,”r”);//用读的方式打开一个文件FILE *p1 = fopen(“D:\\temp\\a.txt”,”w”);//用写的方式打开一个文件//feof(p);//如果已经到了文件结尾,函数返回真While(!feof(p))//如果没有到文件结尾,那么就一直循环{ memset(s,0,sizeof(s));fgets(s,sizeof(s),p);//第一个参数是一个内存地址,第二个参数是这块内存的大小,第三个参数是fopen返回的文件指针code(s);//文件加密fputs(s,p1);}fclose(p);//关闭这个文件fclose(p1);}14.2 二进制和文本模式的区别
    在windows系统中,文本模式下,文件以“\r\n”代表换行。若以文本模式打开文件,并用fputs等函数写入换行符”\n”时,函数会自动在”\n”前面加上“\r”。即实际写入文本的是”\r\n”。
    在类Unix/Linux系统中文版模式下,文件以”\n”代表换行。所以Linux系统中在文本模式和二进制模式下并无区别。

    14.3 fclosefclose关闭fopen打开的文件
    14.4 getc和putc函数Int main() int main(){ { FILE *fp=fopen(“a.txt”,”r”); FTLE *fp=fopen(“a.txt”,”w”); Char c; const char *s=”hello world”; While((c=getc(fp))!=EOF) int I; { for(i=0;i<strlen(s);i++) Printf(“%c”,c); {} putc(s[i],fp);fclose(fp); }return 0; fclose(fp);} return 0; }14.5 EOF与feof函数文件结尾程序怎么才能知道是否已经到达文件结尾了呢?EOF代表文件结尾如果已经是文件尾,feof函数返回true。
    While((c=getc(p))!=EOF)//EOF代表文件最后的一个结束标识{ //c=getc(p);//一次只读取一个字符 Printf(“%c”,c);}##14.6 fprintf,fscanf,fgets,fputs函数这些函数都是通过FILE *来对文件进行读写。都是针对文本文件的行读写函数fprintf(p,”%s”,buf);//和printf功能一样,fprintf将输入的内容输入到文件里面fscanf(p,”%s”,buf); //fscanf与scanf用法基本一致,fscanf是从一个文件读取输入,scanf是从键盘读取输入#include <stdio.h>#include <stdlib.h>#include <string.h>//计算文本中的字符串int calc_string(const char *s){ char buf1[100] = {0}; char oper1 = 0; char buf2[100] = {0}; int len = strlen(s);//得到字符串的长度 int i; int start; for(i=0;i<len;i++) { if(s[i] == '+'|| s[i] == '-' || s[i] == '*' || s[i] == '/') { strncpy(buf1, s, i); oper1 = s[i]; break; } } start = i + 1; for(;i<len;i++) { if(s[i] == '=') { strncpy(buf2,&s[start],i-start); } } printf("buf1 = %s,oper1 = %c,buf2 = %s\n",buf1,oper1,buf2); switch(oper1) { case '+': return atoi(buf1) + atoi(buf2); case '-': return atoi(buf1) - atoi(buf2); case '*': return atoi(buf1) * atoi(buf2); case '/': { int a = atoi(buf2); if(a) return atoi(buf1) / atoi(buf2); else return 0; } }}void cutereturn(char *s)//把字符串最后的回车字符吃掉{ int len = strlen(s); if(s[len - 1] == '\n') s[len - 1] = 0;}int main(){ FILE *p = fopen("D:\\main\\a.txt","r");//32+56=88 FILE *p1 = fopen("D:\\main\\b.txt","w"); char buf[1024]; char buf1[1024]; int value; while(!feof(p)) { memset(buf,0,sizeof(buf)); fgets(buf,sizeof(buf),p);//从文件中读取一行记录,字符串最后是以'\n'结尾的 cutereturn(buf);//吸收回车 value = calc_string(buf); memset(buf1,0,sizeof(buf1)); sprintf(buf1,"%s%d\n",buf,value);//将buf和buf计算结果重新组织成一个字符串 fputs(buf1,p1);//将重新组合后的字符串写入新的文件 } fclose(p); fclose(p1); return 0;}14.7 stat函数#include <sys/stat.h>int stat(const char *_Filename, struct stat * _Stat);stat.st_size;//文件大小,单位:字节函数的第一个参数代表文件名,第二个参数是struct stat结构。得到文件的属性,包括文件建立时间,文件大小等信息。Struct stat st = {0};//定义一个结构,名字叫stStat(“D:\\temp\\a.txt”,&st);//调用完stat函数之后,文件相关的信息就保存在了st结构中了14.8 fread和fwrite函数size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);注意:这个函数以二进制形式对文件进行操作,不局限于文本文件,可进行二进制的读写和拷贝返回值:返回实际写入的数据块数目size_t res=fread(buf,sizeof(char),sizeof(buf),p);//第一个参数是缓冲区,第二个参数是读取的时候最小单位大小,第三个参数是一次读几个单位,第四个参数是打开的文件指针,fread返回值代表读取了多少记录数fwrite(buf,sizeof(char),res,p1);//从源文件中读取多少字节,那么就往目标文件中写多少字节14.9 fread与feofwhile(!feof(p))//如果没有到达文件结尾,那么循环继续{ memset(buf,0,sizeof(buf));//每次读取文件一行之前都把这个buf清空 fgets(buf,sizeof(buf),p);//从文件中读取一行 fread(&buf, 1, sizeof(buf), p);}14.10 通过fwrite将结构保存到二进制文件中做一个代码例子
    14.11 fseek函数int fseek(FILE *stream, long offset, int whence);函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败则不改变stream指向的位置,函数返回一个非0值。实验得出,超出文件末尾位置,还是返回0。往回偏移超出首位置,还是返回0,请小心使用。第一个参数stream为文件指针;第二个参数offest为偏移量,正数表示正向偏移,负数表示负向偏移;第三个参数whence设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、SEEK_END或SEEK_SET。SEEK_SET:文件开头SEEK_CUR:当前位置SEEK_END:文件结尾fseek(fp, 3, SEEK_SET);
    14.12 ftell函数long ftell(FILE *stream);函数ftell用于得到文件位置指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。
    long len = ftell(fp);14.13 fflush函数int fflush(FILE *stream);fflush函数可以将缓冲区中任何未写入的数据写入文件中。成功返回0,失败返回EOF。每当程序通过C语言库函数往文件里面写数据,C语言库函数并不是实时的将数据直接写入磁盘,而是放到内存里面,当内存满了或者明确的调用了fclose,才将数据一次性写入磁盘。结合:C语言所有的文件操作函数都是缓冲区函数。
    fflush(p);//fflush将缓冲区的内容立刻写入文件//优势:不会因为停电,或者电脑死机等故障导致缓冲区的内容丢失;//不好:硬盘读写次数增加,导致程序效率低下,同时硬盘寿命变短。修改配置文件的时候,有时候会使用,或者做一些不经常修改的数据,但很重要数据,那么用fflush。
    14.14 remove函数int remove(const char *_Filename);remove函数删除指定文件参数Filename为指定的要删除的文件名,如果是windows下文件名与路径可以用反斜杠\分隔,也可以用斜杠/分隔。
    14.15 rename函数int rename(const char *_OldFilename, const char *_NewFilename);rename函数将指定文件改名参数OldFilename为指定的要修改的文件名,NewFilename为修改后的文件名,如果是windows下文件名与路径可以用反斜杠\分隔,也可以用斜杠/分隔。
    //程序还没有退出的时候,是不能同时打开很多文件的一个程序同时可以打开的文件数是有限的尽量在一个程序中不要同时打开太多的文件,如果确实要操作很多文件,也是一个操作完毕fclose以后,再去操作下一个文件。
    15 基础数据结构与算法15.1 什么是数据结构数据(data)是对客观事物符号表示,在计算机中是指所有能输入的计算机并被计算机程序处理的数据总称。数据元素(data element)是数据的基本单位,在计算机中通常作为一个整体进行处理。数据对象(data object)是性质相同的数据元素的集合,是数据的一个子集。数据结构(data structure)是相互之间存在一种或多种特定关系的数据元素的集合。数据类型(data type)是和数据结构密切关系的一个概念,在计算机语言中,每个变量、常量或者表达式都有一个所属的数据类型。抽象数据类型(abstract data type ADT)是指一个数据模型以及定义在该模型上的一组操作,抽象数据类型的定义仅取决于它的一组逻辑性,与其在计算机内部如何表示以及实现无关。
    15.2 什么是算法算法是对特定问题求解的一种描述,它是指令的有限序列,其每一条指令表示一个或多个操作,算法还有以下特性:

    有穷性一个算法必须总是在执行有限步骤后的结果,而且每一步都可以在有限时间内完成。
    确定性算法中每一个指令都有确切的含义,读者理解时不会产生二义性,在任何条件下,算法只有唯一的一条执行路径,即相同的输入只能得出相同的
    可行性一个算法是可行的,即算法中描述的操作都是可以通过已经实现的基本运算来实现的。
    输入一个算法有零个或者多个输入,这些输入取自与某个特定对象的集合。
    输出一个算法一个或多个输出,这些输出是和输入有某些特定关系的量。

    15.3 排序15.3.1 冒泡排序冒泡排序首先将一个记录的关键字和第二个记录的关键字进行比较,如果为逆序(elem[1]>elem[2]),则两个记录交换之,然后比较第二个记录和第三个记录的关键字,以此类推,直到第n-1个记录和第n个记录的关键字进行过比较为止。上述过程称作第一次冒泡排序,其结果是将关键字最大的记录接安排到最后一个记录的位置上,然后进行第二次冒泡排序,对前n-1个记录进行同样操作,其结果是使关键字第二大记录被安置到第n-1位置上,直到将所有记录都完成冒泡排序为止。
    #include <stdio.h>//冒泡排序void swap(int *a,int *b){ int tmp = *a; *a = *b; *b = tmp;}void bubble(int *array, int n){ int i; int j; for(i = 0; i < n; i++) for(j = 1; j < n-i; j++) { if(array[j - 1] > array[j]) { swap(&array[j - 1],&array[j]); } }}void print(int *array, int n){ int i; for(i = 0; i < n; i++) { printf("%d\n",array[i]); }}int main(void){ int array[10] = {32,45,8,78,21,89,4,15,23,56}; bubble(array,10); print(array,10); return 0;}15.3.2 选择排序选择排序是每一次在n-1+1(i=1,2,3,…n)个记录中选取关键字,最小的记录作为有序序列中第i个记录。通过n-1次关键字间的比较,从n-i+1个记录中选取出关键字最小的记录,并和第i(1<=i<=n)个记录交换之。
    int minkey(int *array, int low, int high)//查找指定范围内的最小值//第一个参数是一个数组,第二个参数是数组的开始下标,第三个参数是数组的终止下标//返回值是最小元素的下标{ int min = low; int key = array[low];//在没有查找最小元素之前,第一个元素是最小的 int i; for(i = low + 1; i < high; i++) { if(key > array[i]) { key = array[i]; min = i; } } return min;}void select(int *array, int n)//选择排序法{ int i; for(i = 0; i < n; i++) { int j = minkey(array, i, n); if(i != j)//范围内的第一个成员不是最小的 { swap(&array[i],&array[j]); } }}int main(void){ int array[10] = {32,45,8,78,21,89,4,15,23,56}; select(array,10); return 0;}15.4 查找15.4.1 顺序查找顺序查找的过程为:从表的最后一个记录开始,逐个进行记录的关键字和给定值比较,如果某个记录的关键字与给定值相等,则查找成功,反之则表明表中没有所查找记录,查找失败。
    int seq(int *array, int low, int high, int key)//顺序查找//在指定范围内寻找和key相同的值,找到返回下标,找不到返回—1{ int i; for(i = low; i < high; i++) { if(array[i] == key) return i; } return -1;}15.4.2 二分查找在一个已经排序的顺序表中查找,可以使用二分查找来实现。二分查找的过程是:先确定待查记录所在的范围(区间),然后逐步缩小查找范围,直到找到或者找不到该记录为止。假设指针low和high分别指示待查找的范围下届和上届,指针mid指示区间的中间值,即mid=(low + high) / 2。
    int bin(int *array, int low, int high, int key)//二分查找{ while(low <= high) { int mid = (low + high) / 2; if(key == array[mid])//中间切一刀,正好和要查找的数相等 return mid; else if(key > array[mid])//如果要找的数大于array[mid],那么就在下半部分继续切刀 low = mid + 1; else//如果要找的数小于array[mid],那么就在上半部分继续切刀 high = mid - 1; } return -1;//没有找到数据}int bin_rec(int *array, int low, int high, int key)//递归法实现二分查找{ if(low <= high) { int mid = (low + high) / 2; if(key == array[mid])//中间切一刀,正好和要查找的数相等 return mid; else if(key > array[mid])//下半部分继续查找 return bin_rec(array,mid + 1,high,key); else return bin_rec(array,low,mid - 1,key);//在上半部分查找 }else return -1;//没有找到数据}15.5 链表15.5.1 单向链表定义对于数组,逻辑关系上相邻的两个元素的物理位置也是相邻的,这种结构的优点是可以随机存储任意位置的元素,但缺点是如果从数组中间删除或插入元素时候,需要大量移动元素,效率不高。
    链式存储结构的特点,元素的存储单元可以是连续的,也可以是不连续的,因此为了表示每个元素a,与其接后的元素a+1之间的关系,对于元素a,除了存储其本身信息外,还需要存储一个指示其接后元素的位置。这两部分数据成为结点(node)。一个结点中存储的数据元素被称为数据域。存储接后存储位置的域叫做指针域。N个结点(ai(1<=i<=n))的存储映像链接成一个链表。
    整个链表必须从头结点开始进行,头结点的指针指向下一个结点的位置,最后一个结点的指针指向NULL;在链表中,通过指向接后结点位置的指针实现将链表中每个结点“链”到一起。链表中第一个结点称之为头结点。N个结点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,…,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起。如图所示:
    15.5.2 单向链表数据结构定义struct list{ int data;//数据域 struct list *next;//指针域};15.5.3 单向链表的实现#include <stdio.h>#include <stdlib.h>struct list{ int data;//数据域 struct list *next;//指针域};struct list *create_list()//建立一个节点{ return calloc(sizeof(struct list),1);}struct list *insert_list(struct list *ls, int n, int data)//在指定位置插入元素{ struct list *p = ls; while(p && n--) { p = p->next; } if(p == NULL) { return NULL;//n的位置大于链表节点数 } struct list *node = create_list();//新建立一个节点 node->data = data; node->next = p->next; p->next = node; return node;}int delete_list(struct list *ls, int n)//删除指定位置元素{ struct list *p = ls; while(p && n--) { p = p->next; } if(p == NULL) { return -1;//n的位置不合适 } struct list *tmp = p->next; p->next = p->next->next; free(tmp); return 0;//删除成功}int count_list(struct list *ls)//返回链表元素个数{ struct list *p = ls; int count = 0; while(p) { count ++; p = p->next; } return count;}void clear_list(struct list *ls)//清空链表,只保留首节点{ struct list *p = ls->next; while(p) { struct list *tmp = p->next; free(p); p = tmp; } ls->next = NULL;//只有首节点,那么首节点的next也应该设置为NULL}int empty_list(struct list *ls)//返回链表是否为空{ if(ls->next) return 0; else return -1;}struct list *locale_list(struct list *ls, int n)//返回链表指定位置的节点{ struct list *p = ls; while(p && n--) { p = p->next; } if(p == NULL) return NULL; return p;}struct list *elem_locale(struct list *ls,int data)//返回数据域等于data的节点{ struct list *p = ls; while(p) { if(p->data == data) return p; p = p->next; } return NULL;//没有找到数据域等于data的节点}int elem_pos(struct list *ls, int data)//返回数据域等于data的节点位置{ int index = 0; struct list *p = ls; while(p) { index++; if(p->data == data) return index; p = p->next; } return -1;//没有找到数据域等于data的节点}struct list *last_list(struct list *ls)//得到链表最后一个节点{ struct list *p = ls; while(p->next) { p = p->next; } return p;}void merge_list(struct list *ls1,struct list *ls2)//合并两个链表,结果放入ls1中{ //只合并链表的节点,不合并链条头 last_list(ls1)->next = ls2->next; free(ls2);//链表头不要了}void reverse(struct list *ls)//链表逆置{ if (ls->next == NULL) return;//只有一个首节点,不需要逆置 if (ls->next->next == NULL) return;//也不需要逆置 struct list *last = ls->next;//逆置后ls->next就成了最后一个节点了 struct list *pre = ls;//上一个节点的指针 struct list *cur = ls->next;//当前节点的指针 struct list *next = NULL;//下一个节点的指针 while(cur) { next = cur->next; cur->next = pre; pre = cur; cur = next; } ls->next = pre; last->next = NULL;}void traverse(struct list *ls)//循环遍历链表{ struct list *p = ls; while(p) { printf("%d\n",p->data); p = p->next;//p指向他对应的下一个节点 }}int main(void){ struct list *first = create_list();//在堆中间创建一个节点 struct list *second = create_list();//在堆中间创建一个节点 struct list *third = create_list();//在堆中间创建一个节点 first->next = second; second->next = third; third->next = NULL;//对于链表的最后一个节点,next域一定为NULL first->data = 1; second->data = 2; third->data = 3; insert_list(first, 1, 10); insert_list(first, 1, 20); //delete_list(first, 2); //clear_list(first); traverse(first); printf("--------------\n"); printf("count = %d\n", count_list(first)); printf("%d\n", locale_list(first,3)->data); printf("data = %d\n",last_list(first)->data); printf("--------------\n"); struct list *first1 = create_list(); int i; for(i = 0; i < 10; i++) { insert_list(first1, 0, i); } merge_list(first,first1); printf("--------------\n"); traverse(first); printf("--------------\n"); reverse(first); traverse(first); return 0;}逆置操作

    判断首节点的next是否为NULL;
    判断第二个节点的next是否为NULL;
    逆置后ls->next就成了最后一个节点了
    最后一个节点的next指向NULL。
    0  留言 2019-07-27 10:48:37
eject