ch2 变量和基本类型

来源:互联网 发布:凤台县残疾人数据 编辑:程序博客网 时间:2024/06/05 18:40

1. 基本内置类型

1. C++算术类型
  • 算术类型分为两类:整型(integral type,包括字符和布尔类型在内)和浮点型。
    通常,float一个字(32bit)、double两个字、long duoble三或四个字
  • 一个char类型的大小 = 一个机器字节
  • C++规定 short ≤ int ≤ long ≤ long long
有符号和无符号类型

带符号数可以表示正数负数和零,无符号数仅能表示大于等于零的数
类型int、short、long和long long都是带符号的,通过在这些类型名前添加unsigned就可以得到无符号类型。
字符型被分为了三种:char、signed char和unsigned char。特别需要注意的:类型char和类型signed char并不一样。尽管字符型有三种,但是字符的表现形式却只有两种:带符号的和无符号的。
无符号类型中所有比特都用来存储值,如,8比特的unsigned char可以表示0至255区间内的值。
8比特的signed char理论上应该可以表示-127至127区间内的值,大多数现代计算机将实际的表示范围定为-128至127

2. 类型转换
  • 当我们赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示++数值总数++取模后的余数。如,8比特大小的unsigned char可以表示0至255区间内的值,如果我们赋了一个区间以外的值,则实际的结果是该值对256取模后所得的余数。因此,把-1赋给8比特大小的unsigned char所得的结果是255。
  • 当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的。
含有无符号类型的表达式
  • 算术表达式中既有无符号数又有int值时,那个int值就会转换成无符号数。把int转换成无符号数的过程和把int直接赋给无符号变量一样
unsigned u = 10;  int i = -42;  std::cout << i + i << std::endl; // 输出-84  std::cout << u + i << std::endl; //如果int占32位,输出4294967264//相加前首先把整数-42转换成无符号数。把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。
  • 当从无符号数中减去一个值时,不管这个值是不是无符号数,我们都必须确保结果不能是一个负值
unsigned u1 = 42, u2 = 10;  std::cout << u1 - u2 << std::endl; // 正确:输出32  std::cout << u2 - u1 << std::endl;//正确:不过,结果是取模后的值
  • 无符号数不会小于0这一事实可能会造成死循环
  • tips: 切记不要混用符号类型和无符号类型
3. 字面值常量
整型和浮点型字面值
20 /* 十进制 */      024 /* 八进制 */      0x14 /* 十六进制 */ 
  • 默认情况下,十进制字面值是带符号数,八进制和十六进制字面值既可能是带符号的也可能是无符号的
  • 类型选择一般是能容纳当前值的尺寸最小者
  • 浮点型字面值表现为一个小数或以科学计数法表示的指数,其中指数部分用E或e标识
字符和字符串字面值
  • 由单引号括起来的一个字符称为char型字面值,双引号括起来的零个或多个字符则构成字符串型字面值
a // 字符字面值  "Hello World!"  // 字符串字面值 
  • 编译器在每个字符串的结尾处添加一个空字符(′\0′),字符串字面值的实际长度要比它的内容多1
转义序列
  • C++语言规定的转义序列包括
换行符       \n    横向制表符   \t      报警(响铃)符   \a  纵向制表符   \v    退格浮       \b      双引号           \"  反斜线       \\    问号         \?      单引号           \'  回车符       \r    进纸符       \f 
  • 也可以使用泛化的转义序列,其形式是\x后紧跟1个或多个十六进制数字,或者\后紧跟1个、2个或3个八进制数字,其中数字部分表示的是字符对应的数值
\7 (响铃)     \12 (换行符)   \40 (空格)  \0 (空字符)   \115 (字符M)   \x4d (字符M)
指定字面值类型
  • 通过添加的前缀和后缀,可以改变整型、浮点型和字符型字面值的默认类型。
L'a'        // 宽字符型字面值,类型是wchar_t  u8"hi!"     //utf-8字符串字面值(utf-8用8位编码一个Unicode字符)42ULL       // 无符号整型字面值,类型是unsigned long long  1E-3F       // 单精度浮点型字面值,类型是float  3.14159L    // 扩展精度浮点型字面值,类型是long double
布尔字面值和指针字面值
  • true和false是布尔类型的字面值
  • nullptr是指针字面值

2. 变量

1. 变量定义
  • 变量定义的基本形式是:首先是类型说明符(type specifier),随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。
初始值
  • 当对象在创建获得了一个特定的值,我们说这个对象被初始化(initialized)了。同一条定义语句中,可以用先定义的变量值去初始化后定义的其他变量。
  • tips:初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。
列表初始化
  • {} 用花括号来初始化变量称作列表初始化
long double ld = 3.1415926536;  int a{ld}, b = {ld};//错误:转换未执行,因为存在丢失信息的危险  int c(ld), d = ld;      // 正确: 转换执行,且确实丢失了部分值
  • 使用long double的值初始化int变量时可能丢失数据,所以编译器拒绝了a和b的初始化请求。其中,至少ld的小数部分会丢失掉,而且int也可能存不下ld的整数部分。
默认初始化
  • 如果定义变量时没有指定初值,则变量被默认初始化(default initialized),此时变量被赋予了”默认值”。默认值到底是什么由变量类型决定,同时定义变量的位置也会对此有影响。
  • 如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被初始化为0。
  • tips:未初始化的变量含有一个不确定的值,使用未初始化变量的值是一种错误的编程行为并且很难调试。
2. 变量声明和定义的关系
  • 声明(declaration)使得名字为程序所知,规定了变量的类型和名字
  • 定义(definition)负责创建与名字关联的实体,申请存储空间,也可能为变量赋初值
  • 如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式地初始化变量:
extern int i;   // 声明i而非定义i  int j;          // 声明并定义j //给由extern关键字标记的变量赋一个初始值,//但是这么做也就抵消了extern的作extern double pi = 3.1416; // 定义
  • 变量能且只能被定义一次,但是可以被多次声明
    如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。

3. 复合类型

1. 引用
  • 引用(reference)为一个已经存在的对象所起的另外一个名字
  • 引用必须初始化
  • 因为引用本身不是一个对象,所以不能定义引用的引用
int ival = 1024;  int &refVal = ival;     //refVal指向ival(是ival的另一个名字)  int &refVal2;           // 报错:引用必须被初始化 
引用的定义
2. 指针
  • 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象
  • 指针无须在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值
  • 引用不是对象,没有实际地址,所以不能定义指向引用的指针
  • 取地址符(&)
int ival = 42; int *p = &ival; //p存放变量ival的地址,或者说p是指向变量ival的指针 double dval;  double *pd = &dval;     // 正确: 初始值是double型对象的地址  double *pdpd2 = pd;   // 正确: 初始值是指向double对象的指针  int *pi = pd;       // 错误: 指针pi的类型和pd的类型不匹配  pi = &dval;             // 错误: 试图把double型对象的地址赋给int型指针 
空指针
  • 空指针(null pointer)不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空。
int *p1 = nullptr;      // 等价于int *p1 = 0;  新标准下使用int *p2 = 0;            // 直接将p2初始化为字面常量0  // 需要首先#include cstdlib  int *p3 = NULL;             // 等价于int *p3 = 0; 
  • tips:建议初始化所有指针(nullptr或0)
赋值和指针
  • 记住赋值永远改变的是等号左侧的对象
void* 指针
  • void*是一种特殊的指针类型,可用于存放任意对象的地址
  • 不能直接操作void*指针所指的对象,因为我们并不知道这个对象到底是什么类型
3. 理解复合类型的声明
定义多个变量
int* p1, p2;    //合法但是容易产生误导.正确理解应该是p1是指向int的指针,p2是int int *p1, *p2;   // p1和p2都是指向int的指针 

tips:最好将*(或是&)与变量名连在一起

指向指针的指针
int ival = 1024;  int *pi = &ival; // pi指向一个int型的数  int **ppi = &pi; // ppi是一个指向int型指针的指针
指向指针的引用
  • 引用本身不是一个对象,因此不能定义指向引用的指针。
  • 指针是对象,所以存在对指针的引用:
int i = 42;  int *p;         // p是一个int型指针  int *&r = p;    // r是一个对指针p的引用 r = &i;         //r引用了一个指针,因此给r赋值&i就是令p指向i  *r = 0;         //解引用r得到i,也就是p指向的对象,将i的值改为0 
  • tips:面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义。

4. const限定符

  • const对象一旦创建后其值就不能再改变,所以const对象必须初始化,初始值可以是任意复杂的表达式
const int i = get_size();   // 正确:运行时初始化  const int j = 42;           // 正确:编译时初始化  const int k;                // 错误:k是一个未经初始化的常量 
初始化和const
  • 如果利用一个对象去初始化另外一个对象,则它们是不是const都无关紧要
  • 默认状态下,const对象仅在文件内有效
  • 如何只在一个文件中定义const,而在其他多个文件中声明并使用它: 解决的办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了
// file_1.cc定义并初始化了一个常量,该常量能被其他文件访问  extern const int bufSize = fcn();  // file_1.h头文件  extern const int bufSize; // 与file_1.cc中定义的bufSize是同一个
1. const和引用
  • 对常量的引用
    对常量的引用不能被用作修改它所绑定的对象
const int ci = 1024;  const int &r1 = ci;     // 正确:引用及其对应的对象都是常量  r1 = 42;                // 错误:r1是对常量的引用  int &r2 = ci;           // 错误:试图让一个非常量引用指向一个常量对象 
初始化和对const的引用
  • 引用的类型必须与其所引用对象的类型一致,但是有两个例外:
    (1) 初始化常量引用时允许用任意表达式作为初始值,,只要该表达式的结果能转换成引用的类型即可
int i = 42;  const int &r1 = i;      // 允许将const int&绑定到一个普通int对象上  const int &r2 = 42;         // 正确:r1是一个常量引用  const int &r3 = r1 * 2; // 正确:r3是一个常量引用  int &r4 = r1 * 2;           // 错误:r4是一个普通的非常量引用 

此处ri引用了一个int型的数。对ri的操作应该是整数运算,但dval却是一个双精度浮点数而非整数

double dval = 3.14;  const int &ri = dval;  

为了确保让ri绑定一个整数,编译器把上述代码变成了如下形式:

const int temp = dval;  // 由双精度浮点数生成一个临时的整型常量 const int &ri = temp;   // 让ri绑定这个临时量 

临时量对象:当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象
(2)

对const的引用可能引用一个并非const的对象
  • r2绑定(非常量)整数i是合法的行为。然而,不允许通过r2修改i的值。尽管如此,i的值仍然允许通过其他途径修改,既可以直接给i赋值,也可以通过像r1一样绑定到i的其他引用来修改。
int i = 42;  int &r1 = i;            // 引用ri绑定对象i  const int &r2 = i;      // r2也绑定对象i,但是不允许通过r2修改i的值  r1 = 0;                     // r1并非常量,i的值修改为0  r2 = 0;                     // 错误:r2是一个常量引用  
2. 指针和const
  • 指向常量的指针
    不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针
const double pi = 3.14;         // pi是个常量,它的值不能改变  double *ptr = &pi;          // 错误:ptr是一个普通指针  const double *cptr = &pi;   // 正确:cptr可以指向一个双精度常量 *cptr = 42;                     // 错误:不能给*cptr赋值 
  • 指针的类型必须与其所指对象的类型一致,但是有两个例外:
    (1) 允许令一个指向常量的指针指向一个非常量对象
double dval = 3.14;             // dval是一个双精度浮点数,它的值可以改变  cptr = &dval;               // 正确:但是不能通过cptr改变dval的值 

(2)指向常量的指针也没有规定其所指的对象必须是一个常量
- tips:所谓指向常量的指针或引用,指向或引用的对象不一定是常量,但是常量只能被常指针指向或者被常引用引用

const指针
  • 常量指针:指针本身是对象,所以允许把指针本身定为常量
  • 常量指针(const pointer)必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把*放在const关键字之前用以说明指针是一个常量,这样的书写形式隐含着一层意味,即不变的是指针本身的值而非指向的那个值
int errNumb = 0;  int *const curErr = &errNumb;   // curErr将一直指向errNumb  const double pi = 3.14159;  const double *const pip = &pi;  // pip是一个指向常量对象的常量指针
  • 清楚这些声明的含义最行之有效的办法是从右向左阅读。此例中,离curErr最近的符号是const,意味着curErr本身是一个常量对象,对象的类型由声明符的其余部分确定。声明符中的下一个符号是*,意思是curErr是一个常量指针。最后,该声明语句的基本数据类型部分确定了常量指针指向的是一个int对象。与之相似,我们也能推断出,pip是一个常量指针,它指向的对象是一个双精度浮点型常量。
  • 指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全依赖于所指对象的类型
3. 顶层const
  • 顶层const表示指针本身是个常量,底层const表示指针所指的对象是一个常量。
4. constexpr和常量表达式
  • 常量表达式:值不会改变并且在编译过程就能得到计算结果的表达式。
  • C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。
  • 一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
  • constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关,即constexpr把它所定义的对象置为了顶层const,与其他常量指针类似,constexpr指针既可以指向常量也可以指向一个非常量
const int *p = nullptr;         // p是一个指向整型常量的指针  constexpr int *q = nullptr;     // q是一个指向整数的常量指针 

5. 处理类型

1. 类型别名
  • 关键字typedef
  • 类型别名和类型的名字等价
typedef double wages;   //wages是double的同义词  typedef wages base, *p; //base是double的同义词,p是double*的同义词wages hourly, weekly;       // 等价于double hourly、weekly;  
指针、常量和类型别名

某个类型别名指代的是复合类型或常量

typedef char *pstring;  const pstring cstr = 0; // cstr是指向char的常量指针  const pstring *ps;      // ps是一个指针,它的对象是指向char的常量指针 //人们往往会错误地尝试把类型别名替换成它本来的样子,以理解该语句的含义const char *cstr = 0;   // 是对const pstring cstr的错误理解
2. auto类型说明符
  • C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型,显然,auto定义的变量必须有初始值。
/ 由val1和val2相加的结果可以推断出item的类型  auto item = val1 + val2;    // item初始化为val1和val2相加的结果 
3. decltype类型指示符
  • C++11新标准引入了第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型
decltype(f()) sum = x;  // sum的类型就是函数f的返回类型 
const int ci = 0, &cj = ci;  decltype(ci) x = 0;         // x的类型是const int  decltype(cj) y = x;         // y的类型是const int&,y绑定到变量xdecltype(cj) z;             // 错误:z是一个引用,必须初始化 
  • 因为cj是一个引用,decltype(cj)的结果就是引用类型,因此作为引用的z必须被初始化。
    需要指出的是,引用从来都作为其所指对象的同义词出现,只有用在decltype处是一个例外。
  • tips:decltype((variable))(注意是双层括号)的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用

6. 自定义数据结构

1. struct
2. 编写头文件
  • 头文件保护符:#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++ primer(第五版)》后总结,侵删。

0 0
原创粉丝点击