第2章 变量和基本类型

1.位、字节、字、KB

位(bit),电子计算机中最小的数据单位。每一位的状态只能是0或1。

字节(byte),可寻址的最小内存块,存储的基本单元,1byte=8bit。

字(word),进行数据处理和运算的单位,不同机器其字长不同。32位机器上,1字=4字节=32位;64位机器上,1字=8字节=64位。

1KB = 1024B(字节)

1个char型数据占8位,1个字节。1个英文字母占1字节,1个汉字占2个字节。

表示范围。 若数据类型 F占 n位,则无符号类型:0~2n-1,带符号类型:-2n-1~2n-1-1。

2.建议,使用 int执行整数运算,double执行浮点数运算。

3.给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值的总个数取模后的余数。如下例所示

1 unsigned char c = -1;  // c值实际上为255
2 unsigned char d = 257;  // d实际上值为 1
注意,取模(mod)和取余(rem)运算是有区别的。取模,是向小指(负无穷)取值;取余,是向0取值。当被操作数是负数时,二者是不一样的。

-1(mod)256 = -1 + 255; -1(rem)256 = 0 + (-1);

4.不要滥用无符号类型,看下面两个例子。

1)死循环。

1 for (unsigned u = 10; u >= 0; --u)
2     do sth;

这里,当u=0且执行--u时,由于u是无符号类型,此时u=232-1,而不是-1。这样,整个程序就会是个死循环。

2)对无符号数和带符号数进行运算,此时带符号数会自动转换为无符号数。

1 int i = 10;
2 unsigned u = 42;
3 // 预想的输出为 -32,可实际输出为 -32+2^32,4294967264
4 cout << i - u << endl;

5.八进制和十六进制字面值的类型,是能容纳其数值的类型中的尺寸最小者。浮点型字面值默认为 double。

6.两个字符串字面值位置紧邻且仅由空格、缩进和换行符分隔,则实际上是一个整体。下面写在多行的字符串整体实际上是一个字面值。

1 // 分多行书写的字符串字面值
2 cout << "a really, really long string literal "
3               "that spans to two lines" << endl;

7.转义字符,以反斜线开始,如 \n \t \' \'' \\等。比较有意思的是 \a,报警符,会发出 bi声。另外,还可以使用泛化的转义序列,\后不多于3个八进制数字,\x后1个十六进制数字。\n = \12 = \xa,这3个都是换行符。

8.一行语句可以定义多个变量,并为其中某些变量赋初值。可以这么理解,它们彼此独立,只是共同遵循同一个类型说明符。

1 int sum = 0, value, units_sold = 0;

9.C++中,初始化和赋值并不相同。初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来代替。要知道,在初始化时,变量刚刚创建,此时是没有当前值可供擦除的。

10.变量初始化,下面4条语句都可以完成。

1 int a = 0;
2 int a = {0};
3 int a{0};
4 int a(0);

其中,第2和3种方法使用了花括号{},这种方式称为列表初始化。在C++11新标准中,这种方法得到了广泛的应用。它的一个重要特点是:初始值存在丢失信息的风险,编译器将报错。如下所示

1 double d = 3.14;
2 int a{d}, b = {d};   // 错误,存在精度丢失
3 int c(d), e = d;     // 正确,类型转换,但也丢失了精度

不过,在进行类内数据成员初始化时,不能使用 '()',其他三种方式都可以。

11.定义变量时未初始化,则执行默认初始化。

1)定义在函数体外的变量将会被初始化为 0。

2)定义在函数体内部的变量将不会被初始化,其值未定义。

3)类的对象未被初始化,其值由类决定。

12.分离式编译机制下,程序可以被分割为多个文件。为了在文件间共享变量,将声明和定义分离开来。一次定义,多次声明和使用。使用 extern关键字,不进行显示地初始化。

1 extern int i;         // 声明而非定义 i
2 extern int j = 1;  // 定义

13.C++中,不允许在函数外对全局变量赋值,全局变量只能在定义的时候进行初始化。下面的式子就是错误的。

int ta;
ta = 1;

另外,在块作用域内访问全局作用域内的同名变量,可以使用域操作符 "::"来实现。

 1 #include <iostream>
 2 using namespace std;
 3 // 该程序仅用于说明,函数内部不宜定义与全局变量同名的新变量
 4 int a = 42;              // 全局变量,全局作用域
 5 void main()
 6 {
 7    int a = 10;           // 局部变量,块作用域
 8    cout << a << '\n';    // 输出 10 
 9    cout << ::a << '\n';  // 输出 42  
10 }

14.引用

1)引用是与初始值绑定到一起,而不是将初始值拷贝给引用。

2)引用无法重新绑定,一直与初始对象绑定到一起,因此必须初始化。

3)引用不是对象,只是已存在对象的别名,不能定义引用的引用。以引用作为初始值,实际上是以与引用绑定的对象作为初始值。

4)非常量引用不能与字面值或表达式绑定到一起。

15.指针

1)指针本身是一个对象,允许赋值和拷贝,而且在生命周期内可以先后指向不同的对象。

2)无须在定义时赋初值。在块作用域定义时,如果未初始化,其值将不确定。

3)引用不是对象,没有实际地址,不能定义指向引用的指针。

16.C++11中,最好用 nullptr来初始化空指针。不能把 int变量直接赋给指针,下面的代码是错误的。

1 int zero = 0;
2 int *p = zero;  // 错误!

17.void*指针,可以存放任意类型的指针,不过没办法访问内存空间中所存的对象。想要获取其内容,可以使用强制类型转换。

1 int a = 1;
2 void *pi = &a;
3 cout << *pi << '\n';    // 报错,不能取出其值
4 int *pa = static_cast<int *>(pi);
5 cout << *pa << '\n';   // 成功输出 1

18.指向指针的引用

1 int i = 1;
2 int *pi = &i;
3 int *&ri = p;  // 对指针 p的引用

可以这么来理解

  1)将 &ri看作是一个整体 pp,则变成 int *pp = p; 可以看到 pp是个 int型指针,指向 p所指向的内容。

  2)&ri是一个 int型指针,即 ri是 int型指针的引用。

19.const限定符

1)const对象一旦创建后其值不能再改变,所以必须进行初始化。但是不区分是编译还是运行时初始化。

2)利用一个对象去初始化另一个对象,它们是不是 const都无关紧要,如

1 int i=42;
2 const int ci = i;  // 正确,值拷贝
3 int j = ci;           // 正确,值拷贝

3)默认状态下,const对象仅在文件内有效。要想在多个文件内使用同一个 const对象,添加 extern关键字,

1 // file_1.h文件,定义并初始化
2 extern const int bufSize = 512;
3 // file_2.h文件,声明该常量
4 extern const int bufSize;

这样的变量具有三个特点。1.能被外部文件访问。2.只定义一次。3.不可改变。

4)对 const的引用。

    4-1.允许为一个常量引用绑定非常量的对象、字面值,甚至是一个一般表达式,而类型也没有硬性要求,只要能成功类型转换即可。而普通引用则不可以引用 字面值和表达式,类型也必须一致。

int i = 42;
const int &r1 = i;              // 正确
const int &r2 = 42;             // 正确
const int &r3 = r1 * 2;         // 正确
int &r4 = i;                    // 正确
int &r5 = r1;                   // 错误,普通的 int不能绑定到 int常量
int &r6 = 42;                   // 错误
double j = 1.0;
int &r7 = j;                    // 错误
const int &r8 = j;              // 正确

4-2.对 const的引用可以引用一个非 const对象,只是说不能通过该引用去修改所绑定的对象,并不影响该对象通过其他方式进行改变。

5)指针和 const

    5-1.三种常见的指针和const的组合如下所示,在分析时,可以遵循就近原则来判断语义。

1 const int *p;        // 指向常量的指针,底层const
2 int *const p = &pi;  // 指针本身为常量,顶层const,必须进行初始化
3 const int *const p = &pi;  // 指向常量的常量指针,靠左是底层const,靠右是顶层const,也必须进行初始化

5-2.执行拷贝操作,顶层const没有影响,拷入和拷出的对象是否是常量都没什么影响。然而底层const的限制却不能忽略,非常量可以转化成常量,反之则不行。

    5-3.常量表达式,不改变,且编译时就知道结果。C++11新标准中,将常量表达式声明成 constexpr类型,编译器将会验证该变量是否是一个常量表达式。

20.类型别名,使复杂的类型名字变得简单明了、易于理解,下面两种方法都可以

1 typedef double wages, *p;  // wages是double的同义词,p是double*的同义词
2 using SI = Sales_item;      // SI是Sales_item的同义词

注意,如果类型别名是符合类型或常量时,在剖分语义时,不能把类型别名替换成原来的样子,而是将类型别名整体看成一个基本数据类型。可以将 pstring当做是 int一样的基本类型,先解释外面的部分,最后再来解释 pstring本身。如下

1 typedef char *pstring;
2 const pstring cstr = 0;   // cstr是指向 char的常量指针
3 const pstring *ps;        // ps是一个指针,对象是指向 char的常量指针
4 // 第2行代码的两种解释方案
5 const (char) * cstr;      // 错误,基本类型为 char,解释成指向 const char的指针
6 const (char*) cstr;       // 正确,基本类型为char*,解释成指向 char的常量指针

21.auto类型说明符,编译器替我们去分析表达式所属的类型,必须有初始值。

1)一条语句也可以声明多个变量,但所有变量的初始基本数据类型应该是一样的,因为一条声明语句只能有一个基本类型。比如

1 auto i = 0, *p = &i;     // 正确
2 auto sz = 0, pi = 3.14;  // 错误,sz和 pi的类型不一致

2)编译器在进行 auto类型推断时,其值和初始值的类型并不完全一样。

       ①引用作为初始值时,真正参与初始化的是引用的值。

       ②auto一般会忽略顶层 const,底层 const则会保留下来。

       ③如果希望推断出的 auto类型是一个顶层 const,则需要显示加上 const修饰符。

       ④设置 auto类型为引用时,初始值中的顶层 const仍然保留。

1 int i = 0;
2 const int ci = i, &cr = ci;
3 auto b = ci;         // b是整数
4 auto c = cr;         // c是整数
5 auto e = &ci;        // e是指向整数常量的指针
6 const auto f = ci;   // ci的推演类型是 int,f是 const int
7 auto &g = ci;        // g是一个整型常量的引用
8 auto &h = 42;        // 错误,不能为非常量引用绑定字面值
9 const auto &j = 42;  // 正确 

22.decltype类型指示符,只希望推断出要定义的变量的类型,而不想用该表达式的值初始化变量。它与 auto在类型推断上有所不同,

      1)decltype会将顶层 const和引用包括在内,返回该变量的类型。

       2)当表达式可作为左值或被加上了一层或多层括号,反回的是相应类型的引用。不过加括号貌似只对变量有影响,对于表达式好像没有影响。

 1 const int ci = 0, &cj = ci;
 2 decltype(ci) x = 0;    // x的类型是 const int
 3 decltype(cj) y = x;    // y的类型是 const int&,必须初始化
 4 int i = 42, *p = &i, &r = i;
 5 decltype(i) a;         // a是 int
 6 decltype((i)) b = i;   // b是 int&,必须初始化
 7 decltype(r) c = i;     // c是 int&,必须初始化
 8 decltype(r+0) d;       // d是 int
 9 decltype((r+0)) e;     // e是 int
10 decltype(*p) f = i;    // f是 int&,必须初始化

23.C++11新标准规定,可以为类内的数据成员提供一个类内初始值。赋值时,不能使用圆括号,建议使用花括号。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Rovo89

JavaScript闭包与箭头函数

862
来自专栏前端真相

JavaScript基础(3)

1885
来自专栏软件开发 -- 分享 互助 成长

C++ STL之set的基本操作

set是集合,虽然也存在键值和实值,不过两者根本就是同一个值,键值的设置完全就是为了满足红黑树的底层结构,set操作与map很像不过也有些不同。 1、 set迭...

2665
来自专栏C/C++基础

字符数组的初始化与赋值

C语言中表示字符串有两种方式,数组和指针,字符数组是我们经常使用的方式。变量的定义包括指明变量所属类型、变量名称、分配空间以及初始化。可以看出,变量的初始化是变...

1522
来自专栏desperate633

LintCode 字符大小写排序题目分析代码

821
来自专栏有趣的Python

6-Java常用工具类-集合排序

http://www.runoob.com/java/java-generics.html

823
来自专栏GreenLeaves

JavaScript引用类型之Object类型

在JavaScript中大多数的引用类型都是Object的实例,Object类型也是使用最多的类型! 创建Object类型实例的方式有两种,下面分别来分析一下:...

2105
来自专栏蓝天

Linux内核list/hlist解读

Linux内核实现了一批优雅而功能强大的双向循环列表操作宏,它们位于/usr/include/linux/list.h(请注意直接#include会报编译错误...

981
来自专栏编程

Shell 数组

Shell中数据类型不多,比如说字符串,数字类型,数组。数组是其中比较重要的一种,同时Shell中的数组不像JAVA/C,只能是一维数组,没有二维数组;数组元素...

2010
来自专栏cs

c++13.0 STL[stack,queue,set,dequeue]

set相关知识点: ---- set:集合,红黑树实现 特点: 1.0 内部的元素根据其值自动排序。 2.0 内部元素只能出现一次。 set数据结...

2316

扫码关注云+社区