目录
1. 原始内置类型
C++中包含了两种原始类型:
- 算术类型(arithmetic types)
- 特殊类型void :很少用,一般用作函数无返回值。
1.1 算术类型
算术类型分为两类:整数类型(integral types) 和 浮点类型(floating-point types)。在不同的机器上,整数类型占的位是不同,最小的位数要求如下表:
对于整数类型来说,除了bool和扩展的字符类型(wchar_t, char16_t, char32_t),整数类型还分为有符号(signed)和无符号(unsigned)。:
- 有符号:int、short、long、long long。
- 无符号:在类型前加 unsigned。
注:对于char类型分为:char、signed char、unsigned char,但是一般char与后面两个类型之一相同,由编译器决定。一般一个8-bit的unsigned char能够保存值的范围是0-255。
对于用什么类型有些规则:
- 当知道值是非负的话用unsigned 类型。
- 对于整型,用int,当数据值太大时,直接用long long,因为实际情况下long的大小会与int一样。
- 在算术表达式中不要使用char和bool。
- 对于浮点计算的话用double。
1.2 类型转换
一些类型支持的运算可以进行类型的转换
bool b=42; //b is true
int i=b; //i=1
i=3.14; //i=3
double pi=i; //pi=3.0
unsigned char c=-1; //假定8-bit chars,c=255
signed char c2=256; //假定8-bit chars,c2的值不确定
//Converting a negatve number to unsigned
unsigned u=10;
int i=-42;
std::cout<<i+i<<std::endl; // prints -84
std::cout<<u+i<<std::endl; // if 32-bit ints, prints 4294967264
对于上面的转换,当我们给一个无符号类型的变量赋值时,如果值超过其范围,变量保存的结果就是将原始值对目标类型所能保存的值求余。例如上面的 c 保存的值就是255(-1%256)。当我们给一个有符号的变量赋值时,如果值超过其范围,变量保存的结果就未定义了,会产生不确定性。在使用unsigned类型要特别小心,特别是在循环中,防止出现意外。
2. 变量
变量在定义初始化的方法中有个列表初始化的形式,但是对于内置类型的变量在使用列表初始化有个限制:编译器不允许缩窄转换的初始化。
long double ld = 3.1415926536;
int a{ld}, b={ld}; //error:narrowing conversion required
int c(ld), d=ld; //ok:value will be truncated
为了能够分开编译,C++区分了声明和定义:
- 声明:利用extern关键字让程序知道这个变量,指定了变量的类型和名字。
- 定义:创建一个相关联的实体,变量的定义就是一个声明,定义还分配内存以及给变量初始值。
extern int i; //声明但没有定义 i
extern double pi = 3.1416; //定义。但不允许在函数内部给extern进行初始化
int j; //声明并定义了
对于内置型类型,在全局区域没有初始化会被初始化为0,在局部区域没有初始化有未定义值。
3. 复合类型
3.1 引用
引用为对象定义了一个别名。
int ival = 1024;
int &refVal = ival; // refVal refers to ival
引用必须初始化,初始化后引用绑定到初始化对象,无法改变去绑定其他对象。引用不是一个对象,不能定义为一个引用去定义一个引用。
3.2 指针
指针是指向另一种类型的复合类型,间接的获得其他对象,与引用不同的是,指针是个对象,能够赋值和复制,指针能指向不同的对象,在定义时可以不进行初始化。
double dval;
double *pd = &dval; //ok:初始化是一个double的地址
double *pd2 = pd; //ok:初始化是一个指向double的指针
存储在指针中的值有四个形式:
- 指向对象。
- 指向对象的末尾。
- 空指针。
- 除了上面三种的无效值。
int ival = 1024;
int *pi = 0; //pi is a valid, null pointer, nonzero pointer
int *pi2 = &ival; //pi2 is a valid pointer that holds the address of ival
if (pi) //pi has value 0, so condition evaluates as false
if (pi2) // pi2 points to ival, so it is not 0; the condition evaluates as true
//对于相同类型的两个有效指针,当它们的地址一样两个指针相等,否则不相等。
void *Pointers 是一个特殊的指针类型能够保存任何对象的地址。还有需要注意的就是
3.2.1 指针的指针:
int ival = 1024;
int *pi = &ival; //pi points to an int
int **ppi = π //ppi points to a pointer to an int
牢记一点:指针保存的是地址。
3.2.2 指针的引用
因为指针是个对象,所以可以定义一个指针的引用。
int i = 42;
int *p;
int *&r = p; //r is a reference to the pointer p, beacuse the type of reference is required same with objective, so int * is represent the type of reference.
r = &i; // r refers to a pointer; assigning &i to r makes p point to i
*r = 0; //changes i to 0
我们可以通过从右到左读定义来理解r的类型。&与r组合&r,得到r是个引用,剩余部分就是r引用的类型,* 说明r引用的是一个指针类型,最终这个声明就是r是一个指向int类型的指针的引用。
4 const限定词
我们可以通过const让变量不可改变。在创建const对象时必须进行初始化,否则报错,在编译时const对象被常量初始化,编译器会将变量直接替换成对象的常量值,因此为了编译器需要看到变量的初始化,但是当一个程序分为多个文件,每个利用const的文件就都需要看到它的初始化,即变量要定义在每个文件中,为了满足这种需求,同时避免多个相同变量的定义,const变量是被定义为文件本地的。当我们想在多个文件共享一个const 变量,利用关键词extern在它的定义和声明上。
//file_1.cc defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
//file_1.h
extern const int bufSize; //same bufSize as defined in file_1.cc
4.1 const引用(references to const)
const的引用不能改变它所绑定的对象。
const int ci = 1024;
const int &r1 = ci; //ok:both reference and underlying object are const
r1 = 42; //error: r1 is a reference to const
int &r2 = ci; // error: non const reference to a const object
//因为我们不能直接给ci赋值,因此我们也不能利用引用去改变ci
一般情况下,引用的类型必须与引用的对象类型一致,但是有两个特例:
- 我们能够用任何表达式初始化const引用,只要表达式能转换为引用的类型。
int i = 42;
const int &r1 = i; // we can bind a const int& to a plain int object
const int &r2 = 42; // ok: r1 is a reference to const
const int &r3 = r1*2; // ok: r3 is a reference to const
int &r4 = r * 2; // error: r4 is a plain, non const reference
通过下面一个例子仔细的探究发生了什么:
double dval = 3.14;
const int &ri = dval;
//ri是int的引用,而dval是个浮点数,为了确保ri绑定到int对象,编译器进行类型如下转换
const int temp = dval; // create a temporary const int from the double
const int &ri = temp; // bind ri to that temporary
- 当ri不是const会如何?如果能够进行初始化,因为ri不是const,所以可以进行赋值,程序员这样就能通过ri改变所绑定对象的值,但是这里实际绑定的是一个临时变量,并非程序员所想的dval对象,所以会被判定为非法语言。
特别提醒:一个const引用仅仅限制我们通过引用所做的操作,将const的引用绑定到对象不会说明基础对象本身是否为const。
int i = 42;
int &r1 = i; // r1 bound to i
const int &r2 = i; // r2 also bound to i; but cannot be used to change i
r1 = 0; // r1 is not const; i is now 0
r2 = 0; // error: r2 is a reference to const
4.2 指针和const
我们可以定义指向const或者non const类型的指针。
一般情况下,指针的类型要和所指对象的类型保持一致。
const double pi = 3.14;
double *ptr = π // error: ptr is a plain pointer
const double *cptr = π // ok: cptr may point to a double that is const
*cptr = 42; // error: cannot assign to *cptr
我们能够利用pointer to const的指针去指向一个non const类型的对象:
double dval = 3.14; // dval is a double; its value can be changed
cptr = &dval; // ok: but can't change dval through cptr
就像对const的引用一样,指向const的指针并没有说明指针所指向的对象是否为const。只影响通过指针所做的操作。
与引用不同,指针是个对象,我们能够对指针做const,就和其他的const对象一样,一个const pointer必须被初始化,一旦被初始化后,它的值(所保存的地址)就不能再改变。
int errNumb = 0;
int *const curErr = &errNumb; // curErr will always point to errNumb
const double pi = 3.14159;
const double *const pip = π // pip is a const pointer to a const object
5 constexpr和常量表达式
常量表达式:值不能够改变,在编译时就能得出。我们可以通过constexpr关键字去声明一个变量,要求编译器确认这个变量是个常量表达式。
const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression
constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size(); // ok only if size is a constexpr function
当我们在constexpr声明中定义指针时,constexpr说明符将应用于该指针,而不是该指针所指向的类型。
const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int
6 处理类型
6.1 类型别名
两种方式:typedef或者using =
注意指针,常量和类型别名混在一起时的情况
typedef char* pstring; //an alias for the type char*
const pstring cstr = 0; // cstr is a constant pointer to char
const pstring *ps; // ps is a pointer to a constant pointer to char
上面代码中的const pstring是一个指向char的constant指针。并不是一个指向const char的指针。
6.2 auto和decltype
auto可以告诉编译器从初始化值推出类型
对于引用,实际用的是引用的对象进行推导,当我们使用引用作为初始项时,初始项是对应的对象。
int i = 0, &r = i;
auto a = r; // a is an int (r is an alias for i, which has type int)
对于const类型,auto通常忽略top-level const(本身是const),而low-level const(指向const对象的指针)会推出:
const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int*(& of a const object is low-level const)
//想让推出的类型有top-level const,必须声明
const auto f = ci; // deduced type of ci is int; f has type const int
decltype 返回其操作数的类型
decltype(f()) sum = x; // sum has whatever type f returns
对于和const的用法与auto不一样,返回的类型包括top-level const和引用:
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z; // error: z is a reference and must be initialized
我们也可以对表达式用decltype,得到表达式的类型,有些表达式可能让decltype产生一个引用类型:
// decltype of an expression can be a reference type
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int
decltype(*p) c; // error: c is int& and must be initialized
上面代码中,当我们解引用一个指针时,我们得到指针所指的对象,更重要的是我们能够给这个对象赋值,因此推出的类型是int &。
注意:
// decltype of a parenthesized variable is always a reference
decltype((i)) d; // error: d is int& and must be initialized
decltype(i) e; // ok: e is an (uninitialized) int
参考来源
- C++ Primer, Fifth Edition