题记:本系列学习笔记(C++ Primer学习笔记)主要目的是讨论一些容易被大家忽略或者容易形成错误认识的内容。只适合于有了一定的C++基础的读者(至少学完一本C++教程)。
作者: tyc611, 2007-01-13
本文主要讨论C++中关于变量及其类型的一些讨论。
如果文中有错误或遗漏之处,敬请指出,谢谢!
算术类型(arithmetic type)
C++标准规定了每个算术类型的最小存储空间,但允许编译器使用更大的存储空间。事实上,对于int型,几乎现在所有的编译器使用的存储空间都比所要求的大(也表明,在编写程序时不能对这些类型的大小做任何假设)。C++算术类型最小存储空间规定如下:
(注:要特别注意int型,并且这里没有long long类型,基本上所有编译器都有这个扩展)
类型 |
含义 |
最小存储空间 |
bool |
布尔型 |
- |
char |
字符型 |
8位 |
wchar_t |
宽字符型 |
16位 |
short |
短整型 |
16位 |
int |
整形型 |
16位 |
long |
长整型 |
32位 |
float |
单精度浮点型 |
6位有效数字 |
double |
双精度浮点型 |
10位有效数字 |
long double |
扩展精度浮点型 |
10位有效数字 |
- 整型(integral type)
表示整数、字符和布尔值的算术类型合称为整型(与浮点型相对应)。
- 字符型(char)
有三种类型:plain char, signed char, unsigned char,但只有两种表示方式:signed char和unsigned char。plain char用这两个表示方式之一来表示,至于用那种方式来表示取决于编译器,是实现定义的。
signed与unsigned类型
C++标准并没有定义signed类型如何用位来表示,编译器通常用其中一位作为符号位来表示。
对于unsigned 整型(unsigned char,unsigned short int, unsigned int, unsigned long int),编译器会将越界值对2^n取模(n为该类型的位数)使其满足大小要求。因此,unsigned整型在计算时不会发生溢出。
例如:(假设char类型为8位,2^8 = 256)
unsigned char ch = 300; 等价于 unsigned char ch = 44 (300 mod 256)
unsigned char ch = -1; 等价于 unsigned char ch = 255 (-1 mod 256)
而对于越界的signed类型赋值的情形,标准没有明确说明,也就是未定义行为。很多编译器采用与unsigned类型一样的处理方式。
字面值(literal, or literal constant)
只有内置类型存在字面值,每个字面值都有相应的类型。例如:0是int型,2.718是double型。
整形字面值
整型字面值的类型依赖于它的形式、值和类型后缀:
- 如果它是十进制形式并且没有类型后缀,那么它的类型是下面第一个能表示它的类型:int、long int。
- 如果它是八进制或十六进制形式并且没有类型后缀,那么它的类型是下面第一个能表示它的类型:int,unsigned int,long int, unsigned long int。
- 如果它有类型后缀u或者U,那么它的类型是下面第一个能表示它的类型:unsigned int, unsigned long int。
- 如果它有类型后缀l或者L,那么它的类型是下面第一个能表示它的类型:long int, unsigned long int。
- 如果它有类型后缀ul,lu,uL,Lu,Ul,lU,UL或者LU,那么它的类型是unsigned long int。
如果一个字面值不能够被它所有可能类型所表示,那么将产生未定义行为。
字符串字面值(string literal)
形如"..."或者L"..."的字符串。
不是以L开头的字符串字面值,叫作平凡字符串字面值(ordinary string literal),也叫作窄字符串字面值(narrow string literal)。对于平凡字符串字面值,其类型是const char[num],静态存储区,并用所给字符串初始化。
以L开头的字符串字面值,例如:L"foo",是宽字符串字面值。它的类型是const wchar_t[num],静态存储区,并用所给字符串初始化。
(注意上面的num值为所给字符串中字符个数 + 1)
所有字符串字面值的存储是否是不重叠的,这个问题是由实现定义的(implementation-defined)。试图去修改字符串字面值的行为所产生的结果是未定义的。
在编译时,相邻的窄字符串字面值被连接成一个窄字符串字面值,相邻的宽字符串字面值也被连接成一个宽字符串字面值。如果一个窄字符串字面值和一个宽字符串字面值相邻,其结果是未定义了。
变量及其类型
C++没有规定变量名的长度。
一些特殊的标识符为实现专用的保留标识符:
1)包含两个连续下划线的标识符以及以下划线开头紧跟一个大写字母的标识符。
2)在函数外定义的以下划线开头的标识符。
初始化
初始化有两种方式:
1)拷贝初始化(copy-initialization),例如:int var = 6;
2)直接初始化(direct-initialization),例如:int var(6);
两种方式对于内置类型是没区别的,而对于类类型是有区别的,详见构造函数章节。
extern与const
如果extern声明有初始化表达式,那么它被认为是定义,而忽略extern。例如:
extern int var = 6; //definition (只有当extern 声明位于函数外部时,才可以含有初始化表达式)
|
const限定符
在全局作用域内声明的const变量为内部连接属性,即是文件域变量,所以可以把const变量定义放在头文件中(如果这样做,那么在每个包含该头文件中的文件中都会有一个该变量的定义,这样可能造成存储空间的大量浪费。事实上,当const变量是用常量表达式初始化时,大部分编译器在编译时都会用相应的常量表达式替换这些const变量的任何使用。也就不会有空间用于这类const变量的存储)。
可以指定const变量为extern,从而使其变成外部连接属性,可以在整个程序中访问const对象(需要在定义和使用处都用extern声明)。如果定义了一个extern const变量,那么在其它文件中就不能再在全局域定义该变量(一个例外是,可以定义一个内部连接属性的const变量)。例如:
// file1.cpp extern const int var = 6; // file2.cpp extern const int var; //legal: refer to the var in file1 // file3.cpp const int var = 9; //legal: internal linkage, only used in this file // file4.cpp extern const int var = 2; //illegal: redefinion of var (already defined in file1) // file5.cpp int var; //illegal: redefinion of var (already defined in file1)
|
非const引用只能绑定到与该引用同类型的对象;const引用则可以绑定到不同但相关的类型(该类型能够转换到引用的类型)的对象、甚至绑定到右值。例如:
int var = 6; //int& r = 9; // error! const int& r1 = 9; // ok const int& r2 = var + r1; // ok
|
为什么const引用则可以绑定到不同但相关的类型的对象、甚至绑定到右值呢?
因为如果是绑定到不同类型的对象或右值,会先生成一个与引用类型相同的临时对象(必要时进行类型转换),然后引用变量再与该临时对象绑定。由于临时对象是右值,所以只能进行const引用。
另外,还可以从另一个角度来解释,先看下面的例子:
double var = 2.718; const int& r = var;
|
上面的代码等价于:
double var = 2.718; int tmp = var; const int& r = tmp;
|
如果r不是const引用,那么可以修改r的值。这样做,事实上是修改了临时变量tmp的值,而与程序员试图修改var的值的意愿完全偏离。这种错误是很难察觉的。而const引用不能修改,就完全避免了这种错误的发生。
枚举类型
枚举类型的对象的初始化或赋值只能通过其枚举成员或同一枚举类型对象进行。
当需要整形常量时,用枚举类型(匿名枚举类型)代替#define预处理命令是一个不错的选择。
如果文中有错误或遗漏之处,敬请指出,谢谢!
参考资料:
[1] C++ Primer(Edition 4)
[2] International Standard:ISO/IEC 14882:1998