本文共 11373 字,大约阅读时间需要 37 分钟。
小驼峰法,tableName (除第一个单词外,每个单词首字母大写)
大驼峰法,TableName (所有单词首字母大写,其余小写) 下划线法,table_name(所有字母小写,用下划线连接) 类型名和变量名一般为名词,例 num_errors 函数名一般为动明结合(大驼峰法且动词在前名词在后),例OpenFile()文件名全部小写,可包含下划线或连字符。
C++文件以.cpp结尾,例如 hash_table.cpp,头文件以.h结尾,例如hash_table.h。 文件名用途明确,可依据其实现的主要类名来命名,且通常.cpp和.h文件成对出现所有的class/struct, 类型定义(typedef), 枚举(enum)均使用大驼峰法命名
普通变量:采用下划线法,例:string table_name
全局变量:少用为好,如果用以g为前缀,例:int g_table_name
struct 成员变量和普通变量一样,采用下划线法
class 成员变量要以下划线结束,如果是静态成员变量,加s_前缀
class TableSchema {private: string table_name_; static string s_column_name_; };
包括枚举和const常量:名称前加小写k,且除去开头的 k 之外每个单词开头字母均大写
const char kServiceName[] = “webserv”;enum{ kUnused, kEof, kError};
头文件通常用来写类的声明 、函数原型、#define 常数等,但是很少会写出具体的实现和细节。就好比Java
中的抽象类一样
#ifndef CIRCLE#define CIRCLEclass Circle{public: Circle(); double Area();private: double r_;};#endif
源文件主要实现头文件中已经声明的函数
注意:开头必须
#include
实现的头文件
#include "circle.h"#define PI 3.14Circle::Circle(){ r_ = 5.0;}double Circle::Area(){ return PI*r_*r_;}
1、系统头文件用尖括号,这样编译器会在系统文件目录下查找。 例:#include <xxx.h>
#include “xxx.h”
举例:已知头文件
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
中找到实现的函数
重复引用:就是同一个头文件(.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
代码编译流程分为四个环节,以HelloWorld
代码为例
#includeint main(){ printf("hello world!"); return 0;}
hello.cpp --> hello.i
#include
的内容插入 总结:头宏替换,注释消除,头文件插入,生成
.i
文件
hello.i --> hello.s
将预处理后的文件转换成汇编语言,生成.s
文件,.s
文件是汇编文件,其内容是汇编指令
hello.s --> hello.o
将汇编文件转换为目标文件,生成.o
文件,.o
文件是二进制文件,其内容是二进制机器码
hello.o --> hello
将二进制文件转换为可执行文件,链接阶段是在转换之前,去找到相应头文件的函数实现,连接目标代码,生成可执行程序
#define CONNECT_STR(STR1,STR2) STR1##STR2
//错误案例#define MAX(NUM1,NUM2) (NUM1>NUM2?NUM1:NUM2)//正确方式#define MIN(X,Y) ({\ typeof (X) x_ = (X);\ typeof (Y) y_ = (Y);\ (x_ < y_) ? x_ : y_; })
#define PRINT_DEFINE(STR) printf("%s", #STR)
要求:宏函数放到自定义命名空间下,调用代码放到另一个命名空间下
//名字空间用小写字母命名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;}
数据类型 | 内存大小 | 布局 | 范围 |
---|---|---|---|
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(尾数位)
//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";
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 占用四个字节
结论:使用位结构的结构体内存大小应该 = 同类型变量捆绑 + 内存对齐两大规律计算
计算机是按位进行存取数据的,以32位的计算机为例,CPU每次读取四个字节在一个数据块寻址,变量在内存中存储时也是按照这样的对齐规则储存的,它可以提升CPU对内存读写的效率,提高程序的性能
//结构体定义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;}
//结构体定义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;}
共用体使不同的类型变量共享同一段内存单元,所以共用体只能存储一个数据成员的值,
共用体的大小等于最大成员的大小。在程序中改变共用体的一个成员值,其它成员值也随之改变大小端,大小字节序,高低字节序,或Big&Little Endian,表示数据在内存中的存放模式
大端:网络字节序 小端:主机字节序
低字节序:低字节保存在内存的低地址中
高字节序:低字节保存在内存的高地址中
#include//共用体:int checkByteOrder(){ union { int data; char flag; }subject; subject.data = 1; return (subject.flag == 1);};int main(){ if (checkByteOrder() == 1){ printf("当前系统为低字节序\n"); } else{ printf("当前系统为高字节序\n"); } system("pause"); return 0;}
枚举,本质上是一组被命名的常数集合
枚举变量,由枚举类型定义的变量// 定义枚举类型kWeek// 包括枚举和const常量:名称前加小写k,且除去开头k外每个单词开头字母均大写enum Week {kSun, kMon, kTue, kWed, kThu, kFri, kSat};
// 定义枚举变量Week weekday1, weekday2;enum {kSun,kMon,kTue,kWed,kThu,kFri,kSat} weekday1, weekday2;
注意:枚举变量大小,等于枚举类型所占内存大小
枚举类型本身是不占内存的,但是枚举值占用内存的。枚举默认是用int类型来存储的,32位系统下占4个字节。#define 定义的常量,在预编译时替换。而enum定义的常量,是在运行时替换,根据标识去常量区获取对应的值
可以通过继承方式改变枚举的大小,例如:
//TypeChar 类型变量大小占用1字节enum TypeChar : unsigned char{};
具体实现:
enum EnumInt{ kMon = 1, kTue = 2, kWed = 3};enum EnumChar : unsigned char { kRed = 0x00, kYellow, kBule = 0xff};//sizeof(EnumInt) = 4//sizeof(EnumChar) = 1
扩展:枚举的范围
如果某个枚举符的值均为非负值,该枚举的取值范围就是[0~2^k-1]
如果存在负的枚举符值,则该枚举的取值范围就是[-2k~2k-1]
k 是能使所有枚举值位于此范围内最小2的幂
举例1:
enum kE1{ a=2, b=4 };
kE1均为正值,能使所有枚举值位于此范围内最小2的幂为 3 ,因此k
的值为2,于是kE1的取值范围为[0,7]
举例2:
enum kE2{ a=-2, b=4 };
kE2有正有负,那么满足条件的k
的值为3,kE1的取值范围是[-8,7]
名称 | 内存大小 | 布局 | 说明 |
---|---|---|---|
指针 | 四个字节 | 二进制数据储存 | 是用来控制内存地址的变量,它指向单个对象的地址 |
引用 | 四个字节 | 二进制数据储存 | 引用是一个指针常量 |
指针数组 | 元素所占内存的大小 | 与元素的储存结构有关 | 是一个只包含指针元素的数组 |
数组指针 | 四个字节 | 二进制数据储存 | 是一个指针变量,它指向一个数组 |
指向指针的指针 | 四个字节 | 二进制数据储存 | 是一个指针链。第一个指针包含了第二个指针的地址,第二个指针指向实际值 |
说明:以上内存大小均为 32 bit 位系统
深拷贝:被访问的数据,在每个单元中都有自己的一份
浅拷贝:两个函数通过拷贝它们共享的数据指针来工作
#include//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题class Person {public: explicit Person(const int height) { p_height_ = new int(height); } Person(const Person& p) { //p_height_ = p.p_height_; p_height_ = new int(*p.p_height_); } ~Person() { if (p_height_ != NULL){ delete p_height_; } }private: int* p_height_;};int main() { Person p1(180); Person p2(p1); system("pause"); return 0;}
explicit关键字:被修饰的类构造函数,不能进行隐式类型转换,如下图所示:
扩展:new/delete 和 malloc/free 区别1、malloc/free是C/C++标准库的函数,new/delete是C++操作符
2、返回值类型不同:new操作符内存分配成功时, 返回的是对象类型的指针,而malloc返回的是void*指针, 需要强转将指针转换为所需类型
3、定制内存大小不同:malloc/free需要手动计算类型大小,而new/delete编译器可以自己计算类型大小
4、分配失败时返回值:new内存分配失败时会直接抛bac_alloc异常, 它不会返NULL, malloc分配内存失败时返回NULL
5、malloc/free只是动态分配内存空间/释放空间,而new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)
static、register、extern都是用来变量存储说明的关键字,选择适当的存储分类符,不仅能够提高变量的访问效率,而且还能使内存的存储空间更有效
auto 中文释义:自动的
作用:使变量在离开作用域自动销毁,一般情况下可省略
#includeint main(){ auto int a = 2; //离开main函数,变量a自动销毁 return 0;}
static 中文释义:静态的
作用:定义的变量只能接受一次初始化,不能接受第二次
#includevoid func(){ static int a = 10; a++; std::cout << a << std::endl;}int main(){ func(); //初始化a为10,输出a为11 func(); //不接受初始化,a为11,输出a++,等于12 system("pause"); return 0;}
register 中文释义:寄存器
寄存器类型,寄存器是计算机内距离CPU最近的容器,将变量尽可能定义在寄存器里,使变量读取更加快速,提高程序效率。缺点是寄存器空间有限, 常用于频繁使用的变量
是否一定会放在寄存器里
register int often_use_num = 0;
extern 中文释义:外部变量
它属于变量声明,extern int n
和int n
的区别就是,告知编译器,int
类型变量n
定如有调用去其它文件中查找定义
//call.cpp#includevoid func(){ extern int n; std::cout << n << std::endl;}int main(){ func(); system("pause"); return 0;}//definition.cppint n = 101;
const 中文释义:常量
指定一个语义约束,编译器会强制实施这个约束,使某值保持不变的
场景:修饰普通变量,指针变量,参数传递和函数返回值
const int val = 5;//const修饰的是指针,指针指向可以改,指针指向的值不可以更改const int * p1 = &a; p1 = &b; //正确//*p1 = 100; 报错//const修饰的是常量,指针指向不可以改,指针指向的值可以更改int * const p2 = &a;//p2 = &b; //错误*p2 = 100; //正确
volatile 中文释义:不稳定的
使用 volatile 声明的变量,系统总是重新从它所在内存读取数据,简单地说就是防止编译器对代码进行优化
场景:多任务环境下各任务之间的共享标志
#includevoid main(){ volatile int i = 10; int a = i; // 下面汇编语句的作用就是改变内存中 i 的值 // 但是又不让编译器知道 printf("i = %d", a); __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i = %d", b);}
Release 模式下,变量i
声明是否声明volatile
对结果有显著影响
变量未声明volatile
时:编译器对代码进行了优化,输出结果不正确
volatile
时:关键字发挥了作用 volatile
的意义在于多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程可见 在c/c++中,引入inline修饰符,为了解决一些频繁调用的小函数大量消耗栈内存问题,表示为内联函数
#include//函数定义为inline即:内联函数inline char* dbtest(int a) { return (i % 2 > 0) ? "奇" : "偶";} int main(){int i = 0;for (i=1; i < 100; i++) { printf("i:%d 奇偶性:%s /n", i, dbtest(i)); }}
内联函数作用就是在每个for
循环内部任何调用dbtest(i)
的地方都换成了(i%2>0)?”奇”:”偶”
,避免了频繁调用函数对栈内存重复开辟带来的消耗
相比普通函数,内联函数效率更高,因为内联函数通过复制代码,节省了函数调用的时间
inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句,例如while、switch,并且部分编译器不支持递归内联
以下情况使用inline
不会起作用:
内联函数可以被调试,而宏函数不可以
宏定义没有类型检查,无论对错都是直接替换
内联函数在编译时会进行类型检查
宏定义不是函数,而是在预编译的时候把宏名用宏体替换,本质就是字符串替换
而内联函数本质上是一个函数,有参数列表和返回值,它在编译进行代码插入,编译器会在调用内联函数的地方,把函数内容展开,省去函数调用开销,来提高效率
转载地址:http://wzozi.baihongyu.com/