总结
两段代码存在大量相同的程序结构,那么我们就可以对他们进行封装,而非挨个修改;
例如:EXPECT_LT和EXPECT_EQ等等,GREEN RED BLUE等等
总结
两段代码存在大量相同的程序结构,那么我们就可以对他们进行封装,而非挨个修改;
例如:EXPECT_LT和EXPECT_EQ等等,GREEN RED BLUE等等
总结:
1、
设计基础的测试用例框架,将简单的TEST宏的展开为三部分:
1.1、先声明有这个测试用例;
1.2、这个TEST函数,先于主函数去执行,把测试用例添加到全局存储区去;
1.3、为了承接这个{ },形成一个合法的函数
2
void a##_haizei_##b(),
//避免,a1##b1和a2##b2函数名一样,导致函数重定义
总结:
1、
去用第三方程序库提供的功能的时候,只需要把头文件和静态链接库拿到就好了,这样就有函数的声明和定义了!
2、
google测试框架中,EXPECT在出错了会输出错误继续运行下去;ASSERT则在出错后,直接停止;
3、
单元测试?
Unit Testing,是指对软件中的最小可测试单元进行检查和验证。
C语言的一个单元,指的是一个函数(Java中一个的单元,指的是一个类)
其中一个TEST,就是一个测试用例,而每一个EXPECT_EQ则是一个测试点。
总结:
第一部分:避免单个源文件编译过程中,出现函数重定义的错误:
#ifndef _*_H
#define _*_H
//这里写头文件内容
#endif
若希望头文件外面用尖括号,即对于"header.h",可以写成<header.h>,需要将头文件放在系统头文件的查找路径下。
第二部分:避免多个对象文件连接过程中,出现函数重定义的错误:头文件只放函数声明;定义写在源文件中;
第三部分:对于创建和使用静态链接库,我们需要提供及接收:1、包含一组头文件的include文件夹;2、压缩了一组定义的lib文件夹;
总结
#define pchar char *
#typedef char * ppchar;
...
pchar p1, p2;
ppchar p3, p4;
...
预编译后的结果:展开成这个样子
char * p1, p2;
ppchar p3, p4;
定义了char类型指针p1,和一个char类型变量p2,因此sizeof(p2) = 1;
define所作的操作就是替换,傻瓜表达式;因此,用typedef对类型进行重命名,则可以有效避免这样的结果。
总结
1、
0x,(char *),(T *)都具有强转作用;
2、
大端机和小端机的区分和栈这个数据结构有关;我们在栈区申请空间,先进后出;
操作系统在内存栈区中开辟空间,并塞数据,而大端机和小端机是不同方向的塞法;
小端机:先读到的内容,往高地址塞,然后往低地址累加;
大端机:先读到的内容,往低地址塞,然后往高地址累加;
而读取的时候,都是从低地址往高地址读取;
总结:
1、
主函数也有参数,分别为,参数个数,参数内容和环境变量。操作系统会对其进行传参。
2、
类型和变量的区别:例如类型int和变量a,类型可以定义很多变量,而变量不能用来定义其他更多的东西。
3、
内建类型的重命名:
typedef long long lint;
typedef char * pchar;
结构体类型的重命名:
typedef struct __node{
int x, y;
} Node, *PNode;
//PNode a——指向该结构体类型的指针变量;
5、
函数指针命名:
typedef int (*func)(int);
这样去用宏,和type define,是有区别的。
总结:
1、
有一片空间,大家共用这片空间,叫做共用体。
有一片,公用的存储空间。当上面的值或内容发生改变,其他人的也发生了改变。
2、
4个char就是一个int,就是一个共用体,这就是这个例子的作用,ip转为int数字。
一个unsiged char,就是可以放得下ip里面的每一部分的数字,0 - 256;
3、
输入:192.168.0.1
输出:16820416
输入:192.168.0.2
输出:33597632
输入:192.168.0.3
输出:16820417
机器是小端机
按理说,输入是挨着的,整数值应该差一位,而输出却差很多;隔一个却差不离;
大端,小端:机器分为大端机和小端机
大端机的规则,将我们的数字分为低位跟高位,每次开辟空间后,将我们的低位数据放在高地址的位置上,叫大端机;
小端机的规则,每次开辟空间后,将我们的低位数据,放在低地址上;
为什么,192.168.0.2的输出,会和192.168.0.1的输出差这么多?因为2在高位上!
如果想输出的差距和输入的差距一样,将数字反过来储存就好了。
总结:
1、
如果,想表示一个人,怎么定义?要表示的变量特别多,有年龄,名字,身高等等;
有一个特殊的“类型”——结构体类型!——
struct person {
char name[20]
int age;
char gender;
float height;
}
2、
类型和变量如何区分?
2.1、一个类型可以声明很多相同类型的变量;
2.2、struct person 代表的是一个类型;而person本身不是一个类型
3、
计算机会以当前结构体中的最大类型,最为开辟空间的对齐标准。最大类型为多少字节,每次就开辟多少字节;
struct node1{
char a;
char b;
int c;
}
struct node1{
char a;
int c;
char b;
}
第一种:| a | b | _ | _ | c | c | c | c |——总占据8字节;
第二种:| a | _ | _ | _ | c | c | c | c | b | _ | _ | _ |——总占据12个字节,因为开辟了4字节放了一个char后,放不下int,所以新开了4个字节放int,因此就空了3个字节;
因此,对于相同字节,要放在一起定义,减少开辟空间的“大小”
4、
struct node1 {}后,
在C++中,可以用node1 a去定义一个变量,因为对于C++,它是一个类;
而在C语言中是一个类型,因此在C语言中,得这么写 struct node1 a;
5、
直接访问,a就是它所定义的变量,再去访问a中的字段,就是直接访问,例如:a.b,访问了一个char类型的变量。
间接访问,*a代表我定义了指向struct node1类型的指针变量(还是指向该结构体类型的首地址吗?例如数组的首地址),这就是间接访问。
总结:
1、
int strlen1(char *str)
printf("%d\n", strlen1("hello haizei"));
这样往函数里传一个常量的地址,而函数里是用变量的地址来接收的,会出错。
因为,变量地址是无法接收常量地址的,要改成常量地址,如下:
uint64_t strlen1(const char *str)
2、
uint64_t可以避免超过2G的字符串传进来接收不下的问题。(为什么不是避免超过4G的字符串?因为每个字符有一个符号位)
总结:
1、
char str[] = "hello world";
如果开11个大小,最后\0没有地方放,到字符串末尾下一项就会变为非法访问,因此,至少要开11+1 = 12个大小,留一个放\0;
2、
memset(str, 0, sizeof(str))——将str每一个字节初始化为0;将它的每一个字节进行赋值(而非每一位)
memset(str, 1, sizeof(str))——是不是将每一个整型的位置上赋值为1?不对!它的功能是按字节赋值,每个字节赋值为1,表示的不是1!
3、
strncmp(str1, str2, n)——安全的字符串比较,最多比较n位;
strncpy(dest, src, n)——安全的字符串拷贝,会限制最多拷贝n位;
总结
1、【__typeof】 也是一种预置的宏定义,取a的变量类型,定义新的变量__a,可以避免重复计算a++;
2、【##】具有链接作用,类似于
#define contact(a, b) a##b
#define contact(a, b) ab
允许我们传入空参数,使得预编译成功。(可以避免宏定义一个函数时,传入空参数预编译失败)
例如对于,printf("Hello World");
宏定义代码段中有这样一段代码——printf(frm, ##arg); ——后续代码arg若为空,预编译后得到可能大概是这样的代码——printf(frm, );——则能成功;
如果不加##——printf(frm, arg);——后续代码arg若为空,预编译则会报错;
3、宏定义一个《打印文件名,函数名,行数的函数》,通常用于检查错误结果位于哪个文件,函数和行数;
[19.og.cpp : main : 21] 123
[19.og.cpp : main : 22] Hello World
[19.og.cpp : main : 23] 23
可以用条件式编译来减少,查错完成后,注释掉一大段宏定义的麻烦操作。
启用的时候,去掉//#define DEBUG前面的注释符号“//”即可;
弄完了,把注释符号“//”添加回去即可;
4、
宏__FILE__以字符串形式返回所在文件名称
宏__func_以字符串形式返回所在函数名称
宏__LINE__以整数形式返回代码行号
5、
当遇到了具有完全相同性质的逻辑的时候,可以用函数封装起来,避免重复写同样冗长的逻辑;
总结:
宏定义的作用,就是将后半部分的内容,替换成define后面的内容;
主要有三类:
1、常量替换,用名称表示一个常数;
2、傻瓜表达式替换,用一个名字替换一个“常用”表达式;
3、代码段替换,用一个名字,替换一个“常用”的代码段;(为了方便阅读,通常用“\”连接符,写成多行;宏定义一般不支持写成多行)
宏定义的条件式编译:
1、可以看当前是否存在相应的宏定义,来做进一步的代码处理;
2、可以通过记录版本号,来判断,编译成哪种代码段来运行——老式电脑替换一段低版本代码;性能强的电脑替换一段功能丰富的高版本代码;
3、可以根据编译环境调整非标准的宏定义;有些编译环境没有对应的宏定义,因此需要判断调整,要么添加新的宏定义或者换替换为另一种宏定义;
总结:
1、为什么我们没有办法用一个二维指针变量来接收一个二维数组的地址?
因为二维指针变量a[1]不等于原有的二维数组的arr[1],即a[1] != arr[1]
要想正常传一个二维数组arr[100][200]的地址,我们需要一个特殊的声明形式,这种声明形式,使得我们的a[1]跟a[0]也是差400个字节;
因此,我们用int (*a)[200]来接收
4、int (*a)[200],表示指针每次向后移动1位,跳过的是长度为200的整型数组
5、传递数组只能省略一维,最高维,否则就对不上了,不能正常地索引值了;
总结:
1、二分查找前提——数据具有单调性;
2、时间复杂度是log2n,二十亿的数据量,也只需要32次;
【前提条件】数组是单调有序的。
【复杂度】时间复杂度是log2n,每次都舍去一半。
【效率】现在从20亿的值里面找一个值,如果用二分查找,时间复杂度就是log2n,2^(32-1)大约就是20亿)
总结:
【形参与实参】区分形参和实参主要是,参数的作用域不一样,形参只在函数内部进行处理;
【传出函数】如果希望函数能改变外部的参数,需要往里面传参数地址,函数用指针变量来接收地址,这样的函数称之为传出函数,例如scanf函数,还有sscanf, sprintf;
【二维数组,地址+1】
对于二维数组,函数名+1,地址实际上偏移了(第二维度的总长 × 变量长度);
例如对于int arr[100][200], arr + 1,地址向后偏移了200 * 4 = 800个字节;
【接收2维及以上维度的数组】
接收2维及以上维度的数组,只能忽略第一个维度;
例如,对于
int arr[100][200][300]
就得这样来接收,才能保持地址偏移量是一样的;
int func(int (*a)[200][300]) {}
总结
数组是展开的函数,函数是压缩的数组;
数组省时间,函数省空间;
素数筛,提前标记,把未被标记的展示出来;
总结:
a代表数组名,还代表当前数组的首地址
a+i的读取方式,是基于我知道整个数组的首地址,还知道它的数据类型,就可以访问到任何一个下标的值,因此,数组的数据类型和首地址很重要。
如果对函数传的是地址,函数定义的时候,就要用一个指针变量来接收。
总结:
宏定义可以限制在函数内部使用,函数内部定义内部销毁,避免影响外部的使用,这可以给写函数增加很多趣味;
写printf的过程,可以反映出开发一个能实现复杂功能的程序是怎样的,用新数据不断测量,然后不断找bug解决bug;
一个有符号整型的极小值的相反数,用该整型表示不下;
总结:
在实现printf函数中,针对传入不同结构的数据的传出结果,需要进行相应的设计;
例如,
输入%%得到%,用了新的case '%';
传入123打印321,设计了翻转;
传入1000打印1,设计了digit来记录位数;
传入0打印“空”,设计了do while
传入-123打印“/.-”,设计了条件判断,提前打印负号“-”,并令x = -x;