博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++基础入门
阅读量:3958 次
发布时间:2019-05-24

本文共 11373 字,大约阅读时间需要 37 分钟。

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 CIRCLEclass Circle{public:	Circle();	double Area();private:	double r_;};#endif

2.2 源文件

源文件主要实现头文件中已经声明的函数

注意:开头必须#include实现的头文件

#include "circle.h"#define PI 3.14Circle::Circle(){	r_ = 5.0;}double Circle::Area(){	return PI*r_*r_;}

2.3 源文件关联头文件

1、系统头文件用尖括号,这样编译器会在系统文件目录下查找。 例:#include <xxx.h>

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.cppb.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 
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;}

7 共用体的使用

7.1 共用体的内存布局

共用体使不同的类型变量共享同一段内存单元,所以共用体只能存储一个数据成员的值,

共用体的大小等于最大成员的大小。在程序中改变共用体的一个成员值,其它成员值也随之改变

7.2 判断系统字节序

大小端,大小字节序,高低字节序,或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;}

8 枚举的使用

8.1 枚举&枚举变量

枚举,本质上是一组被命名的常数集合

枚举变量,由枚举类型定义的变量

// 定义枚举类型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;

注意:枚举变量大小,等于枚举类型所占内存大小

8.2 枚举的内存布局

枚举类型本身是不占内存的,但是枚举值占用内存的。枚举默认是用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]

9 指针与引用

9.1 指针&引用

名称 内存大小 布局 说明
指针 四个字节 二进制数据储存 是用来控制内存地址的变量,它指向单个对象的地址
引用 四个字节 二进制数据储存 引用是一个指针常量
指针数组 元素所占内存的大小 与元素的储存结构有关 是一个只包含指针元素的数组
数组指针 四个字节 二进制数据储存 是一个指针变量,它指向一个数组
指向指针的指针 四个字节 二进制数据储存 是一个指针链。第一个指针包含了第二个指针的地址,第二个指针指向实际值

说明:以上内存大小均为 32 bit 位系统

9.2 深拷贝&浅拷贝

深拷贝:被访问的数据,在每个单元中都有自己的一份

浅拷贝:两个函数通过拷贝它们共享的数据指针来工作

#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除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)

10 存储分类说明符

static、register、extern都是用来变量存储说明的关键字,选择适当的存储分类符,不仅能够提高变量的访问效率,而且还能使内存的存储空间更有效

10.1 auto

auto 中文释义:自动的

作用:使变量在离开作用域自动销毁,一般情况下可省略

#include 
int main(){ auto int a = 2; //离开main函数,变量a自动销毁 return 0;}

10.1 static

static 中文释义:静态的

作用:定义的变量只能接受一次初始化,不能接受第二次

#include 
void 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;}

10.2 register

register 中文释义:寄存器

寄存器类型,寄存器是计算机内距离CPU最近的容器,将变量尽可能定义在寄存器里,使变量读取更加快速,提高程序效率。缺点是寄存器空间有限, 常用于频繁使用的变量

是否一定会放在寄存器里

register int often_use_num = 0;

10.3 extern

extern 中文释义:外部变量

它属于变量声明,extern int nint n的区别就是,告知编译器,int类型变量n定如有调用去其它文件中查找定义

//call.cpp#include 
void func(){ extern int n; std::cout << n << std::endl;}int main(){ func(); system("pause"); return 0;}//definition.cppint n = 101;

11 存取限定符

11.1 const

const 中文释义:常量

指定一个语义约束,编译器会强制实施这个约束,使某值保持不变的

场景:修饰普通变量,指针变量,参数传递和函数返回值

const int val = 5;//const修饰的是指针,指针指向可以改,指针指向的值不可以更改const int * p1 = &a; p1 = &b; //正确//*p1 = 100;  报错//const修饰的是常量,指针指向不可以改,指针指向的值可以更改int * const p2 = &a;//p2 = &b; //错误*p2 = 100; //正确

11.2 volatile

volatile 中文释义:不稳定的

使用 volatile 声明的变量,系统总是重新从它所在内存读取数据,简单地说就是防止编译器对代码进行优化

场景:多任务环境下各任务之间的共享标志

#include 
void 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的意义在于多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程可见

12 inline关键字

12.1 引入inline关键字

在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)?”奇”:”偶”,避免了频繁调用函数对栈内存重复开辟带来的消耗

12.2 inline内联优缺点

相比普通函数,内联函数效率更高,因为内联函数通过复制代码,节省了函数调用的时间

inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句,例如while、switch,并且部分编译器不支持递归内联

以下情况使用inline不会起作用:

  1. 函数体内代码长,使用内联将导致内存消耗代价较高
  2. 函数体出现循环,执行函数体内代码的时间要比函数调用的开销大

12.3 宏函数&内联的区别

内联函数可以被调试,而宏函数不可以

宏定义没有类型检查,无论对错都是直接替换

内联函数在编译时会进行类型检查

宏定义不是函数,而是在预编译的时候把宏名用宏体替换,本质就是字符串替换

而内联函数本质上是一个函数,有参数列表和返回值,它在编译进行代码插入,编译器会在调用内联函数的地方,把函数内容展开,省去函数调用开销,来提高效率

转载地址:http://wzozi.baihongyu.com/

你可能感兴趣的文章
JDK工具
查看>>
JDK工具
查看>>
JNA-JNI升级版
查看>>
JNA-JNI升级版
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>
Android 下 JNI 开发
查看>>