C++基础碎碎念
如果想在多个文件之间共享 const
对象,必须在变量得定义之前添加 extern
关键字。
一般来说,如果你认定变量是一个常量表达式,那就把它声明成 constexpr
类型。
auto
类型说明符: C++11引入 auto
来让编译器替我们去分析表达式所属的类型。
注意, auto
让编译器通过初始值来推算变量的类型,显然 auto
定义的变量必须有初始值。
使用 auto
也能在一条语句中声明多个变量,因为一条声明语句只能有一个基本数据类型,所以
该语句中所有变量的初始基本数据类型都必须一样。
decltype
类型指示符:有时会遇到这种情况:希望从表达式的类型推断出要定义的变量的类型,
但是不想用该表达式的值初始化变量。为了满足这一要求C++11引入了第二种类型说明符 decltype
,它的作用是
选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值:
decltype(f()) sum = x;
// su的类型就是函数f的返回类型
预处理器:预处理变量有两种状态:已定义和未定义。 #define
指令把一个名字设定为预处理变量,
另外两个指令则分别检查某个指定的预处理变量是否已经定义: #ifdef
当且仅当变量已定义时为真,#ifndef
当且仅当
变量未定义时为真,则执行后续操作直到遇到endif
指令为止。
使用getline读取一整行:有时我们希望能在最终得到的字符串中保留输入时的空白符,这时应该用getline函数
代替原来的>>运算符。getline函数的参数是一个输入流和一个string对象,函数从给定的输入流读取内容,直到遇到换行符为止(注意
,换行符也被读进来了)。
string::size_type类型:string的size函数返回值的类型是size_type而不是int。string类及其它大多数
标准库都定义了几种配套的类型。显然size_type是无符号类型。如果一条表达式中已经有了size()
函数就不要再使用int了,这样可以避免混用int和unsigned可能带来的问题。
如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环。
另一个限制是任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象
的迭代器失效。
拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型。const_iterator和常量指针差不多,
能读取但不能修改它所指的元素值。相反,iterator的对象可读可写。
在使用数组下标的时候,通常将其定义为size_t类型。size_t是一种机器相关的无符号类型,
它被设计得足够大以便能表示内存中任意对象的大小。
显式转换:
static_cast:任何具有明确定义的转换,只要不包含底层const,都可以使用static_cast。例如,通过将
一个运算对象强制转换成double类型就能使表达式执行浮点数除法:
double slope = static_cast<double>(j) / i;
当需要把一个较大的算术类型赋值给较小的类型时,static_cast非常有用。此时,强制转换告诉程序的读者
和编译器:我们知道且不在乎潜在的精度损失。一般来说,如果编译器发现一个较大的算术类型试图赋值给较小的
类型,就会给出警告信息;但是当我们执行了显式的类型转换后,警告信息就会被关闭了。
static_cast对于编译器无法自动执行的类型转换也非常有用,例如,我们可以使用static_cast找回
存在于void*指针中的值:
void* p = &d;
double *dp = static_cast<double*>(p);
当我们把指针放在*void中并且使用static_cast将其强制转换回原来的类型时,应该确保指针的值保持不变,
也就是说,强制转换的结果与原始的地址相等,因此我们必须确保转换后所得的类型就是指针所指的类型。类型一旦不符,
将产生未定义的后果。
const_cast:只能改变运算对象的底层const。对于将常量对象转换成非常量对象的行为,
我们一般称其为“cast away the const”。一旦我们去掉了某个对象的const性质,编译器就不再阻止我们对该对象
进行写操作了。如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。反之会产生未定义的后果。
值域const_cast能改变表达式的常量属性,使用其它形式的命名强制类型转换改变表达式的常量属性都将引发编译器错误。
同样的,不能使用const_cast改变表达式的类型。
reinterpret_cast:通常为运算对象的位模式提供较低层次上的重新解释。reinterpret_cast
本质上依赖于机器。要想安全地使用reinterpret_cast必须对涉及的类型和编译器实现转换的过程都非常了解。
continue语句终止最近的循环中的当前迭代并立即开始下一次迭代。
throw表达式:程序的异常检测部分使用throw表达式引发一个异常。throw表达式包含关键字throw和紧随其后的一个表达式,
其中表达式的类型就是抛出的异常类型。throw表达式后面通常紧跟一个分号,从而构成一条表达式语句。
try语句块:try语句块的通用语法形式是:
try{
program-statements} catch (exception-declaraction){
handler-statements} catch (exception-declaraction){
handler-statements}//...
跟在try块之后的是一个或多个catch子句。catch子句包括三部分:关键字catch、括号内一个对象的声明(exception declaration)
以及一个块。当选中了某个catch子句处理异常之后,执行与之对应的块。catch一旦完成,程序跳转到try语句块最后一个catch子句之后的
那条语句继续执行。
函数重载:如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载(overloaded)函数。
这些函数接收的形参类型不一样,但是执行的操作非常类似。当调用这些函数时,编译器会根据传递的实参类型推断想要的是哪个函数。main
函数不能重载。对于重载函数来说,它们应该在形参数量或形参类型上有所不同。不允许两个函数除了返回类型外其它所有的要素都相同。
调试帮助:
assert预处理宏 所谓的预处理宏其实是一个预处理变量,它的行为有点类似于内联函数。assert宏
使用一个表达式作为它的条件:assert(expr);
首先对expr求值,如果表达式为假(0),assert输出信息并终止程序的执行。
如果表达式为真(非0),assert什么也不做。assert宏常用于检查“不能发生”的条件。例如,一个对输入文件进行操作的程序可能要求
所有给定单词的长度都大于某个threshold。此时,程序中可以包含一条如下的语句:
assert(word.size() > threshold);
NDEBUG预处理变量 assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。
默认状态下没有定义NDEBUG,此时assert将执行运行时检查。
成员函数通过一个this的额外的隐式参数来访问调用它的对象,在成员函数内部,我们可以直接使用调用该函数的对象的成员,
而无须通过成员访问运算符来做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看做this的隐式引用。对于我们来说,
this形参是隐式定义的。实际上,任何自定义名为this的参数或变量的行为都是非法的。我们可以在成员函数体内部使用this。
访问控制和封装:定义在public
说明符之后的成员在整个程序内可被访问,public
成员定义类的接口。
定义在private
说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private
部分封装了类的实现细节。
=default的含义 如果我们需要默认的行为,那么可以通过在参数列表后面写上=default
来要求编译器生成构造函数。
其中,=default既可以和声明一起出现在类的内部,也可以作为定义出现在类的内部。和其他函数一样,如果=default在类的内部,则默认构造函数是
内联的,反之则不是内联的。
struct
和 class
的默认访问权限不太一样。类可以在它的第一个访问说明符之前定义成员,对这种成员的访问权限依赖于类定义的方式。
如果我们使用 struct
关键字,则定义在第一个访问说明符之前的成员是 public
的;相反,如果我们使用 class
关键字,则这些成员是 private
的。
出编程风格的考虑,当我们希望定义的类的所有成员是public
时,使用 struct
;反之,如果希望成员是 private
的,使用 class
。
类可以允许其它类或函数访问它的非公共成员,方法是令其他类或函数成为它的友元(friend)。如果类想把一个
函数作为它的友元,只需要增加一条friend关键字开始的函数声明语句即可。友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。
友元不是类的成员也不受它所在区域访问控制级别的约束。友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户
能够调用某个友元函数,那么我们就必须在友元声明之外再专门对函数进行一次声明。