2.1 基本内置类型
如何选择类型:
- 当明确知晓数值不可能为负时,选用无符号类型。
- 使用int执行整数运算。
- 在算术表达式中不要使用char或bool。
- 执行浮点数运算选用double。
提示:切勿混用带符号类型和无符号类型.
2.2 变量
初始化不是赋值,初始化的含义是创建变量时賦予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代.
初始化的4种形式:
int units_sold = 0;
int units_sold = {0};
int units sold{0}; //{}列表初始化,c++11
int units_sold(0);
如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错.
默认初始化
定义于任何函数体之外的变量被初始化为0。
定义在函数体内部的内置类型变量将不被初始化(uninitialized)。一个未被初始化的内置类型变量的值是未定义的,如果试图拷贝或以其他形式访问此类值将引发错误。
#include <iostream>
#include <string>
using namespace std;
int main() {
int a;
cout << a << endl;
return 0;
}
这样会报错。
#include <iostream>
#include <string>
using namespace std;
int a;
int main() {
cout << a << endl;
return 0;
}
这样会输出0.
建议初始化每一个内置类型的变量。虽然并非必须这么做,但如果我们不能确保初始化后程序安全,那么这么做不失为一种简单可靠的方法。
变量声明规定了变量的类型和名字,在这一点上定义与之相同。但是除此之外,定义还申请存储空间,也可能会为变量赋一个初始值。
如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式地初始化变量。
2.3 复合类型
- 引用与指针的区别
引用:
- 引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字.
- 引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起.
- 无法令引用重新绑定到另外一个对象,因此引用必须初始化。
指针:
- 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。
指针无须在定义时赋初值。
- 几个生成空指针的方法:
int *pl = nullptr; //等价于int*pl=0; c++11
int *p2 = 0; //直接将p2初始化为字面常量0
//需要首先#include <cstdlib>
int *p3 = NULL; //等价于int*p3=0;
- 在新标准下,现在的C++程序最好使用nullptr,尽量避免使用NULL。
把int变量直接赋给指针是错误的操作,即使int变量的值恰好等于0也不行。
- 建议:初始化所有指针
尽量等定义了对象之后再定义指向它的指针。如果实在不清楚指针应该指向何处,就把它初始化为nullptr或者0,
- void指针
由于void指针可以指向任意类型的数据,亦即可用任意数据类型的指针对void指针赋值,因此还可以用void指针来作为函数形参,这样函数就可以接受任意数据类型的指针作为参数(参考:深入理解void以及void指针的含义)。例如:
void * memcpy( void *dest, const void *src, size_t len );
void * memset( void * buffer, int c, size_t num );
- 面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义
int *p; //p是一个int型指针
int *&r = p; //r是一个对指针p的引用
要理解r的类型到底是什么,最简单的办法是从右向左阅读r的定义。离变量名最近的符号(此例中是&r的符号S)对变量的类型有最直接的影响,因此r是一个引用。声明符的其余部分用以确定r引用的类型是什么,此例中的符号*说明r引用的是一个指针。最后,声明的基本数据类型部分指出r引用的是一个int指针。
2.4 const限定符
- 因为const对象一旦创建后其值就不能再改变,所以const对象必须初始化。
默认情况下,const对象被设定为仅在文件内有效。
如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关鍵字。
- 对常量的引用
const int ci = 1024;
const int &rl = ci; //正确:引用及其对应的对象都是常量
rl = 42; //错误:rl是对常量的引用
int &r2 = ci; //错误:试图让一个非常量引用指向一个常量对象
- 容易搞反的两个定义
1.pointer to const
const double pi = 3.14;//pi是个常量,它的值不能改变
double *ptr = π//错误:ptr是一个普通指针
const double *cptr = π//正确:cptr可以指向一个双精度常量
2.const指针(const pointer)
int errNumb=0;
int *const curErr = &errNumb; // curErr将一直指向errNumb
- 两个const
用名词顶层const(top-level const)表不指针本身是个常量,而用名词底层const(low-levl const)表示指针所指的对象是一个常量。
const int *const p3 =
底层 顶层
可以这样理解,上面是指针,下面是指针指向的位置。
上面说的是指针的,一般情况,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。底层const则与指针和引用等复合类型的基本类型部分有关。
2.5处理类型
为什么要用类型别名?
一是一些类型难于“拼写”,它们的名字既难记又容易写错,还无法明确体现其真实目的和含义。二是有时候根本搞不清到底需要的类型是什么,程序员不得不回过头去从程序的上下文中寻求帮助。
两种方法可用于定义类型别名:
1.typedef
typedef double wages;//wages是double的同义词
typedef wages base, *p;//base是double的同义词,p是double*的同义词
2.别名声明(alias declaration)
using SI = Sales_item;//SI是Sales_item的同义词 c++11
- auto
auto让编译器通过初始值来推算变量的类型。显然,auto定义的变量必须有初始值
//由vail和val2相加的结果可以推断出item的类型
auto item = val1 + val2;//item初始化为val1和val2相加的结果
auto—般会忽略掉顶层const,同时底层const则会保留下来
int i = 0,&r = i;
auto a = r;//a是一个整教(r是i的别名,而i是一个整数)
const int ci = i, &cr = ci;
auto b = ci;//b是一个整数(ci的顶层const特性被忽略掉了)
auto c = cr;//c是一个整数(cr是ci的别名,ci本身是一个顶层const)
auto d = &i;//d是一个整型指针(整数的地址就是指向整数的指针)
auto e = &ci;//e是一个指向整数常量的指针(对常量对象取地址是一种底层const)
- auto与decltype:
1.auto从表达式推断出要定义的变量的类型,并用表达式进行初始化。
2.decltype也从表达式推断出要定义的变量的类型,但是不用该表达式的值初始化。
2.6 自定义数据结构
为什么类体右侧的表示结束的花括号后必须写一个分号?
这是因为类体后面可以紧跟变量名以示对该类型对象的定义,所以分号必不可少。
struct Sales_data { /* ... */ } accum, trans, *salesptr;
// 与 上 一 条 语 等 价 ,但 可 能 更 好 一 些
struct Sales_data { /* ... */ };
Sales_data accum, trans, *salesptr;
最好不要把对象的定义和类的定义放在一起。
类的初始化:
#include <iostream>
#include <string>
using namespace std;
struct sale{
int a;
string s; // 会初始化为空
};
int main()
{
struct sale sa;
//cout << """ << sa.s << """ << endl;
cout << """ << sa.a << """ << endl;
return 0;
}
当sale里面有string和int时,a将被初始化为一个不确定的数。如下是多次运行后的结果:
"-858993460"
"-858993460"
"-858993460"
当把string s注释后被报错,说a没有初始化,忽略错误后依然会得到一个不确定的数,为什么上面的不报错呢?因为string可以初始化为空串?
#include <iostream>
#include <string>
using namespace std;
struct sale{
int a;
//string s;
};
int main()
{
struct sale sa;
//cout << """ << sa.s << """ << endl;
cout << """ << sa.a << """ << endl;
return 0;
}
- 预处理器
预处理器是在编译之前执行的一段程序,可以部分地改变我们所写的程序。
1. #include
之前已经用到了一项预处理功能#include,当预处理器看标记时就会用指定的头文件的内容代替#include。
2. 头文件保护符
#ifndef SALES_DATA_H
#define SALES_DATA_H
/*.......*/
#endif
- 整个程序中的预处理变量包括头文件保护符必须唯一
通常的做法是基于头文件中类的名字来构建保护符的名字,以确保其唯一性。为了避免与程序中的其他实体发生名字冲突,一般把预处理变量的名字全部大写。
- 头文件即使(目前还)没有被包含在任何其他头文件中,也应该设置保护符