1 C++编程命名约定
1.1 通用命名规则
小驼峰法,tableName (除第一个单词外,每个单词首字母大写)
大驼峰法,TableName (所有单词首字母大写,其余小写)
下划线法,table_name(所有字母小写,用下划线连接)
类型名和变量名一般为名词,例 num_errors
函数名一般为动明结合(大驼峰法且动词在前名词在后),例OpenFile()
1.2 文件命名
文件名全部小写,可包含下划线或连字符。
C++文件以.cpp结尾,例如 hash_table.cpp,头文件以.h结尾,例如hash_table.h。
文件名用途明确,可依据其实现的主要类名来命名,且通常.cpp和.h文件成对出现
1.3 类型命名
所有的class/struct, 类型定义(typedef), 枚举(enum)均使用大驼峰法命名
1.4 变量命名
普通变量:采用下划线法,例:string table_name
全局变量:少用为好,如果用以g为前缀,例:int g_table_name
struct 成员变量和普通变量一样,采用下划线法
class 成员变量要以下划线结束,如果是静态成员变量,加s_前缀
class TableSchema { private: string table_name_; static string s_column_name_; };
1.5 常量命名
包括枚举和const常量:名称前加小写k,且除去开头的 k 之外每个单词开头字母均大写
const char kServiceName[] = “webserv”; enum{ kUnused, kEof, kError};
2 头文件和源文件的区别
2.1 头文件
头文件通常用来写类的声明 、函数原型、#define 常数等,但是很少会写出具体的实现和细节。就好比Java中的抽象类一样
#ifndef CIRCLE #define CIRCLE class Circle { public: Circle(); double Area(); private: double r_; }; #endif
2.2 源文件
源文件主要实现头文件中已经声明的函数
注意:开头必须#include实现的头文件
#include "circle.h" #define PI 3.14 Circle::Circle() { r_ = 5.0; } double Circle::Area() { return PI*r_*r_; }
2.3 源文件关联头文件
1、系统头文件用尖括号,这样编译器会在系统文件目录下查找。 例:#include
2、用户自定义文件用双引号,编译器首先会在用户目录下查找,然后再到C++安装目录中查找,最后在系统文件中查找。 例:#include “xxx.h”
2.4 头文件关联源文件
举例:已知头文件a.h声明了一系列函数,a.cpp中实现了这些函数,若在b.cpp中使用a.h中声明的这些在a.cpp中实现的函数,那么b.cpp是怎样找到a.cpp中的实现呢?
#include叫做编译预处理指令,它会在编译器预处理时,完成一次“复制并插入代码”的工作。 即#include命令进行文件包含处理:将a.h的全部内容复制到#include “a.h”处
程序编译阶段不会去找a.cpp文件中的函数实现,只有在link阶段才会进行这个操作。a.cpp和b.cpp中用#include “ a.h”,实际上是引入声明,使编译通过。源文件编译完成后生成了目标文件(.o或.obj文件)。在link阶段,目标文件中的这些函数就视作一个个符号,通过连接器去在a.cpp中找到实现的函数
2.5 头文件重复引用
重复引用:就是同一个头文件(.h)在同一个源文件(.cpp)中被include了多次,产生这个错误的原因常是include嵌套。
举例:存在cellphone.h这个头文件引用了#include "huawei.h",而china.cpp这个源文件同时引用了#include "cellphone.h" 和 #include "huawei.h",此时huawei.h就在一个源文件中引用了两次
重复引用include不仅会增大编译器的工作量,而且在一些情况下,会引起严重错误,例变量类型重复定义
//解决重复引用问题 #ifndef XX(头文件名) #define XX //头文件内容 #endif
3 代码编译流程
代码编译流程分为四个环节,以HelloWorld代码为例
#include <iostream> int main() { printf("hello world!"); return 0; }
3.1 预处理
hello.cpp --> hello.i
在预处理阶段,涉及宏替换,注释消除,找到相关的库文件,将#include的内容插入
总结:头宏替换,注释消除,头文件插入,生成.i文件
3.2 编译
hello.i --> hello.s
将预处理后的文件转换成汇编语言,生成.s文件,.s文件是汇编文件,其内容是汇编指令
3.3 汇编
hello.s --> hello.o
将汇编文件转换为目标文件,生成.o文件,.o文件是二进制文件,其内容是二进制机器码
3.4 链接
hello.o --> hello
将二进制文件转换为可执行文件,链接阶段是在转换之前,去找到相应头文件的函数实现,连接目标代码,生成可执行程序
4 宏的使用
4.1 宏实现字符串连接
#define CONNECT_STR(STR1,STR2) STR1##STR2
4.2 宏返回两数最大值
//错误案例 #define MAX(NUM1,NUM2) (NUM1>NUM2?NUM1:NUM2) //正确方式 #define MIN(X,Y) ({\ typeof (X) x_ = (X);\ typeof (Y) y_ = (Y);\ (x_ < y_) ? x_ : y_; })
4.3 宏实现日志输出
#define PRINT_DEFINE(STR) printf("%s", #STR)
4.4 编多行宏和测试
要求:宏函数放到自定义命名空间下,调用代码放到另一个命名空间下
//名字空间用小写字母命名 namespace definition{ //宏命名:采用下划线法,并且所有字母大写 #define ADD(X, Y) \ {\ std::cout << (X + Y) << std::endl; \ } }//多行宏函数定义的命名空间 namespace call{ void Add(int x, int y){ ADD(x, y); } }//多行宏函数调用的命名空间 int main() { call::Add(1, 2); system("pause"); return 0; }
5 基础数据类型的使用
5.1 基础类型内存布局
数据类型 | 内存大小 | 布局 | 范围 |
bool | 一字节 | 数值位 | 0~1 |
char | 一字节 | 数值位 | -128~+127 |
int | 四字节 | 符号位+数值位 | -231~231-1 |
long | 四字节 | 符号位+数值位 | -231~231-1 |
浮点数单独说明
数据类型 | 内存大小 | 布局 |
float | 4 | 1(符号位)+ 8(指数位)+ 23(尾数位) |
double | 8 | 1(符号位) + 11(指数位)+ 52(尾数位) |
以float举例,下图为内存布局样式
以十进制为例说明指数位和尾数位,10.01 = 1.001*10^1(科学计数法)
那么易推想,浮点数的最大值数值为:符号位0+指数位全1+尾数位全1
但是实际上并不是这样的,因为IEEE 建立了浮点数计算的技术标准,规定了指数位范围为-126 ~ +127,即0111 1111表示指数位是127 - 127 = 0,而且指数位0000 0000和1111 1111有特殊含义
综上所述:指数位的 最大值:11111110 (float) 最小值:00000001
那么,不考虑符号位,浮点数的最大值&最小值,二进制表示为
【最大值】11111110 (指数位)+ 全是1(尾数位)
【极小值】00000001(指数位)+ 最后一位为1(尾数位)
5.2 编程求出最值
//double最大值 long long max_indices_bit = 0xffc; max_indices_bit = max_indices_bit >> 1; max_indices_bit = max_indices_bit << 52; long long max_tail_bit = 0xfffffffffffff; long long max_res = max_indices_bit | max_tail_bit; double* max_res_double = (double*)&max_res; std::cout << *max_res_double << "\n"; std::cout << DBL_MAX; //double最小值 long long min_indices_bit = 0xffc; min_indices_bit = min_indices_bit >> 1; min_indices_bit = min_indices_bit << 52; long long min_tail_bit = 0xfffffffffffff; long long min_sign_bit = 1; min_sign_bit = min_sign_bit << 63; long long min_res = min_sign_bit | min_indices_bit | min_tail_bit; double* min_res_double = (double*)&min_res; std::cout << *min_res_double << "\n";
6 结构体的使用
6.1 结构体内存布局
struct Stc1{ //4 int a; }; struct Stc2{ //16 int a; double b; }; struct Stc3{ //24 int a; double b; char c; }; struct Stc4{ //24 int a; double b; char c; char d; }; struct Stc5{ //24 int a; double b; char c[6]; char d; long long e; };
在VS2013,对以上代码编译(命令 /d1 reportAllClassLayout ),可以在输出窗口看到结构体的内存结构打印信息,我们挑选结构最简单的Stc1和结构最复杂的Stc5来分析,如下图所示:
实际上打印信息已经将对齐成员和对大小告诉我们了,经过多个打印信息的分析,我们可以找到内存对齐的规律:
1、C++结构体占用内存的只有成员变量和虚函数表
2、C++默认对齐大小为结构体内最大的基础类型大小
因此不同的变量排列方式会改变结构体占用空间的大小,建议变量从大到小或从小到大排列,这样空间会达到最优
扩展:结构体里使用位结构
位结构是把一个字节中的二进位划分为几个不同的区域,并声明每个区域的位数和位名,这样就可以把几个不同的变量用一个字节的二进制位域来表示
作用:为了节省存储空间,处理简便
我们先来看看下面两个结构体定义:
struct Stc1 { char a:2; char b:3; char c:1; }; struct Stc2 { char a:2; char b:3; char c:7; };
打印结构体的大小,结果为:
sizeof(struct Stc1) = 1 sizeof(struct Stc2) = 2
结果出于意料,按照正常的内存对齐规则, 这两个结构体大小应该均为3。
以上现象说明带有位结构的结构体并不是按照每个域对齐的,而是将位结构成员捆在一起做对齐的。
以Stc1为例,这个结构体中所有的成员都是char型的,而且三个位域占用的总空间为6 bit < 8 bit(1 byte),这时编译器会将这三个成员’捆绑’在一起做对齐,并且以最小空间作代价
而Stc2同上,三个成员类型都是char型,但是三个成员位域所占空间之和为9 bit > 8 bit(1 byte),由于位结构是不能跨越两个基本类型空间的,这时编译器将a和b两个成员捆绑按照char做对齐,而c单独拿出来以char类型做对齐
我们再看一种结构体定义:
struct Stc3 { char a:2; char b:3; int c:1; };
打印结构体的大小,结果为:
sizeof(struct Stc3) = 8
结果出于意料,在Stc3中三个位域所占用空间之和为6 bit < 8 bit(1 byte),按照上面的分析,结构体大小应该为是 1 才对。为什么会出现这种情况呢?原因在于char和int的对齐系数是不同的,所以不能捆绑在一起做内存对齐
我们从结构体内存结果打印信息可以看出:编译器把 a 和 b '捆绑’在一起,占用一个字节。然后按照内存对齐的两大规律之一默认对齐大小为结构体内最大的基础类型大小,内存对齐三个字节,最后的 int 类型变量 c 占用四个字节
结论:使用位结构的结构体内存大小应该 = 同类型变量捆绑 + 内存对齐两大规律计算
6.2 什么是内存对齐
计算机是按位进行存取数据的,以32位的计算机为例,CPU每次读取四个字节在一个数据块寻址,变量在内存中存储时也是按照这样的对齐规则储存的,它可以提升CPU对内存读写的效率,提高程序的性能
6.3 结构体初始化方式
//结构体定义 struct Student { //成员列表 std::string name; int age; int score; }; int main() { //结构体初始化方式1 struct Student stu1; //struct 关键字可以省略 stu1.name = "Alice"; stu1.age = 18; stu1.score = 99; //结构体初始化方式2 顺序赋值 struct Student stu2 = { "Alice", 18, 99 }; //结构体初始化方式3 乱序赋值 struct Student stu3 = { .name = "Alice", .age = 18 , .score = 99 }; system("pause"); return 0; }
6.4 结构体访问的方式
//结构体定义 struct Student { //成员列表 std::string name; int age; int score; }; int main() { Student student; Student *stu_pointer; //给指针变量申请地址空间 stu_pointer = new Student(); //3.1 结构体变量名直接访问 student.age = 18; //3.2 指针变量访问 stu_pointer->age = 18; //3.3 指针结构体变量访问 //可以看成是前两种的结合 (*stu_pointer).age = 18; system("pause"); return 0; }