1. C++基本算术类型
类型 | 含义 | 最小存储空间 |
bool | 布尔型 | - |
char | 字符型 | 8位 |
wchar_t | 宽字符型 | 16位 |
short | 短整型 | 16位 |
int | 整型 | 16位 |
long | 长整型 | 32位 |
float | 单精度浮点型 | 6位有效数字 |
double | 双精度浮点型 | 10位有效数字 |
long double | 扩展精度浮点型 | 10位有效数字 |
上表是C++标准规定的算术类型基本要求,但实际编译器使用的存储空间一般比所要求的大。
对于内置类型,变量是否自动初始化取决于变量定义的位置。在函数体外定义的变量都初始化为0,而在函数体内定义的变量是不进行自动初始化的。
C++支持两种初始化变量的方式:复制初始化(copy-initialization)和直接初始化(direct-initialization)。如下所示:
char cmark('Z');
int ival(222);
int ival2 = 222;
应当注意的一点是:在C++中“初始化不是赋值”。初始化指创建变量并给它赋初始值,而赋值是擦除对象的当前值并用新值代替。
我们初始化上述变量时使用的是字面值常量,实际上使用其他变量或const常量都可以进行初始化,如下:
int iVal = 222;
const int cIVal = 222;
int mVal(iVal), mVal2(cIVal);
const int cValue1 = iVal;
const int cValue2(GetIntVal()); //OK: direct-initialization using function
const int cValue3 = GetIntVal(); //OK
无论是用作初始化的还是被初始化的,都可以使用const或一般的变量,甚至是函数未知的返回值,可见对于内置类型来说,其初始化是非常自由的。
2. 引用类型
引用是一种复合类型(compound type),通过在变量名前添加"&"符号来定义。引用在定义时必须初始化,而且必须用与该引用同类型的对象初始化。下面是引用初始化的一个小例子:
int iRefVal; //error: a reference must be initialized
extern int &iRefVal; //ok: a reference declaration
int iVal = 222;
int &iRef = iVal; //ok: iRef refers to iVal
int &mRefVal = GetIntVal(); //error: can't initialize
把const变量与引用类型联系起来分析,会发现:
(1)把const对象绑定到普通的引用是不合法的,即普通引用只能绑定普通变量
(2)const引用可以绑定到普通和const对象
int iValue = 222;
const icValue = 222;
int &iRef = iValue; //ok
int &iRef = icValue; //error
const int &icRef = iValue; //ok
const int &icRef = icValue; //ok
注:const引用是指“指向const对象的引用”
另外,非常细微的一点:非const引用只能绑定到与该引用同类型的对象,而const引用则可以绑定到不同但相关的类型的对象或绑定到右值。
int iVal = 222;
int rValueRef = 222; //error: illegal for non-const reference
double dRef = iVal; //error
const int &rValueRef = 222; //ok
const double &dRef = iVal; //ok
3. 指针的初始化
对指针进行初始化或赋值只能使用以下四种类型的值:
(1)0值常量表达式
(2)类型匹配的对象的地址
(3)另一对象之后的下一地址
(4)同类型的另一个有效指针
允许使用从C语言集成下来的预处理器变量NULL来初始化指针为0值:
int *pi = NULL;
const与指针
(1)指向const对象的指针
在C++中,如果指针指向const对象,则不允许用指针来修改其所指对象的值,其格式如下:
const double *cptr; // cptr may point to a double that is const
需要注意的是,const限定了cptr指针所指向的对象类型,而并非cptr本身。如果需要的话,允许给cptr重新赋值,使其指向另一个const对象,并且也允许把非const对象的地址赋给指向const对象的指针。
double dval = 3.14;
cptr = &dval; // ok: but can't change dval through cptr
(2)const指针
const指针本身不能修改,即不能使该指针指向其他对象。
int errNum = 0;
int errNum2 = 1;
int *const curErr = &errNum; // defines a constant pointer
curErr = &errNum2; // error: curErr is const
指针本身是const的事实并没有说明是否能使用该指针修改它所指对象的值。例如:
*curErr = -1; // change errNum value to -1
const double pi = 3.14159;
double *const pi_ptr = π //error
const double *const pi_ptr2 = π //ok: a const pointer points to a const object
但在测试中发现,const指针指向const对象会产生编译错误,因此如果是想指针本身和所指对象都是const的话,不妨考虑指向const对象的const指针的写法。
4. C++类的初始化
类背后蕴涵的思想是数据抽象和封装。C++类可以没有成员,也可以定义多个成员,成员可以是数据,函数或类型别名,而类的初始化指的是类中数据成员的初始化。
只要创建类类型的对象,就会执行构造函数。关于构造函数的详细解释需要参考原书,本文只对涉及初始化的部分进行详细阐述。
(1) 构造函数初始化列表
构造函数是类的特殊成员函数,它可以包含一个初始化列表来对类中数据成员进行初始化。构造函数初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式,并且这些初始化式只能在构造函数定义中而不是声明中指定。
// Sample class borrowed from C++ primer
class Sales_item {
public:
Sales_item() : units_sold(0), revenue(0.0){}
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
// constructor out of class, using a constructor initializer
Sales_item::Sales_item(const string &book) :
isbn(book), units_sold(0), revenue(0.0) {}
从概念上讲,可以认为构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。计算阶段由构造函数的函数体中所有语句组成。因此初始化其实在进入构造函数语句体之前就已经发生了:使用初始化列表初始化成员变量,或对未显式提及的成员变量采用标准变量的初始化规则。可以得到的是,类类型的数据成员总是在初始化阶段初始化,不管它是否在初始化列表中显式初始化。上例中Sales_item的默认构造函数中并没有指定数据成员isbn,但它在进入函数体之前已经被初始化了。
下面介绍几种特殊的数据成员初始化:
引用类型: 我们知道,引用必须在定义的时候初始化,如果一个类含有引用类型的数据成员初始化时会发生什么呢?
class Vector {
public:
Vector(const double (&axis)[3]) :
x(axis[0]), y(axis[1]), z(axis[2])
{}
const double x;
const double y;
const double z;
};
class Distance {
public:
Distance(Vector startP, Vector endP) : start_point(startP), end_point(endP)
{}
double GetValue();
private:
Vector &start_point;
Vector &end_point;
};
上例中,Distance类中的引用Vector类类型在构造函数初始化列表中必须显式初始化。
指针类型:指针类型可以看做内置类型,
const成员:也必须使用初始化式
另外,对于没有默认构造函数的类类型也必须使用初始化列表进行显式初始化
也可以这样想:const对象和引用类型的对象都是只能初始化,而不能赋值的,因此必须要在初始化阶段,即进入构造函数的函数体之前,完成初始化。
(2)隐式类类型转换和explicit
class Sales_item {
public:
Sales_item(const std::string &book):
isbn(book), units_sold(0), revenue(0.0) {}
};
// method same_isbn() expects a Sales_item object
string null_book = "9-999-99999-9";
item.sam_isbn(null_book);
Sales_item类定义了一个形参为string的构造函数,在期待一个Sales_item类对象的地方,可以使用一个string对象。
实际上,编译器会进行一次隐式的转换:执行接受一个string参数的Sales_item构造函数,创建一个临时的对象,并将该对象传递给调用者。我们构造了一个在测试完就丢弃的临时对象,这个行为几乎肯定是一个错误。
解决方案:explicit
可以通过将构造函数声明为explicit,来防止在需要隐式转换的上下文中使用构造函数:
class Sales_item {
public:
explicit Sales_item(const std::string &book):
isbn(book), units_sold(0), revenue(0.0) {}
};
注意:关键字explicit只能用于类内部的构造函数声明上,不能在类的定义体外部出现。
此时,需要显式使用构造函数来生成转换:
item.same_isbn(null_book); // error: string constructor is explicit
item.same_isbn(Sales_item(null_book)); // ok
参考书籍:C++ primer