CPP基础

C++是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程面向对象编程泛型编程,被认为是一种中级语言,它综合了高级语言低级语言的特点;C++是C的一个超集,任何合法的C程序都是合法的C++程序。使用静态类型的编程语言是在编译时执行类型检查而不是运行时执行类型检查;标准的C++由三个重要部分组成:

  • 核心语言:提供了所有构件块,包括变量数据类型常量
  • C++标准库:提供了大量的函数,用于操作文件字符串
  • 标准模板库STL:提供了大量的方法,用于操作数据结构

变量

在同一条定义语句中,可以使用先定义的变量值去初始化后定义的其他变量,在C++中初始化和赋值时两个完全不同的操作,初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义把对象的当前值擦除以一个新的值来替代,C++初始化有好几种不同的形式,用花括号{}来初始化变量是C++11新标准的一部份,这种初始化的形式被称为列表初始化,无论是初始化对象还是为对象赋新值都可以使用一组由花括号括起来的初始值:

1
2
3
4
int units_sold = 0;
int units_sold = {0};
int units_sold{0};
int units_sold(0);

当用于内置类型的变量时,若使用列表初始化且初始值存在丢失信息的风险,则编译器将报错:

1
2
3
long double ld = 3.141592653;
int a{ld}, b = {ld}; // 错误:转换未执行,因为存在丢失信息的危险
int c(ld), d = ld; // 正确:转换执行,且确实丢失了部分值

为了支持分离式编译,C++将声明定义区分开,声明使得名字为程序所知定义是负责创建与名字关联的实体;若想声明一个变量而非定义,可在变量名前加extern关键字;在函数体内若初始化一个由extern标记的变量将引发错误

1
2
3
extern int ival;  // 声明
int j; // 定义
extern int ival2 = 122; // 定义
const

默认情况下,const对象仅在文件内有效,当多个文件同时出现同名const变量时,等同于在不同文件中分别定义了独立的变量;若要多个文件共享const对象,必须在变量的定义之前添加extern关键字;某些时候const变量不是一个常量表达式,但需要文件间共享,可以在const变量声明和定义处都添加extern关键字

1
2
3
4
// file_1.h
extern const int bufSize;
// file_1.cc
extern const int bufSize = fcn();

const引用,对常量的引用不能被用作修改它所绑定的对象;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const int ci = 1024;
const int &r1 = ci;
r1 = 42; // 错误:r1是对常量的引用
int &r2 = ci; // 错误:不能让一个非常量引用指向一个常量对象

int i = 42;
const int &r1 = i;
const int &r2 = 42;
const int &r3 = r1 * 2;
int &r4 = r1 * 2; // 错误
int ik = r1 * 2;

int &r4 = i;
r4 = 30;
r1 = 20; // 错误

指向常量的指针,不能改变其所指向的值,允许一个指向常量的指针,指向一个非常量对象;允许把指针本身定义为常量,即常量指针;指针本身是一个对象,它可以指向另一个对象,指针本身是不是常量以及指针所指向是不是一个常量是两个相互独立的问题;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const double pi = 3.14;
double *ptr = π // 错误:ptr是一个普通指针
const double *cptr = π
*cptr = 8.88; // 错误:不能给*cptr赋值

double dval = 3.14;
cptr = &dval;

int errNumb = 0;
int *const curErr = &errNumb;
const double pi = 3.14;
const double *const pip = π // 不能修改pip存储的地址和其指向的对象

*curErr = 2.2; // 正确

顶层const表示指针本身是一个常量底层const表示指针所指向的对象是一个常量;顶层const可以表示任意的对象是常量;

1
2
3
4
5
6
int i = 0;
int *const p1 = &i; // 不能改变p1的值,顶层const
const int ci = 42; // 不能改变ci的值,顶层const
const int *p2 = &ci; // 允许改变p2的值,底层const

const int *const p3 = p2; // 靠右的const是顶层,靠左的const是底层
类型别名

有两种方式可以定义类型别名,传统方法是使用typedef关键字,新标准可以使用using关键字;

1
2
3
4
typedef double wages; // wages是double的同义词
typedef wages base, *p; // base是double的同义词,p是double*的同义词

using dalise = double;
auto

auto能让编译器通过初始值来推算,从而替我们去分析表达式所属类型,故auto定义的变量必须要有初始值;auto能在一条语句中声明多个变量,且只能有一个基本数据类型;auto一般会忽略顶层const保留底层const

1
2
3
4
5
6
7
8
9
10
11
auto sz = 0, pi = 3.14; // 错误:sz和pi类型不一致

auto &h1 = 42; // 错误
int &h2 = 42; // 错误
const auto &h3 = 42; // 正确
const int &h4 = 42; // 正确

int i = 0;
const int ci = i;

auto &n = i, *p2 = &ci; // 错误,i的类型是int,&ci的类型是const int
decltype

decltype的作用是选择并返回操作数的数据类型,编译器分析表达式并得到他的类型,但不实际计算表达式的值;decltype的表达式若是加上括号的变量,则结果则是引用;即decltype((veriable))的结果永远是引用,decltype(veriable)结果只有veriable本身就是一个引用时才是引用;

1
2
3
4
5
6
7
8
9
10
11
12
13
decltype(f()) sum = x;  // sun的类型是函数f返回的类型,编译器不实际执行函数f

const int ci = 0, &cj = ci;
decltype(ci) x = 0;
decltype(cj) y = x; // y的类型是const int&,y绑定到变量x
decltype(cj) z; // 错误:z是一个引用,必须初始化

int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // 正确:加法结果是int
decltype(*p) c; // 错误:c是int&,必须初始化

decltype((i)) d; // 错误:d是int&,必须初始化
decltype(i) e; // 正确:e是一个int
预处理器

确保头文件多次包含仍能安全工作的常用技术是预处理器,当预处理器遇到#include标记时就会用指定的头文件的内容代替#include;C++还会用到一项预处理功能即头文件保护符,头文件保护符依赖于预处理变量#define定义的变量,预处理变量已定义未定义两种状态;#ifdef当且仅当变量一定义时为正,#ifndef当且仅当变量未定义时为真,一旦检测到为真,则执行后续操作直到遇到#endif指令为止;

整个程序中预处理变量包括头文件保护符必须唯一,通常做法是基于头文件中类的名字来构建保护符名字,以确保唯一性,为了避免与程序中的其他实体发生名字冲突,一般预处理器变量名称全部大写

引用&指针

引用必须被初始化,初始化变量时,初始值会被拷贝到新建的对象中,但定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用;引用并非对象,只是为一个已存在的对象起的一个别名;

1
2
3
4
5
6
7
int ival = 1024;
int &refVal = ival;
int &refVal2; // 错误:引用必须被初始化
int &refVal3 = 10; // 错误:引用类型的初始值必须是一个对象

double dval = 3.14;
int &refVal4 = dval; // 错误:这里引用类型的初始值必须是int对象

指针是指向另一种类型的复合类型,其本身就是一个对象允许对指针赋值和拷贝,无需在定义时赋初值;和其他类型一样在块作用域内定义的指针若没有被初始化,将拥有一个不确定的值;指针存放某个对象的地址,若想获取地址,需使用取地址符&;声明语句中指针的类型实际上被用于指定它所指向对象的类型二者必须匹配;指针指向了一个对象允许用解引用符*来访问对象;引用本身不是一个对象,因此不能定义指向引用的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int ival 42;
int *p = &ival; // p存放变量ival的地址
int *p2 = p;
std::cout << *p;

double *p3 = p; // 错误:指针p3的类型和p类型不匹配

int *p4 = nullptr; // 等价于int *p4 = 0;
int *p5 = 0;
int *p6 = NULL; // 需要#include cstdlib,等价于int *p6 = 0;

int* p7, p8; // p7是指向int的指针,p8是int

int **p9 = &p; // p9是指向指针p的指针

int *&r = p; // r是一个对指针p的引用
r = &ival; // r引用了一个指针,这里其实就是令p指向了ival
*r = 0; // 将ival值改变为0

void*是一种特殊的指针类型,可用于存放任意对象的地址,但比较局限,只能用于和别的指针比较,作为函数的输入输出,或赋值给另一个void*指针,不能直接操作该指针指向的对象

数据类型

C++提供七种基本的数据类型,且一个基本类型可以使用一个或多个类型修饰符signedunsignedshortlong来修饰:

关键字类型具体类型占位范围备注
bool布尔型bool1字节
char字符型char1字节-128到127或0到255
unsigned char1字节0到255
signed char1字节-128到127
int整型int4字节-2^31到2^31-1
signed int4字节-2^31到2^31-1
unsigned int4字节0到2^32-1
short int2字节-2^15到2^15-1
signed short int2字节-2^15到2^15-1
unsigned short int2字节0到2^16-1
long int8字节-2^63到2^63-1
signed long int8字节-2^63到2^63-1
unsigned long int8字节0到2^64-1
float浮点型float4字节1位符号
8位指数
23位小数
double双浮点型double8字节1位符号
11位指数
52位小数
long double16字节
void无类型void表示类型的缺失
wchar_t宽字符型wchar_t24字节1个宽字符
1
2
3
4
5
6
7
// 获取某个类型的最大最小值
(numeric_limits<bool>::max)();
(numeric_limits<bool>::min)();
// 获取类型或变量所占字节数
sizeof(float);
int a =10;
sizeof(a);

枚举类型

枚举类型是C++中的一种派生数据类型,它是由用户定义若干枚举常量的集合,枚举类型的一般形式为,若枚举没有初始化即省掉=整型常数时,则从第一个标识符开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
标识符[=整型常数]
} [枚举变量];
// 默认第一个名称的值为0,第二个名称的值为1,第三个名称的值为2
enum color { red, green, blue } c, d, e; // 不管red是多少,c默认为0,但d和e并非0,且可同时定义多个枚举变量
c = blue; // c = 2
color c1(blue);

// 给名称赋予一个特殊的值,blue的值为6,默认每个名称都会比它前面一个名称大1,但red的值依然为0
enum color { red, green=5, blue };
color light, g, h; // 不管red是多少,light默认为0

light = 1; // 不可以,int不能转为枚举常量
light = 200; // 不可以, 200不是color枚举常量
red++; // 错误
color b = red + green; // 错误

int num = 0;
num = blue + 2; // 可以,枚举常量可以提升为int

作用域

函数一个代码块内部声明的变量称为局部变量,在函数参数的定义中声明的变量称为形式参数,在所有函数外部声明的变量称为全局变量局部变量被定义时系统不会对其初始化全局变量系统会自动初始化为下列值:

数据类型 初始化默认值
int 0
char ‘\0’
float 0
double 0
pointer NULL

常量

可使用#define 预处理器和const关键字两种方式定义常量#define宏定义其最大的特点是语义替换,其定义的常量值没有类型限定不做类型检查在宏出现的地方直接展开,在预处理阶段完成#define只是替换不会做表达式运算#define宏仅仅是展开,有多少地方使用就展开多少次,不会分配内存#define定义的宏常量在内存中有若干个拷贝

1
2
#define N 1 + 2 	// 预想的N值是3,我们这样使用N 
double a = N/2; // 预想的a的值是1.5,可实际上:a = 1+2/2 = 2 结果是2

const编译器编译运行时完成的,时间上define早于constconst是关键字它会在编译时检查数据类型,会在内存中分配,const定义的只读变量在程序运行过程中只有一份拷贝;就空间效率而言const优于define

常见关键字用法

asm

允许在C++程序中嵌入汇编代码

auto

存储类型标识符,但从C++ 17开始,auto关键字不再是C++存储类说明符,表明变量自动具有本地范围块范围的变量声明,如for循环体内的变量声明默认为auto存储类型;auto关键字用于声明变量时根据初始化表达式自动推断该变量的类型声明函数时函数返回值的占位符

1
2
3
4
auto f = 3.14;          // double
auto s("hello"); // const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3 = 'r'; // 错误,必须是初始化为同一类型
register

register声明的变量称为寄存器变量,在可能的情况下会直接存放在机器的寄存器中;但对32位编译器不起作用,当全局优化global optimizations打开时,它会选择是否放在自己的寄存器中;不过其它与register关键字有关的其它符号都对32位编译器有效;

explicit

显示的,作用是禁止单参数构造函数被用于自动类型转换

const_cast

用来修改类型的constvolatile属性,常量指针被转化成非常量指针,并且仍然指向原来的对象常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象,用法:

1
const_cast<type_id> (expression)
delete

释放程序动态申请的内存空间,delete后面通常是一个指针数组[],且只能delete通过new关键字申请的指针,否则会发生错误

dynamic_cast

动态转换,允许在运行时进行类型转换,从而使程序能够在一个类层次结构安全地转换类型,提供了把基类指针转换成派生类指针把指向基类的左值转换成派生类的引用两种转换方式

operator

用于操作符重载

typedef

typedef可以为一个已有的类型取一个新的名字,语法:typedef type newname; ,如typedef int feet;,即feet是int的另一个名称,然后则可以通过feet声明整型变量;

typeid

指出指针引用指向的对象的实际派生类型

typename

该关键字告诉编译器把一个特殊的名字解释成一个类型

extern

声明变量函数外部链接,即该变量或函数名在其它文件中可见被其修饰的变量静态分配空间的,即程序开始时分配,结束时释放,用其声明的变量或函数应该在别的文件同一文件的其它地方定义实现。在文件内声明一个变量或函数默认为可被外部使用。在C++中,还可用来指定使用另一语言进行链接,这时需要与特定的转换符一起使用,目前仅支持C转换标记,来支持C编译器链接,使用这种情况有两种形式:

1
2
extern "C" 声明语句
extern "C" { 声明语句块 }
export

为了访问其他编译单元中的变量对象,对普通类型包括基本数据类结构,可利用关键字extern,来使用这些变量或对象时;但模板类型则必须在定义这些模板类对象模板函数时,使用标准C++新增加的关键字export导出;

union

联合,类似于enum,不同的是enum实质上是int类型的,而union可用于所有类型,并且其占用空间是随着实际类型大小变化的

friend

友元,声明友元关系友元可以访问与其friend关系中的privateprotected成员,通过友元直接访问类中的privateprotected成员的主要目的是提高效率,友元包括友元函数友元类

inline

内联,函数的定义将在编译时调用处展开,inline函数一般由短小的语句组成,可以提高程序效率

mutable

易变的,只能用于非静态非常量数据成员,若需要在该类函数中类的数据成员进行赋值,这时就需要用到mutable关键字

reinterpret_cast

type-id必须是一个指针引用算术类型函数指针成员指针。它可以把一个指针转换成一个整数也可以把一个整数转换成一个指针,先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值;

1
reinpreter_cast<type-id> (expression)
sizeof

由于C++每种类型的大小都是由编译器自行决定的,为了增加可移植性,可以用sizeof运算符获得该数据类型占用的字节数

wchar_t

宽字符类型,每个wchar_t类型占2个字节16位宽,汉字的表示就要用到wchar_t

volatile

告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量,对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率;