第二章
1.当一个表达式中既有无符号数又有int值时,int值会转换成无符号数。
unsigned u = 10;
int i = -42;
std::cout << u+i <<std::endl;//如果int占32位,输出4294967264
2.如果两个字符串字面值位置紧邻且仅由空格,缩进和换行符分隔,则它们实际上是一个整体。
std::cout << "a really,really long string literal “
"that spans two lines " << std:: endl;
3.列表初始化
int units_sold = 0;
int units_sold = {0};
int units_sold{0};
int units_sold(0);
上面四条语句都可以做到初始化一个int变量。用花括号来初始化变量的方式称为列表初始化。
初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。
4.默认初始化
如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被始化为0.定义在函数体内部的内置类型变量将不被初始化。
定义于函数体内的内置类型的对象如果没有初始化,则其值未定义。类的对象如果没有显式地初始化,则其值由类确定。
5.变量声明与定义
为了支持分离式编译,C++语言将声明和定义区分开来。声明 让名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义 负责创建与名字关联的实体。变量声明规定了变量的类型和名字,而定义除此之外,还申请存储空间,也可能会为变量赋一个初始值。
extern int i; //声明i而不是定义i
int j; //声明并定义j
6.作用域
作用域能彼此包含,被包含的作用域称为内层作用域,包含着别的作用域的作用域称为外层作用域。
作用域中一旦声明了某个名字,它所嵌套着的所有作用域中都能访问该名字。同时允许在内层作用域中重新定义外层作用域已有的名字。
7.复合类型
一条声明语句由一个基本数据类型和紧随其后一个 声明符 列表组成。
- 引用 为对象起了另外一个名字,引用类型引用另外一种类型。通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名。
int ival = 1024;
int &refVal = ival; //refVal指向ival(是ival的另一个名字)
int &refVal2; //报错:引用必须被初始化。
一般在初始化变量时,初始值会被拷贝到新建的对象中。但是在定义引用时,程序把引用和它的初始值 绑定 在一起,而不是将初始值拷贝给引用。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。引用并非一个对象,只是一个已经存在的对象所起的另外一个名字。
引用只能绑定在对象上,而不能与字面值或某个表达的计算结果绑定在一起。
除了两种例外情况,其他所有引用的类型都要和与之绑定的对象严格匹配。
第一种:在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。
int i = 42;
const int &r1 = i;
const int &r2 = 42;
const int &r3 = r1 * 2;
double dval = 3.14;
const int &ri = dval;
int &r4 = r1 * 2; //error
编译器在这里把代码变成
const int temp = dval;
const int &ri = temp;
即ri绑定了一个临时量对象。临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。(也叫临时量)
-
指针 是”指向“另外一种类型的复合类型。相比于引用:指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象;指针无须在定义时赋初值。
空指针的生成方法:
int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL; //新标准下最好使用nullptr,避免使用NULL;
除了两种例外情况,其他所有指针的类型都要和它所指向的对象严格匹配。
第一种:允许令一个指向常量的指针指向一个非常量对象 。
void型指针可用于存放任意对象的地址。不能直接操作void指针所指的对象,因为并不知道这个对象到底是什么类型,无法确定能在这个对象上执行哪些操作。(可以与其它指针比较,作为函数的输入或输出,或者赋给另外一个void*指针。)
面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义
8.const限定符
const类型的对象只能执行不改变其内容的操作。
如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。
对const的引用可能引用一个并非const的对象。对const的引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量限定。因此对象也可能是个非常量,允许通过其他途径改变它的值。
int i = 42;
int &r1 = i;
const int &r2 = i;
r1 = 0;
r2 = 0; //error
上面代码虽然不允许通过r2修改i的值,但是可以通过r1修改,或者直接给i赋值。
指针和const:类似于对const的引用,指向常量的指针不能用于改变其所指对象的值。要存放常量对象的地址,只能使用指向常量的指针。
const double pi = 3.14;
double *ptr = π //error
const double *cptr = π
*cptr = 42; //error
允许一个指向常量的指针指向一个非常量对象:
double dval = 3.14;
cptr = &dval; //正确,但是不能通过cptr来改变dval的值。
常量指针(const pointer):常量指针必须初始化,一旦初始化完成,它的值(即存放的地址)就不能再改变了。
int errNumb = 0;
int *const curErr = &errNumb; //curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π //pip是一个指向常量对象的常量指针。
用名词 * 顶层const(top-level const) 表示指针本身是个常量,底层const(low-level const)*表示指针所指的对象是一个常量。
更一般的,顶层const可以表示任意的对象是常量,底层const则与指针和引用等复合类型部分有关。比较特殊的是指针类型既可以是顶层const也可以是底层const。
int i = 0;
int *const p1 = &i; //不能改变p1的值,顶层const;
const int ci = 42; //不能改变ci的值,顶层const;
const int *p2 = &ci;//允许改变p2的值,这是一个底层const;
const int *const p3 = p2; //右边的const是顶层const,左边的是底层const;
const int &r = ci; //用于声明引用的const都是底层const;
9.constexpr与常量表达式
常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。(如字面值)
一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定。
const int max_files = 20; //max_files是常量表达式
const int limit = max_files + 1; //limit是常量表达式
int staff_size =27; //staff_size不是,因为数据类型只是一个普通int而非const int
const int sz = get_size(); //sz的具体值在运行时获得,帮也不是。
C++11新标准规定,允许将变量声明为constexpr类型,以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。
constexpr int sz = size();//只有当size是一个constexpr函数时,才是一条正确声明语句。
指针与constexpr:限定符constexpr仅对指针有效,与指针所指的对象无关:
const int *p = nullptr; //p是一个指向整形常量的指针; constexpr int *q = nullptr; //q是一个指向整数的常量指针
10.类型别名:
- 传统方法用关键字typedef定义类型别名。
typedef double wages;//wages是double的同义词。
- 新标准规定了一种新方法,使用别名声明 来定义类型的别名:
using SI = Sales_item; //SI是Sales_item的同义词。
typedef char *pstring;
const pstring cstr = 0; //cstr是指向char的常量指针
const pstring *ps; //ps是一个指针,它的对象是指向char的常量指针(指针的指针)。
const是对给定类型的修饰,pstring实际上是指向char的指针,因此,const pstring 就是指向char的常量指针,而不指向常量字符的指针。
即不能把类型别名替换成它本来的样子去理解。
11.auto类型说明符
C++11标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型,编译器通过初始值来推算变量的类型。所以,auto定义的变量必须有初始值
auto也能在一条语句中声明多个变量,但一条声明语句只能有一个基本数据类型,所 所有变量的初始基本数据类型都必须一样
auto sz = 0, pi = 3.14; //error
auto一般会忽略掉顶层const,同时底层const会保留下来,比如当初始值是一个指向常量的指针时:
int i = 0;
const int ci = i,&cr = ci;
auto b = ci; //b是一个整数(ci的顶层const特性被忽略了,即b不是常量)。
auto c = cr; //c是一个整数
auto d = &i; //d是一个整形指针
auto e = &ci; //e是一个指向整数常量的指针(对常量对象取地址是一种底层const)
12.decltype类型指示符
C++11新标准引入的第二种类型说明符,作用是选择并返回操作数的数据类型。编译器分析表达式并得到它的类型,却不实际计算表达式的值:
delctype(f()) sum = x; //sum的类型就是函数f的返回类型。
编译器并不实际调用函数f,而是使用当调用发生时f的返回值类型作为sum的类型。
decltype与引用
int i = 42,*p = &i, &r = i;
decltype(r + 0) b; //正确:加法的结果是int,因此b是一个(未初始化的)int
decltype(*p) c; //错误:c是int&,必须初始化。
即如果表达式的内容是解引用操作,则decltype将得到引用类型。(解引用指针可以得到指针所指的对象,而且还能给这个对象赋值。因此,是int&)
decltype的表达式如果是加上了括号的变量,结果将是引用。
decltype((i)) d; //error
13.自定义数据类型struct
不要忘了在struct类体后面加分号!
新标准规定,可以为数据成员提供一个 类内初始值。创建对象时,初始值将用于初始化数据成员,没有初始值的成员,将被默认初始化。
预处理器概述
确保头文件多次包含仍能安全工作的常用技术是 预处理器 。从C继承而来,而C++还会用到一项预处理功能是 头文件保护符 ,它依赖于预处理变量,而预处理变量有两种状态:已定义,未定义。
#define 指令把一个名字设定为预处理变量,而#ifdef与#ifndef指令检查状态。当检查结果为真时,执行后续操作直到遇到#endif指令。#ifndef SALES_DATA_H #define SALES_DATA_H #include <string> struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
endif
预处理器无视C++中关于作用域的规则。