C++ Primer 5th 第2章 变量和基本类型

*****代码在Debian g++ 5.3.1 / clang++ 3.8(C++11)下编写调试*****

 

由于部分编译器对标准遵循的不同以及自身额外的扩展,本章书中的少数知识点与实际实现存在偏差情况,在实际调试中存在差异时,以书本为准。

 

1.基本数据类型

计算机世界中的类型有很多,但是基本类型就那么几种,有基本数据类型,媒体类型,设备类型等,目前只学习数据类型。.

基本数据类型包括算术类型和空类型,算术又分为整数和浮点数(即小数)。字符和布尔类型从属于整数,bool类型是C++内置类型,但不是C内置类型。除了0是假,其余都是真。

单精度计算不一定比双精度快,可能一样快,也可能double更快。

C++基本数据类型的存储尺寸不统一!包括char的实现是不确定的,可能有符号,可能无符号,也可能符号不确定。算术表达式中不要随意使用char,因为不同机器实现不一样,导致结果不同。

 

2.类型的转换

对象类型决定对象包含的数据和能参与的运算操作,同时还决定了存储空间大小。当C++的类型不匹配时,会发生隐式转换,是自动进行的。但隐式转换是有条件的,字符型和数据型之间无法相互转换。

当给无符号类型的变量所赋值超过其最大值时,结果是所给值对最大值加1取模(最大值加1是因为还有个0),当给有符号类型的变量所赋值超过其最大值时,程序可能会直接崩溃,有符号超过范围时结果不确定(因为符号位不知是否被溢出覆盖了)。虽然知道无符号超过范围时取值是多少,但是也不能那样使用。

不能有无符号混用,也不能超过类型的取值范围。

在循环中,不宜使用无符号数来作判断条件,容易造成死循环。例如

unsigned i=10;

while ( i >= 0 )  //当i=0时,执行--i,i会变成4294967295,然后持续循环

  i--;

 

3.字面值常量

所谓字面值常量就是像“123”、“abc”这样的文字,一眼看上去就知道它的内容,字面内容就是其值,而且没法改变,“123”就是“123”,所以是个常量。每个字面值都属于一种类型,它的形式和值决定了它的类型, “123”它就默认属于int类型,“abc”则默认属于字符串类型,一个数字字面值虽然默认是int类型,但是可以是int、long、long long中的一个,主要看哪个能存储得下,int存储不下就是long,long还存储不下就是long long。具体可以指定,使用后缀来指定,比如long可以用后缀字面L来指定,long long可以用后缀字面LL来指定。short没有对应的字面值

另外字面值也没有负数,比如“-42”,这是一个42的字面值,其中的负号只是对字面值取负值。

对于字符串类型,如果2个字符串是连续写的,中间只有空格或者换行,则视为同一个字符串,例如"abc"   "def",虽然看起来是2个字符串,但是这2个字符串中间只有空格类型,因此等同于“abcdef”,拆开写是因为太长了。字符串使用前缀来指定类型,如u、U、L、u8分别指定char16_t、char_32t、wchar_t、char类型。

转义序列表示原本的符号不再是原来的意思了,比如"\n",这里表示"n"不再是"n"了,它已经不是自己了,它代表另一个意思了。转义序列可以使用泛化的转义序列,形式是\x后紧跟1个或多个16进制数,或者\后紧跟1到3个八进制数,实际上就是ASCII里面的数字,反斜线'\'后跟的如果是8进制,则最多前3个有效,后面无效,如\1234,\123是转义,4就是单独的数字,反斜线'\'后跟的如果是16进制,最所有的数字都视为转义,实际上不可能所有的都是,超过的找不到对应字符,然后报错。

 注意:字符串字面值在实际存储过程中是使用数组存储的,并且末尾有一个隐含的'\0'空字符。

 true和false是C++中保留的关键词,这两个关键词是用于bool类型的,nullptr也是保留的关键词,该关键词是用于指针类型的

 4.变量的初始化与赋值

C++的内置基本类型变量初始化有四种方式:

1.int a=0;

2.int a={0};

3.int a{0};    //一种很好用,也推荐使用的方式!

4.int a(0);

使用花括号初始化变量的方式称为:列表初始化。

对内置类型使用列表初始化去初始化一个值,且2者类型不通,存在丢失信息的风险,列表初始化将无法完成,例如:

long double a=3.1415926;

int b{a};  //编译报错,无法完成。

实际调试情况:g++编译带warning通过,clang++无法编译。不同编译器实现不同,具体遵循书本的标准。

 初始化和赋值的区别:二者都是给一个变量或者对象一个确定的值,区别是初始化给值之前不知道变量或者对象已有的值,赋值是已经知道变量或对象已有的值。

 

关于默认初始化:

如果定义变量时没有指定初值,则变量被默认初始化(default initialized),此时变量被赋予了"默认值"。默认值到底是什么由变量类型决定,同时定义变量的位置也会对此有影响。

如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被初始化为0。然而一种例外情况是,定义在函数体内部的内置类型变量将不被初始化(uninitialized)。一个未被初始化的内置类型变量的值是未定义的,如果试图拷贝或以其他形式访问此类值将引发错误。

在G++和clang++中测试发现:main函数中未初始化的内置变量将被初始化为0,访问其他函数未初始化的变量也不会报错。不同编译器实现不同,具体遵循书本的标准。

每个类各自决定其初始化对象的方式。而且,是否允许不经初始化就定义对象也由类自己决定。如果类允许这种行为,它将决定对象的初始值到底是什么。

在C语言中:其中静态变量和全局变量只初始化一次,未显式初始化时,默认初始化为0,这就是为什么【定义于任何函数体之外的变量被初始化为0】的原因。

PS:静态变量和外部(全局)变量在程序调入内存时已准备就绪。
 
另外,在MS的VS中,如果内置类型的局部变量没有被初始化,是不能以任何方式访问,包括输出和复制,VS2013编译时会报错!
不同编译器实现不同,具体遵循书本的标准。
 
重要的是:不要使用未初始化的对象!,否则很可能毫无头绪的程序就崩溃了!
 
C++是一种静态类型(statically typed)语言。
 
 

5.标识符

标识符的读音是biao zhi(四声,同“志”读音相同) fu,所谓标识符就是为一段内存起个易于记忆理解的名字,命名规则是只允许有字面、数字、下划线,且不能以数字开头。

每个标识符都有一个有效的作用域,即在一定范围内起作用,这就是作用域,超过作用域则无效。全局作用域一般定义在函数外面,从声明处开始到代码文件底部结束,持续有效。局部作用域只在定义的函数体内部有效,超过函数则失效。作用域可以嵌套,内层隐藏外层。外层被隐藏时,使用作用域符号显示指定才能访问外层作用域的标识符(变量),此时外层作用域一般没有名字,直接::variable即可,左侧不需要namespace。

 

6.引用与指针

定义引用时,程序把引用和引用的对象初始值绑定(bind)在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。
对于引用来说,只能绑定相同类型的对象,而不能在不同类型之间进行绑定,例如
int i = 5;
int &ri = i;            //正确,相同类型间的绑定
double &rd = i;       //错误,不同类型,无法绑定

 

指针值

指针的值(即地址)应属下列4种状态之一:

1.指向一个对象。

2.指向紧邻对象所占空间的下一个位置。  //这种是C++特有的一种状态,主要在容器和泛型算法中使用(迭代器),比如一个含有3个元素的int数组 int a[3];其内的元素分别为a[0]、a[1]、a[2],当在for或者while循环中遍历数组时,循环判断结束条件可以用a[i] == a[3],一旦a[i] == a[3]成立,说明已经到达数组的最后一个元素的下一个位置了,即刚刚溢出就把循环终止了,而没有造成任何异常,仅仅拿来比较地址,参与判断,因此不是一个无效指针,也不会造成危害,这就是这样一种特殊的指针状态。

3.空指针,意味着指针没有指向任何对象。也就是nullptr。

4.无效指针,也就是上述情况之外的其他值。

另外,未初始化的指针是极度危险的。
 

指针可以拿来直接做bool判断,只要指针拥有一个合法值,就能将它用在条件表达式中。和采用算术值作为条件遵循的规则类似,如果指针的值是0,条件取false:
int ival = 1024;
int *pi = 0; // pi 合法,是一个空指针
int *pi2 = &ival; // pi2 是一个合法的指针,存放着ival 的地址
if (pi) // pi 的值是0,因此条件的值是false
if (pi2) // pi2 指向ival,因此它的值不是0,条件的值是true

这里的if (pi)   pi 的值是0,因此条件的值是false,其实if ( pi )是与if ( pi != NULL )等价的,故判断的结果是false。
 
空类型指针(void * p)是没有办法直接操作的,只能用来作比较,赋值给另外一个void *p的空类型指针,或者用来作为函数的输入输出,不能进行其他操作,原因是空类型指针只能定位到具体的地址,但是不知道采取什么样的操作,因为不理解类型,前面说过,类型决定了相应的操作,存储范围。
 
 

7.const限定符

const主要用来限定一个变量的内容不可改变,经const修饰后的变量,可以叫他常量,也可以叫他常变量。

有些复合类型含有双层的const,一方面是自身是const,另一方面是所指对象或所引用对象是const,本身是const称为顶层const,而所指对象或引用对象是const,则称呼为底层const。

关于顶层const和底层const的理解记忆有个简单的方法,如下:

对于const int *p 和 int * const p,前者const修饰的是 int *p,把*p拿出来看就是解引用,也即p所指的对象,const修饰的是这个解引用的对象,因此是底层const,后者const修饰的p,即指针自身,因此是顶层const。

 

8.constexpr和常量表达式

不论是const 指针,实数,还是引用对象,统统需要初始化。  

const引用可以用字面值进行初始化,例如 const int &r=2 是合法的;书本在前面说过普通引用类型定义时必须初始化,且要绑定到一个对象而非一个字面常量上,例如 int &r=2 就是非法的。

对于用constexpr声明的变量是const的,且必须用常量表达式来初始化,另外使用constexpr声明的指针是顶层const指针,不会产生底层const,也就是说下面2个声明是完全不同的:

const int *p=&i;  //指向一个常量i的指针变量p,i原本可以是常量,也可以是变量

constexpr int *p=&i;  //指向一个变量i的指针常量p,i原本必须是变量,若i是常量,则*p和i不匹配,编译错误。

constexpr声明的指针不能指向局部变量,必须指向全局对象(不在任何函数体内),因为用constexpr声明的都必须在编译期间确定,而局部变量的地址在编译期间是未知的。

用constexpr是在告诉编译器,编译时期就要初始化这个常量,而const,却不一定,因为很可能你需要用一个运行时才能决定的值去初始化一个你不希望改变的量,这个时候就不能用constexpr了,必须用const

 

常量表达式的值是在编译期间,注意不是运行期间就能得到,使用constexpr修饰的表达式,表达式中用到的类型必须都是字面值类型。

什么是字面值类型(literal type)?字面值类型和字面值并不是一回事,字面值类型那些能存储字面值的基本内置算术类型,比如int类型,int类型能存储字面值42,且int是个基本的内置类型。另外,引用,指针也属于字面值类型。第7章还将介绍字面值类类型。

 

9.类型推断

auto能够使编译器通过初始值来自动推算变量的类型,因此auto定义的变量要初始值,变量必须初始化。auto并不能推断出顶层const,也不能推断出引用,若需要则要显式指定。另外,一条以分号结束的语句当中,一个auto只能推断一种基本数据类型,例如int i=5;auto a=i,*b=&i;这个是正确的,但是int m=5;double n=3.14;auto a=m,b=n,*c=&n;则是错误的,因为b和c的基本数据类型不同于a,故不能编译通过。

decltype是另一种用来推断类型的关键词,与auto不同的是decltype不需要用一个表达式来初始化被推断类型的变量,它只返回操作数类型,也即decltype声明的变量可以是未初始化的。例如:

decltype ( fun() ) sum ;函数fun()返回的类型就是sum的类型,这里在推断sum类型时,不会调用fun()函数,因此推测其应该是根据声明的函数原型的返回类型来推断类型。

与auto相反,decltype能推断出const和引用,decltype的参数是一个非变量的表达式时,decltype返回表达式结果类型,例如:

int i=1,*p=&i,&r=i;decltype(r) a=i;decltype (r+0) b;这里decltype(r) 返回的是引用,必须初始化,而decltype(r+0)返回的则是int类型,因此b可以不初始化 。

特别注意:decltype(*p)返回的是引用类型,而非指针类型,因此decltype也不能推断出指针类型,这个地方感觉语言标准设计特别混乱,不知道设计者们怎么想的。

decltype的参数额外增加括号时,始终返回引用,比如decltype((i))d;这个地方返回的是引用类型,因此d必须要初始化进行绑定,故这里是错误的。

 

10.自定义数据结构

自定义的数据类型可以使用struct来定义,在定义自定义的数据类型时,不仅可以自己定义它的数据,也可以定义该类数据支持的运算。定义数据成员时,可以使用类内初始值,即成员的默认值,但在C语言中,默认值不被支持。

 

11.头文件

头文件一般包含的是声明,而不是定义,因为声明可以多次,而定义只能一次,否则会报重复,导致冲突。

一个包含头文件的分离式编译示意图如下:

 主函数main()定义在文件main.cpp中,自定义函数fun()定义在fun.cpp中,fun()的声明在头文件fun.h中。

 

在编译时,分别将各个.cpp的文件单独不链接编译,编译完毕后,再统一链接,例如使用g++来分离式编译,g++  -c main.cpp  fun.cpp,这里的-c参数是 Compile and assemble, but do not link(编译并汇编,但不链接),编译成功后会输出2个.o的文件,一个是main.o,一个是fun.o,当所有的源代码都编译好后,链接即可成为一个完整的重新,例如g++ main.o fun.o -o main,这样链接后会生成main可执行文件,这里的-o参数是指定链接后输出的完整程序名。

注意:fun()函数的声明和定义一定要分离开来。不能只定义fun()函数,然后在main()函数文件内包含引用进fun.cpp文件,这样会导致链接错误,具体请参照编译原理。 

 

练习2.1 类型int、long、long long和short的区别是什么?无符号类型和带符号类型的区别是什么?float和double的区别是什么?

解答:

以上这些类型之间的区别都是存储位数的不同,因而导致了不同的存储范围,float和double还存在精度上的差异。

 

练习2.2 计算按揭贷款时,对于利率、本金和付款分别应选择何种数据类型?说明你的理由。

由于利率、本金和付款都是实数,带有小数,因此应该选中float或者double,这里2者的精度和范围都足够,可以任意选择。

 

练习2.3 读程序写结果。

unsigned u = 10,u2 = 42;        
std::cout << u2 - u << std::endl;  //32
std::cout << u - u2 << std::endl;   //4294967264
int i = 10,i2 = 42;
std::cout << i2 - i << std::endl;  //32
std::cout << i - i2 << std::endl;  //-32
std::cout << i - u << std::endl;  //0
std::cout << u - i << std::endl;  //0


练习2.4 编写程序检查你的估计是否正确,如果不正确,请仔细研读本节直到弄明白问题所在。

解答:答案见上题注释。

 

练习2.5 指出下述字面值的数据类型并说明每一组内几种字面值的区别:

(a) 'a', L'a', "a", L"a"
(b) 10, 10u, 10L, 10uL, 012, 0xC
(c) 3.14, 3.14f, 3.14L
(d) 10, 10u, 10., 10e-2
解答:
(a)第一个是单char字符,第二个是单wchar_t宽字符,第三个是单个字符串,在内存中存储时,末尾隐含一个'\0'空字符,第四个是单个wchar_t类型的宽字符串,在内存中存储时,末尾也隐含一个'\0'空字符。
(b)第一个是一个int整型,第二个是无符号整型,第三个是long int型,第四个是unsigned long型,第五个是八进制的int
型,最后一个是16进制的int型
(c)第一个是double类型,第二个是float类型,第三个是long double类型
(d)第一个是整型,第二个是无符号整型,第三个是double类型,最后一个是double类型的科学记数法表示。

 

练习2.6 下面两组定义是否有区别,如果有,请叙述之:

int month = 9, day = 7;
int month = 09, day = 07;
解答:有区别,第一行是十进制整型,第二行是八进制整型,但是没有09这一表示。

练习2.7 下述字面值表示何种含义?它们各自的数据类型是什么?

(a) "Who goes with F\145rgus?\012"
(b) 3.14e1L
(c) 1024f
(d) 3.14L
解答:
(a)Who goes with Fergus? 换行。\145是字母‘e’,\012是换行符。这三一个字符串类型。
(b)3.14*10^1,long double类型
(c)无意义,错误
(d)3.14 long double类型

练习2.8 请利用转移序列编写一段程序,要求先输出2M,然后转到新一行。修改程序使其先输出2,然后输出制表符,在输出M,最后转到新一行。

解答:

#include <iostream>

int main()
{
	std::cout << "\062\115\012" ;    // \062是2  \115是M  \012是换行
	std::cout << "\062\011\115\012";  // \011是制表符
	return 0;
}

  

练习2.9 解释下列定义的含义。对于非法的定义,请说明错再何处并将其改正。
(a) std::cin >> int input_value;
(b) int i = {3.14};
(c) double salary = wage = 9999.99;
(d) int i = 3.14;
解答:
(a)错误,第一章曾经说明过,输入运算符其左侧是一个istream的流对象,右侧是一个用来存储istream流的对象,不能是一个定义或者声明语句,修改为
int input_value;
std::cin>>input_value;
(b)不正确,有的编译能通过,有的编译器失败。
(c)错误,这种定义方式在实际的数学中看起来逻辑正确,但是在C++编程中是错误的!修改为 double salary(9999.99),wage(9999.99);
(d)不正确,但编译能通过,有的编译器会给出警告。
 
练习2.10 下列变量的初值分别是什么?
std::string global_str;
int global_int;
int main()
{
  int local_int;
  std::string local_str;
}
解答:
global_str初始值是空字符串,global_int初始值是0,local_int初始值未知(实际编译结果是0,有的却警告),local_str初始值是空字符串。
 
 
练习2.11 指出下面的语句是声明还是定义:
(a)extern int ix = 1024;
(b)int iy;
(c)extern int iz;
解答:
(a)定义  (b)声明并定义  (c)声明

练习2.12 请指出下面的名字中哪些是非法的?
(a) int double = 3.14
(b) int _;
(c) int catch-22;
(d) int 1_or_2 = 1;
(e) double Double = 3.14;
解答:(a)非法  (b)合法  (c)非法  (d)非法  (e)合法

练习2.13 下面程序中j的值是多少?
int i = 42;
int main()
{
   int i = 100;
   int j = i;
}
解答:j的值是100

练习2.14 下面的程序合法吗?如果合法,它将输出什么?
int i = 100, sum = 0;
for (int i = 0; i != 10; ++i)
 sum += i;
std::cout << i << " " << sum << std::endl;
解答:合法,输出是100和45。
  
 
练习2.15 下面那个定义是不合法的?为什么?
(a) int ival = 1.01;
(b) int &rval1 = 1.01;
(c) int &rval2 = ival;
(d) int &rval3;
解答:(a)合法,但不应该用实数赋值,尾数会被舍去
(b)不合法,引用只能绑定到对象上,而不能绑定到字面常量
(c)合法
(d)不合法,引用定义时必须绑定到对象上。
 
 练习2.16 考查下面的所有赋值然后回答:哪些赋值是不合法的?为什么?哪些赋值是合法的?它们执行了什么样的操作?
int i = 0, &r1 = i; double d = 0, &r2 = d;
(a) r2 = 3.14159;
(b) r2 = r1;
(c) i = r2;
(d) r1 = d;
解答:(a)合法,给d赋值3.14159
(b)合法,给d赋值0(隐含类型转换)
(c)合法,给i赋值0
(d)合法,给i赋值0
 

练习2.18 编写代码分别更改指针的值以及指针所指向对象的值。

解答:

#include <iostream>

int main()
{
	int m = 55, n = 66;
	int *p1 = &m;
	std::cout << "address1 of p1: " << p1 << std::endl;
	std::cout << "value1 of p1:" << *p1 << std::endl;
	p1 = &n;
	std::cout << "address2 of p1: " << p1 << std::endl;
	std::cout << "value2 of p1:" << *p1 << std::endl;

	return 0;
}

 

练习2.19  说明指针和引用的主要区别。

解答:指针是一种对象,而引用不是。指针可以为空,允许不初始化,引用不可以。
 

练习2.20 请叙述下面这段代码的作用。

int i = 42;

int *p1 = &i;

*p1 = *p1 * *p1;

解答:使用i的值平方运算后存储到i中

 

练习2.21 请解释下述定义。在这些定义中有非法的吗?如果有,为什么?

int i = 0;

(a) double *dp = &i;

(b) int *ip = i;

(c) int *p = &i;

解答:
(a)非法,类型不匹配
(b)非法,类型不匹配
(c)合法
 

练习2.22 假设p是一个int型指针,请说明下述代码的含义。

if(p) //...

if(*p)//...

解答:第一个,判断这个指针是否为空。第二个,判断这个指针指向的对象是否为0

 

练习2.23 给定指针p,你能知道它是否指向了一个合法的对象吗?如果能,叙述判断思路;如果不能,也请说明原因。

解答:不能知道,因为指针可以任意指向一个地址,即使有时候可能看似正确,但依然不能保证它是无害的,指针的意义就是它的值是内存地址,它将按它指向的类型的信息来解释那片空间。而那片空间是否合法,是由程序员保证,而不是指针本身。

 

练习2.24 再下面这段代码中为什么p合法而lp非法?

int i = 42;

void *p = &i;

long *lp = &i;

解答:void *是一种特殊的指针类型,可以由任何类型地址转换而来,所以p是合法的。而lp已经指明是double类型指针,故非法。

 

练习2.25 说明下列变量的类型和值。

(a) int* ip, i, &r = i;

(b) int i, *ip = 0;

(c) int *ip, ip2;

解答:

(a)ip:int型指针,值不确定; i:int整型,值不确定;r:int型引用,值不确定

(b)i:int整型,值不确定;ip:int型指针,值为空

(c)ip:int型指针,值不确定;ip2:int整型,值不确定;

 

练习2.26 下面哪些句子是合法的?如果有不合法的句子,请说明为什么?

(a) const int buf;

(b) int cnt =0;

(c) const int sz = cnt;

(d) ++cnt; ++sz;

解答:(a)不合法,没有初始化

(b)合法

(c)合法

(d)第一个合法,第二个不合法,因为sz是const变量,不能做自增运算

 

练习2.27 下面哪些初始化是合法的?请说明原因。

(a) int i = -1, &r = 0;

(b) int *const p2 = &i2;

(c) const int i = -1, &r = 0;

(d) const int *const p3 = &i2;

(e) const int *p1 = &i2;

(f) const int &const r2;

(g) const int i2 = i, &r = i;

解答:(a)非法,引用r只能绑定到对象上

(b)合法

(c)合法

(d)合法

(e)合法

(f)非法,没有这种语法。

(g)合法

 

练习2.28 说明下面这些定义是什么意思,挑出其中不合法的。

(a) int i, *const cp;

(b) int *p1, *const p2;

(c) const int ic, &r = ic;

(d) const int *const p3;

(e) const int *p;

解答:以下答案经过调试验证,有疑惑的地方,可以自己上机调试

(a)定义一个int整型变量i,一个const int型指针,cp没有初始化,不合法。

(b)定义一个int型指针变量p1,一个const int型指针p2,p2没有初始化,不合法。

(c)定义一个const int整型变量ic,一个const int型引用rc,ic没有初始化,不合法。

(d)定义一个指向常量对象的int常量指针p3,没有初始化,不合法。

(e)定义一个指向int常量的指针变量p,合法。(注意:这里不是const常量,是变量,指向的是const而已,二者不同)

 
 

练习2.29  假设已有上一个练习中的哪些变量,则下面哪些语句是合法的?请说明原因。

(a) i = ic;

(b) p1 = p3;

(c) p1 = &ic;

(d) p3 = &ic;

(e) p2 = p1;

(f) ic = *p3;

解答:以下答案经过调试验证,有疑惑的地方,可以自己上机调试

(a)合法

(b)不合法,p1指向非const,p3指向const

(c)不合法,p1指向非const,ic是const

(d)不合法,p3是const变量,不能赋值

(e)不合法,p2是const变量,不能赋值

(f)不合法,ic是const变量,不能赋值

 

练习2.30  对于下面的这些语句,请说明对象被声明成了顶层const还是底层const?

const int v2 = 0;

int v1 = v2;

int *p1 = &v1, &r1 = v1;

const int *p2 = &v2, *const p3 = &i, &r2 = v2;

解答:v2是顶层const,v1、p1、r1不是const,p2是底层const,p3、r2既是顶层const,又是底层const

 

练习2.31 假设已有上一个练习中所做的那些声明,则下面的那些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。

r1 = v2;

p1 = p2; p2 = p1;

p1 = p3; p2 = p3;

解答:p1 = p2不合法,p1 = p3不合法,因为p1指向的是变量,而p2,p3指向的是常量,如果允许把p2,p3赋值到指向变量的p1,那么通过p1就能改变所指的对象,但原p2,p3并不能改变所指的常量对象。

 

练习2.32  下面的代码是否合法?如果非法,请设法将其修改正确。

int null = 0, *p = null;

解答:不合法,null是个整型,p是个int 指针,二者虽然字面值是一样的,但类型不同,不可混用。

 

练习2.33  利用本节定义的变量,判断下列语句的运行结果。

a = 42; b = 42; c = 42;

d = 42; e = 42; g = 42;

解答:d、e、g不合法,其中d和e是指针,g是const引用,不能赋值。

 代码如下所示,可以自行调试,调试时记得启用c++11选项

#include <iostream>

int main()
{
	int i = 0, &r = i;
	auto a = r;
	const int ci = i, &cr = ci;
	auto b = ci;
	auto c = cr;
	auto d = &i;
	auto e = &ci;
	const auto f = ci;
	auto &g = ci;

	a = 42;
	b = 42;
	c = 42;
	d = 42;      //error: invalid conversion from ‘int’ to ‘int*’ [-fpermissive]
	e = 42;      //error: invalid conversion from ‘int’ to ‘const int*’ [-fpermissive]
	g = 42;      //error: assignment of read-only reference ‘g’

	return 0;
}

  

练习2.34  给予上一个练习中的变量和语句编写一段程序,输出赋值前后变量的内容,你刚才判断正确吗?如果不对,请反复研读本节的示例直到你明白错在何处为止。

解答:代码见上一题。

 

练习2.35  判断下列定义推断出的类型时什么,然后写程序进行验证。

const int i = 42;

auto j = i;

const auto &k = i;

auto *p = &i;

const auto j2 = i, &k2 = i;

解答:j是int变量,k是const int引用,p是指向const的int 指针,j2是const int常量,k2是const int引用,可以用如下代码验证

#include <iostream>

int main()
{
	const int i = 42;
	auto j = i;
	const auto &k = i;
	auto *p = &i;
	const auto j2 = i, &k2 = i;

	*p = 55;
	k2 = 6;

	return 0;
}

  

 

练习2.36  关于下面的代码,请指出每一个变量的类型以及此程序结束时他们各自的值。

int a = 3, b = 4;

decltype(a) c = a;

decltype((b)) d = a;

++c;

++d;

解答:a :int,结束时a=4;b:int,结束时b=4;c:int,结束时c=4;d:int & ,结束时d=4

 

练习2.37  赋值会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果i是int,则表达式i=x的类型是int&。根据这一特点,指出下面代码中每一个变量的类型和值。

int a = 3, b = 4;

decltype(a) c = a;

decltype(a = b) d = a;

解答:a是int,值为3;b为int,值为4;c为int,值为3;d为int &,值为3

 

练习2.38  说明由decltype指定类型和由auto指定类型有何区别。请举出一个例子,decltype指定的类型与auto指定的类型一样;再举一个例子,decltype指定的类型与auto指定的类型不一样。

解答:auto必须初始化,decltype不用。auto的结果与表达形式无关,decltype结果类型与表达式形式有关

decltype指定的类型与auto指定的类型一样:auto (x)=a; decltype(x)=a;

decltype指定的类型与auto指定的类型不一样:auto (x)=a; decltype((x))=a;

 

练习2.39  编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录相关信息,以后可能会用到。

struct Foo { /* 此处为空 */ } //注意:没有分号
int main()
{
 return 0;
}

解答:会报语法错误:error: expected ‘; ’ after struct definition

 

练习2.40  根据自己的理解写出Sales_data类,最好与书中的例子有所区别。

解答:

struct Sales_data
{
	std::string item_name;
	float price=0.0f;
	unsigned short number=0;
};

 

后面2节练习需要自己敲代码实验了,就不往这贴了。  

 

posted @ 2015-03-20 17:43  impluse  阅读(1439)  评论(0编辑  收藏  举报