《C++ Primer》【Chapter 2】
chapter2 变量和基本类型
1. 基本内置类型
了解这些类型的原理是为了方便理解,但在写代码时,因该要避免代码依赖与硬件或编译器环境
unsigned char
unsigned char 的理论数据范围为[-128, 127]。其中0的二进制编码为(00000000),-128的二进制编码为(10000000)。
不要使用char来进行运算,因为有些机器上char时有符号的,有些机器上char是无符号的
为什么要用double 而不是 float ?
- a. float精度为6位有效数字,double精度为10位有效数字。
- b. double计算代价和float相差无几,甚至可能更优。
- c. long double消耗会增大,精度也是10位,没有必要。
类型转换
- 非bool类型转换为bool时,初始值等于0为false,否则为true,不是看二进制的最低位。
- 浮点类型转换为整型时,只保留整数部分。
- 给无符号类型赋一个超出其表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。例如 unsigned char c = -1 => c 的实际值为(-1)%256 = 255。
- \(给带符号类型一个超过其表示范围的值时,结果是未定义的,程序可能继续工作、崩溃、或生成垃圾数据。\)例如: signed char c = 256
2. 变量
声明只是单纯起名字,定义会分配内存空间,初始化是定义+赋值
extern int a; //申明
int a; //定义,有默认值
int a = 3; //初始化a为3
对象
对象是指一块能够存储数据
并具有某种类型
的内存空间
。
初始化对象时,经常用“=”号,但是这并不代表初始化是赋值的一种。初始化的含义是创建变量并赋予一个初始值,而赋值时把对象原来的值擦除然后赋予新的值。
初始化每一个内置类型的变量,可以防止不必要的错误,确保使用的值的合理性。
声明和定义
变量只能被定义一次,但是可以被声明很多次
extern int i; //extern 是声明的关键字,表示只声明i 而不定义i
int j // 声明并定义j
上述代码中,只要给i赋了初值,extern关键字就会被抵消掉。
C++是静态类型语言
其含义是在编译阶段检查类型,其中检查类型的过程称为类型检查,所以要求每个变量在使用之前必须声明类型。
标识符(变量名)标准
- 用户自定义的标识符不能连续出现两个下划线,也不能下划线紧连大写字母开头
- 标识符要能体现实际含义
- 变量名一般用小写字母
- 用户自定义的
类名
一般以大写字母开头 - 如果标识符由多个单词组成,则单词间应由明显区分,如student_loan,studentLoan
3. 复合类型
引用
引用为对象起另外一个名字,引用类型引用另外一种类型
- a. 定义引用时必须被初始化,要把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用
- b. 引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字
- c. 引用类型的初始值类型要和定义的类型一致, 且必须是一个对象
- d. 引用本身不是一个对象,所以不能定义引用的引用
int &reval; //wrong from a
int val = 1;
int &reval = val;
int i = reval; //equals (int i = val)
int &reval = 10; //wrong from c because 10 is not a object
double dval = 3.14;
int &reval2 = dval; // wrong from c
指针
始终牢记,指针指向的是对象的地址
定义指针时用*,且指针在定义时无需赋初值。
int *p;
给指针进行赋值时,可能要用取址地址符&
int i = 3;
int *p = &i;
注意:声明语句中指针的类型与指向对象的类型必须一致。
获取指针所指向的对象用解引用符*
int a = 4;
int *p = &a;
cout << *p << endl; //4
指针值的4种状态
- a. 指向一个对象
- b. 指向紧邻对象所占空间的下一个位置
- c. 空指针,没有指向任何对象
- d. 无效指针,除了上述3种情况的其他值
空指针
int *np1 = nullptr; //c++11引用的字面值(推荐使用)
int *np2 = 0; //直接初始化字面常量0
int *np3 = NULL; //NULL为预处理变量,等价于上面一句, 但是需要引用头文件#include <cstdlib>
引用和指针的区别
引用本身并非一个对象,它只是给变量起另外一个名字,即指针可以按对象的操作方法进行操作
一旦定义了引用,就无法令其再绑定到另外的对象,之后每次访问的都是最初绑定的那个对象
4. const限定符
const对象一旦创建后其值就不能再改变,所以const对象必须初始化。且const类型的对象上只能执行不改变其内容的操作。
当const和&同时使用时,要注意引用的类型必须与其所引用对象的类型一致。
int i = 42;
const int &r1 = i; //正确,允许将const int&绑定到一个普通int对象上
const int &r2 = 42; //正确,r2是一个常量引用
const int &r3 = r1*2; //正确,r3是一个常量引用
int &r4 = r1*2; //错误,r4的类型是普通引用 与 r1的常量引用不符合
指针和const
指向常量(pointer to const)的指针不能用于改变其所指对象的值
。
常量指针
常量指针(const pointer)必须初始化,而且一旦初始化完成,则它的值(存放的地址)就不能再改变了。把*放在const关键字之前用以说明指针是一个常量,即指针指向的那个地址是一个常量,但是地址对应的对象可以是可变的。
int errNumb = 0;
int *const curErr = &errNumb;
*currErr = 1; // 可以,因为只是地址不变,指向的对象可以变
const double pi = 3.14;
const double *const pip = π
从左往右读的原则
例如上面的代码中,pip指针,先是const,表示pip是个常量;然后碰*,标识pip是个常量指针;然后碰 const double,表示指向的对象是const double 类型。
感觉中文翻译的书中这里并没有说清楚
- 顶层const:即指向的地址是一个常量(即对于一个对象本身是否是const的)
- 底层const:即指针所指向的对象是一个常量(对于对象指向的对象是否是const的)
常量表达式和constexpr
常量表达式是指值不会改变且在编译过程中就能得到计算结果的表达式。
const int max_file = 20; //是常量表达式
const int limit = max_file + 1; //是常量表达式
int size = 20; //不是
const int sz = get_size(); //不是,因为在编译时得不到结果
C++11
中允许变量声明constexpr类型,以便由编译器来验证
变量的值是否是一个常量表达式。
constexpr int mf = 2;
constexpr int limit = mf + 1;
constexpr int sz = size(); //当size是constexpr时满足
const int *p = nullptr; //p是一个指向整型常量的指针
constexpr int *q = nullptr; //q是一个指向整数的常量指针
constexpr int i = 42; //i是整型常量
constexpr const int *t = &i //t是常量指针,并且指向整型常量
static int j = 3;
constexpr int *p1 = &j //p1是常量指针,指向整数j (会报错)
5. 处理类型
typedef
typedef 并不能理解成语句的替换
typedef char *pstring;
const pstring cstr = 0; //cstr是指向char的常量指针
const pstring *ps; //ps是一个指针,它指向的对象是(指向char的常量指针)
const char *cstr = 0; //是对const pstring cstr = 0的错误理解,这是一个指向const char(字符常量)的指针
auto
auto是让编译器通过初始值来推算变量的类型的,所以auto定义的变量必须有初始值。
当auto在一条语句中声明多个变量时,那么auto只能代表一个基本数据类型
auto i = 0, *p = &i; // auto表示整型
auto sz = 0, pi = 3.14; //错误
如果初始值有顶层const会被忽略掉
const int i = 4;
const int &cr = i;
auto b = i;
b = 6; // true
auto d = &i; //d现在是指针,底层const会被保留下来,即d指向整型常量
decltype类型指示符
int i = 4;
decltype(i) k = 4;
decltype((variable))的结果永远是引用,而decltype(variable)的结果只有当variable本身是一个引用时才是引用
int i = 4;
decltype((i)) d; //错误,d是int&,必须初始化
decltype((i)) e; //正确,e是未初始化的int
decltype与auto的区别
- 处理顶层const和引用方式不同,decltype会把顶层const考虑进去,auto不会
- decltype的结果类型与表达式密切相关
6. 头文件中的预处理器和头文件保护
确保头文件多次包含仍能安全工作的常用技术是预处理器(preprocesser),预处理器是在编译前执行的一段程序
头文件保护符依赖于预处理变量,预处理变量有两种状态,已定义
和未定义
。
-
define 指令把一个名字设定为预处理变量
-
ifdef 当且仅当变量已定义时为真
-
ifndef 当且仅当变量未定义时为真,一旦检查为真,则执行后续操作直至遇到#endif指令为止
//sales_data.h
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data {
std::string book;
};
#endif
这样,当sales_data.h多次被引用时,可以保证#define和#endif中间的代码只被编译一次,因为只有第一次#ifndef结果为真。
几个技巧:
- 预处理变量为避免冲突尽量大写
- 预处理变量尽量和头文件名字保持一致