人就是这样子, 自己造囚牢, 关着自己; 自己也做上帝, 自己来崇拜.
生存真实一件可怜的事. --<边城>
目录
第2章 | C++入门
2.1 C++关键字
- C++98定义了63个关键字
2.1.1 数据类型相关
(1)bool、true、false
- bool即为布尔类型,属于基本类型中的整数类型,取值为真和假。
- true和false是具有布尔类型的字面量,为右值,即表示真和假。
- 字面量:用于表达式源代码中一个固定值的表示法。
(2)char、wchar_t
- 表示字符型和宽字符型这些整数类型(属于基本类型),但一般只专用于表示字符。char(和signed char、unsigned char一起)事实上定义了字节的大小。char表示单字节字符,wchar_t表示多字节字符。
(3)int、double、float、short、long、signed、unsigned
- signed和unsigned作为前缀修饰整数类型,分别表示有符号和无符号。
- signed和unsigned修饰char类型,构成unsigned char和signed char,和char都不是相同的类型;不可修饰wchar_t、char16_t和char32_t。其它整数类型的signed省略或不省略,含义不变。signed或unsigned可单独作为类型,相当于signed int和unsigned int。
- double和float专用于浮点数,double表示双精度,精度不小于float表示的浮点数。long double则是C++11指定的精度不小于double的浮点数。
(4)explicit
- 该关键字的作用就是避免自定义类型隐式转换为类类型。
(5)auto
- auto关键字会根据初始值自动推断变量的数据类型。
2.1.2 语句相关
(1)switch、case、default
- switch分支语句的起始,根据switch条件跳转到case标号或defalut标记的分支上。
(2)do、for、while
- 循环语句的组成部分,C和C++都支持这三种循环。
(3)if和else
- 条件语句的组成部分。if表示条件,之后else表示否定分支。
(4)break、cotinue、goto
- break用于跳出for、while循环或switch语句。continue用于调到一个循环的起始位置。goto用于无条件跳转到函数内得标号处。一般情况不建议使用goto,风险较大。
2.1.3 定义、初始化相关
(1)const、volatile(易变的,不稳定的)
- const和volatile是类型修饰符,语法类似,用于变量或函数参数声明,也可以限制非静态成员函数。const表示只读类型(指定类型安全性,保护对象不被意外修改),volatile指定被修饰的对象类型的读操作是副作用(因此读取不能被随便优化合并,适合映射I/O寄存器等)。
- volatile是“易变的”、“不稳定”的意思。volatile是C的一个较为少用的关键字,它用来解决变量在“共享”环境下容易出现读取错误的问题。
- volatile 的应用场景:<1>并行设备的硬件寄存器;<2>一个中断子服务程序中访问到的变量;<3>多线程应用中被多个任务共享的变量。
(2)enum
- 构成枚举类型名的关键字。
(3)export
- 使用该关键字可实现模板函数的外部调用。对模板类型,可以在头文件中声明模板类和模板函数;在代码文件中,使用关键字export来定义具体的模板类对象和模板函数;然后在其他用户代码文件中,包含声明头文件后,就可以使用该这些对象和函数。
(4)extern
- 当出现extern “C”时,表示 extern “C”之后的代码按照C语言的规则去编译;当extern修饰变量或函数时,表示其具有外部链接属性,即其既可以在本模块中使用也可以在其他模块中使用。
(5)public、protected、private
- 这三个都为权限修饰符。public为公有的,访问不受限制;protected为保护的,只能在本类、派生类和友元中访问;private为私有的,只能在本类和友元中访问。
(6)template - 声明一个模板,模板函数,模板类等。模板的特化。
(7)static
- 可修饰变量(静态全局变量,静态局部变量),也可以修饰函数和类中的成员函数。static修饰的变量的周期为整个函数的生命周期。具有静态生存期的变量,只在函数第一次调用时进行初始化,在没有显示初始化的情况下,系统把他们初始化为0。
(8)struct、class、union
- 用于类型声明。class是一般的类类型。struct在C++中是特殊的类类型,声明中仅默认隐式的成员和基类访问限定与class不同(struct是public,class是private)。union是联合体类型。满足特定条件类类型——POD struct或POD union可以和C语言中的struct和union对应兼容。
- 注:POD类型(Plain Old Data),plain—代表普通类型,old—代表可以与C语言兼容。
(9)mutable
-
mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。
-
10.virtual
-
声明虚基类,虚函数。虚函数=0时,则为纯虚函数,纯虚函数所在的类称为抽象类。
2.1.4 系统操作I相关
(1)catch、throw、try
- 用于异常处理。try指定try块的起始,try块后的catch可以捕获异常。异常由throw抛出。throw在函数中还表示动态异常规范。
(2)new、delete
- new、delete属于操作符,可以被重载。new表示向内存申请一段新的空间,申请失败会抛出异常。new会先调用operator new函数,再在operator new函数里调用malloc函数分配空间,然后再调构造函数。delete不仅会清理资源,还会释放空间。delete先调用析构函数,其次调用operator delete函数,最后在operator delete函数里面调用free函数。malloc申请内存失败会返回空。free只是清理了资源,并没有释放空间。
(3)friend
- 友元。使其不受访问权限控制的限制。例如,在1个类中,私有变量外部是不能直接访问的。可是假如另外1个类或函数要访问本类的1个私有变量时,可以把这个函数或类声明为本类的友元函数或友元类。这样他们就可以直接访问本类的私有变量。
(4)inline
- 以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,将所调用的函数代码直接嵌入到主调函数中,没有函数压栈的开销,内联函数提升程序运行的效率。
- 特性1:inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
- 特性2: inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
- 特性3:inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
(5)operator
- 和操作符连用,指定一个重载了的操作符函数,比如,operator+。
(6)register
- 提示编译器尽可能把变量存入到CPU内部寄存器中。
(7)typename
- typename关键字告诉编译器把一个特殊的名字解释为一个类型。
2.1.5 命名相关
(1)using
- 在当前文件引入命名空间,例using namespace std;在子类中使用,using声明引入基类成员名称。
(2)namespace
- C++ 标准程序库中的所有标识符都被定义于一个名为 std 的 namespace 中。命名空间除了系统定义的名字空间之外,还可以自己定义,定义命名空间用关键字 “namespace”,使用命名空间时用符号“::”指定。
- 使用目的:对标识符的名称进行本地化,以避免命名冲突或名字污染。
(3)typedef
- typedef声明,为现有数据类型创建一个新的名字。便于程序的阅读和编写。
2.1.6 函数和返回值相关
(1)void
- 特殊的"空"类型,指定函数无返回值或无参数。
(2)return
- return表示从被调函数返回到主调函数继续执行,返回时可附带一个返回值,由return后面的参数指定。return通常是必要的,因为函数调用的时候计算结果通常是通过返回值带出的。如果函数执行不需要返回计算结果,也经常需要返回一个状态码来表示函数执行的顺利与否(-1 和 0 就是最常用的状态码),主调函数可以通过返回值判断被调函数的执行情况。
(3)sizeof
- 返回类型名或表达式具有的类型对应的大小。
(4)typeid
- typeid是一个操作符,返回结果为标准库中类型的引用。
2.1.7 其他
(1)this
- 每个类成员函数都隐含了一个this指针,用来指向类本身。this指针一般可以省略。但在赋值运算符重载的时候要显示使用。静态成员函数没有this指针。
(2)asm
- asm是一个语句的分隔符。不能单独出现,必须接汇编指令。一组被大括号包含的指令或一对空括号。
(3)*_cast(const_cat/dynamic_cast/reinterpret_cast、static_cast)
- C++类型风格来性转换。const_cast删除const变量的属性,方便赋值;dynamic_cast用于将一个父类对象的指针转换为子类对象的指针或引用;reinterpret_cast将一种类型转换为另一种不同的类型;static_cast用于静态转换,任何转换都可以用它,但他不能用于两个不相关的类型转换。
2.1.8 C、C++ 中0、NULL、nullptr
// C语言表示
#define NULL (void*)0
char* p = NULL;
// C++表示
#define NULL 0
char* p = NULL;
char* p = nullptr;
- NULL是一个宏定义,在c和c++中的定义不同,c中NULL为(void*)0,而c++中NULL为整数0。
- nullptr是一个字面值常量,类型为std::nullptr_t,空指针常数可以转换为任意类型的指针类型。
2.2 命名空间
2.2.1 定义
- (1)在C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字 污染,namespace关键字的出现就是针对这种问题的;
- (2)定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名 空间的成员。
- (3)普通的命名空间;
- (4)命名空间可以嵌套;
- (5)同一个工程中允许存在多个相同名称的命名空间;
- (6)注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于被该命名空间。
2.2.2 命名空间的使用
- (1)加命名空间名称以及作用域限定符;
- (2)使用using将命名空间中的成员引入;
using N::B; - (3)使用using namespace命名空间名称引入;
using namespace N;
2.3 C++输入&输出
2.3.1 cin&cout
2.3.2 说明
- (1) 使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间;
- (2)注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件 即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文 件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用 +std的方式;
using N::B; - (3)使用C++输入输出更方便,不需增加数据格式控制,比如:整形–%d,字符–%c。
using namespace N;
2.4 缺省参数
2.4.1 缺省参数的定义
- 缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
2.4.2 缺省参数的分类
- (1)全缺省参数
每个参数都带有默认值; - (2)半缺省参数
部分参数带有缺省值。
2.4.3 注意
- (1) 半缺省参数必须从右往左依次来给出,不能间隔着给 ;
- (2) 缺省参数不能在函数声明和定义中同时出现;
- (3)缺省值必须是常量或者全局变量 ;
- (4) C语言不支持(编译器不支持)。
2.5 函数重载(function overloading)
2.5.1 定义
- 是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或顺序)必须不同,即对一个函数名赋予它新的含义,使一个函数名可以多用,也就是“一物多用”,常用来处理实现功能类似数据类型不同的问题;
2.5.2 名字修饰(Name Mangling)
- (1)Name Mangling是一种在编译过程中,将函数、变量的名称重新改革的机制,简单来说就是编译器为了区分各个函数,将函数通过某种算法,重新修饰为一个全局唯一的名称;
- (2)C语言的名字修饰规则:非常简答,只是在函数名字前面添加了下划线;
- (3)C++的名称修饰规则:修饰名字由?开头,接着是函数名由“@”符号结尾的函数名,后面跟着“@”结尾的类名“C”和名称空间“N”,再一个“@”表示函数的名称空间结束,第一个“A”表示函数调用类型为“_cdecl”,接着是函数的参数类型及返回值,由“@”结束,最后由“Z”结尾;
例如:?func@@YAHH@Z
2.5.3 exetern “C”
- 有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加exetern“C”,意思是告诉编译器,将该函数按照 C 语言规则来编译;
2.5.4 为什么C语言不支持
- C语言中编译器对函数名字的修饰规则非常简单:只是在函数名字前增加了一个下划线;
如果工程中存在同名的函数时,编译器就无法区分调用那个函数;
2.6 auto关键字(C++11)
2.6.1 定义
- C/C++定义:使用auto修饰的变量,是具有自动存储器的局部变量;
- C++11中定义:auto不再是一个存储类的指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推到而得;
- 注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始来推导化表达式auto的实际类型,因为auto并非是一种“类型”的声明,而是一个类型声明的“占位符”,编译器在编译器会将auto替换为变量实际的类型;
2.6.2 auto的使用细则
- auto与指针和引用结合起来使用。用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型则必须加&;当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量;
2.6.3 auto不能推导的场景
- (1)auto不能作为函数的参数;
- (2)auto不能直接用来声明数组;
- (3) 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法;
- (4) auto在实际中常见的优势用法就是跟以后会用到的C++11提供的新式for循环,还有lambda表达式等进行配合使用;
- (5)auto不能定义类的非静态成员变量;
- (6)实例化模板时不能使用auto作为模板参数。
2.7 基于范围的for循环(C++11)
2.7.1 范围for的语法
- (1)C++11中 引入了基于范围的for循环,for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围。
- (2)注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
2.7.2 范围for的使用条件
- (1)for循环迭代的范围必须是确定的;对于数组而言,就是数组中第一个元素和后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围;
- (2)迭代的对象要实现++和==的操作。
2.8 指针空值–nullptr(C++11)
2.8.1 C++98中的指针空值
- 在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
2.8.2 nullptr与nullptr_t
- (1)为了考虑兼容性,C++11并没有消除常量0的二义性,C++11给出了全新的nullptr表示空值指针。C++11为什 么不在NULL的基础上进行扩展,这是因为NULL以前就是一个宏,而且不同的编译器厂商对于NULL的实现可能不太相同,而且直接扩展NULL,可能会影响以前旧的程序;
- (2)为了避免混淆,C++11提供了 nullptr,即:nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转化为指针类型,nullptr_t被定义在头文件中:
typedef decltype(nullptr) nullptr_t;
2.8.3 注意
- (1)在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- (2) 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- (3)为了提高代码的健壮性,在后续表示指针空值时建议好使用nullptr。
2.9 引用
2.9.1 定义
- 引用(reference)是C++对C的一个重要扩充,引用是一个新的变量类型,是给已经存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
2.9.2 引用一般形式
- 1.通过对象名和成员运算符访问对象中的成员(一般形式:对象名.成员名);
- 2.通过指向对象的指针访问对象中的成员(例如:p = &t);
- 3.通过对象的引用变量来访问对象中的成员;类型 & 引用变量名(对象名) = 引用实体(int& ra = a; 定义引用类型);
2.9.3 引用特性
- 1.引用在定义时必须初始化;
- 2.一个变量可以有多个引用;
- 3.引用一旦引用一个实体,再不能引用其他实体。
2.9.4 使用场景
(1)做参数
- 1.需要通过形参改变外部实参,T&;
- 2.不需要通过形参改变外部实参,const T&;
(2)做返回值
- 1.返回全局变量;
- 2.返回 static 类型的变量;
- 注意:引用作为函数返回值,一定不能返回函数栈上的空间,即离开函数作用域之后,其栈上的空间已经还给系统,返回结果的生命周期不受函数控制(实体的生命周期闭函数长)。
2.9.5 传值、传址的比较
传值
- 优点:函数的副作用不会影响外部的实参;
- 缺点:传参效率低,不能通过形参改变外部的实参。
传址
- 优点:传参效率高,可以通过形参改变外部实参;
- 缺点:不安全,可读性差。
2.9.6 引用和指针的区别
相同点
- 1.在语法概念上引用就是一个别名,没有独立空间,和引用实体共用同一块内存空间;
- 2.在底层实现上实际是有空间的,因为引用是按照指针方式来实现的;
不同点
- 1.引用在定义时必须初始化,而指针在定义时没有和这个要求;
- 2.引用再初始化时引用一个实体后,就不能在引用其他实体,而指针可以任何时候指向任何一个同类型的实体;
- 3.没有 NULL 引用,但有 NULL 指针;
- 4.在 sizeof 中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占空间字节个数(32位操作系统下是占4个字节,64位操作系统下是占8个字节);
- 5.引用自加即引用的实体加1,指针自加即指针向后偏移一个类型的大小;
- 6.有多级指针,但是没有多级引用;
- 7.访问实体方式不同,指针需要显示解引用,引用编译器自己处理;
- 8.引用比指针使用起来相对更安全。