C++Primer —— 一本牛逼的书

作为一个精神脆弱的人,学习Primer之前可能会被800多页的厚书压垮,翻不动书;本文将以习题的形式来总览全书,主要记录一些C++语法坑点的相关习题,希望能让读者明白这本书的大致内容和优秀伟大之处,然后制定自己的读书计划。

  • 练习 1.8:指出下列哪些输出语句是合法的(如果有的话):
std::cout << "/*"; 合法
std::cout << "*/"; 合法
std::cout << /* "*/" */; 不合法
std::cout << /* "*/" /* "/*" */; 合法

答:预测编译这些语句会产生什么样的结果,实际编译这些语句来验证你的答案(编写一个小程序,每次将上述一条语句作为其主体),改正每个编译错误。
解答:本题展示了界定符的使用方法,可以看出界定符的配对原则和括号的配对原则相同

  • 练习 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;      结果为无符号值 4294967264
std::cout << u - i<< std::endl;      0

答:当无符号用于条件为不小于0的循环语句时,可能陷入死循环

for(;u>=0;--u)
	cout<<u<<endl;
  • 练习2.9:解释下列定义的含义。对于非法的定义,请说明错在何处并将其改正。
(a) std::cin >> int input_value;    非法,输出语句中出现声明语句
(b) int i = { 3.14 };               非法,花括号会进行安全检测,这里会造成小数点后值的丢失
(c) double salary = wage = 9999.99; 合法
(d) int i = 3.14;                   合法

答:花括号会执行安全检测,所以narrowing conversion会报错

  • 练习2.10:下列变量的初值分别是什么?
std::string global_str;    全局变量会初始化为空串
int global_int;            全局变量会初始化为0
int main(){
	int local_int;         值是未定义的,内置类型的局部变量进行默认初始化,内置类型的默认初始化不进行任何操作
	std::string local_str; 空串,类对象会执行默认构造函数
}

答:内置类型的局部变量的默认初始化是危险的,所以定义变量时最好带上花括号。

练习 2.11:指出下面的语句是声明还是定义:

(a) extern int ix = 1024;   定义
(b) int iy;                 声明并定义
(c) extern int iz;          声明

答: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; 合法

答:为了方便编译器进行语法分析和词法分析,标识符只能以字母,数字和下划线组成,并且只能以字母和下划线开头;同时不能出现内置标识符

练习 2.15:下面的哪个定义是不合法的?为什么?

(a) int ival = 1.01;     合法
(c) int &rval2 = ival;   合法
(b) int &rval1 = 1.01;   不合法,不能给临时变量起别名
(d) int &rval3;          不合法,初始化引用时必须指定要引用的变量

答:只能给具有局部变量和全局变量起别名,即定义引用

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

(a) int* ip,i, &r = i;   ip为int指针,i为int变量,r为int引用;变量的值根据是全局还是局部变量有所不同
(b) int i, *ip = 0;      i为int变量,ip为int指针
(c) int* ip, ip2;        ip为int指针,ip2为int变量

答:C++复合类型的定义需要将类型和复合类型声明符分开解析,指针和引用的声明非常容易产生误导;理解类型是类型,复合类型是复合类型是关键,这也是C++为什么要这样设计复合类型声明的原因

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

(a) const int buf;  				非法,常量必须初始化
(b) int cnt = 0;						合法
(c) const int sz = cnt;			合法
(d) ++cnt; 							非法,常量不能被改变
(e) const int &rcnt = 2;			合法,因为常量引用不会改变,因此可以绑定一个字面值或者临时量

答:
1. 常量是不可被改变的值,因此必须将其初始化,确定常量的值;
2. 并且全局常量的作用域是当前文件,需要在定义和声明时加上extern表明只有一个地方可以定义。

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

const int v2 = 0; 															顶层const
int v1 = v2;																	普通遍历
int *p1 = &v1, &r1 = v1;												普通指针和引用
const int *p2 = &v2, *const p3 = &i, &r2 = v2;				p2为底层常量指针,p3为底层和顶层常量指针,r2为底层常量引用

答:引用的顶层常量没有什么用,底层常量表示引用的对象不可改变;指针的底层常量表示执行的对象不可改变,指针的顶层常量表示指针本身不可改变

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

r1 = v2;		合法,常量可以赋值给非常量,因为常量本身不会受到影响
p1 = p2; 		非法,指针的底层常量只能赋值给其他底层常量指针,因为底层常量指向的值不可更改
p2 = p1;		合法,普通指针可以赋值给底层常量指针
p1 = p3; 		非法, 同第二个
p2 = p3;		合法,底层常量类型相同,顶层常量可以赋值给非顶层常量,因为顶层常量指针指向的地址不会改变。

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

const int i = 42;					
auto j=i; 									j为int类型,顶层const将被抛弃
const auto &k = i;					k为const int &类型
const auto *p = &i;					p为const int *类型
const auto p = &i;					p为const int *const类型
const auto j2 = i, &k2 = i;			j为const int,k2是const int &

**答:顶层const和引用将被抛弃,底层const将被保留,比较难理解的有:
1. const auto p = &i,加const之前,auto为const int*类型,加上const,添加了顶层const
2. const auto *p=&i,为了初始化多个变量时,能够初始化指针,这里将auto的类型推断为int,p为const int *类型
3. auto p = &i,将保留底层const,p也是const int 类型

练习2.38:说明由decltype指定类型和由auto指定类型有何区别。请举出一个例子,decltype指定的类型与auto指定的类型一样;再举一个例子,decltyp指定的类型与auto指定的类型不一样。
答:decltyp的类型会保留表达式的const和引用属性,并且与表达式的返回类型息息相关

int a = 0;
const int &i=a;
auto J1 = a;   decltype(a) J2 = a;	两个类型相同,都为int
auto L1 = i;	decltype(i) L2 = a;		L1为int类型,L2为const int&类型

decltype(++a) A = a;  						A为int&类型,decltype的类型与表达式的返回类型相关

非习题:判断function的初始化是否正确

int add(int, int){
    return 1;
}

class  test{};

test add(test, test)
{
    return test();
}

int main()
{
    function<int(int, int)> t = add; 
}

答:不正确,初始化function时,编译器首先会进行标识符匹配,然鹅有两个add;

练习 14.48:你在 7.5.1 节的练习 7.40(第 261 页)中曾经选择并编写了一个类,你认为它应该含有向 bool 的类型转换运算符吗?如果是,解释原因并说明该运算符是否应该是 explicit 的;如果不是,也请解释原因。

练习 14.50:在初始化 ex1 和 ex2 的过程中,可能用到哪些类类型的转换序列呢?说明初始化是否正确并解释原因。

struct LongDouble 
{
	LongDouble (double = 0.0);
	operator double();
	operator float ();
};
LongDouble ldobj;
int exl = ldObj; // 初始化失败,会产生向double和float转换的两种候选
float ex2 = ldObj;

练习14.51:在调用calc的过程中,可能用到哪些类型转换序列呢?说明最佳可行函数是如何被选出来的。

void calc(int);
void calc (LongDouble);
double dval;
calc (dval);// 哪个 calc?

答:调用的void (int)类型的函数,因为编译器优先进行内置类型转换,从而匹配int类型。

练习 15.4:下面哪条声明语句是不正确的?请解释原因。

class Base { ... };
(a) class Derived : public Derived { ... };
(b) class Derived : private Base { ... };
(c) class Derived : public Base;

答:c是错的,派生列表必须与类定义一起出现,可能存在继承列表相当于对类进行了部分定义,我不是很清楚。

练习15.18:假设给定了第543页和第544页的类,同时已知每个对象的类型如注释所示,判断下面的哪些赋值语句是合法的。解释那些不合法的语句为什么不被允许:

Base *p = &d1;// d1 的类型是 Pub_Derv              合法,公有继承,基类类型对用户可见   
p = &d2;// d2 的类型是 Priv_Derv                   不合法,私有继承,基类类型对用户不可见
p = &d3;// d3 的类型是 Prot_Derv                   不合法,受保护继承,基类类型对用户不可见
p = &dd1;// dd1的类型是Derived_from_Public         合法
p = &dd2;// dd2的类型是Derived_from_Private        不合法
p = &dd3;//dd3 的类型是 Derived_from_Protected     不合法

答:继承访问说明符相当于在派生类中更改了基类的公有成员和受保护成员的访问权限,想要改变部分继承而来的成员的访问权限的话,可以通过using声明来显示说明基类中成员的访问权限。

public:
	using Base::basemember;

练习15.19:假设543页和544页的每个类都有如下形式的成员函数:

void memfcn (Base &b) { b = *this; }
对于每个类,分别判断上面的函数是否合法。

答:

  • Pub_Derv 合法

  • Priv_Derv 合法

  • Prot_Derv 合法

  • Derived_from_Public 合法

  • Derived_from_Private 不合法,Priv_Derv会将基类成员声明为私有,对于派生类来说不可访问,不能进行转换。

  • Derived_from_Protected 合法

  • 常量

    • constexpt int *q 是一个顶层常量指针
    • 聚合类可以作为字面值常量
    • 非聚合类,但是具有常量构造函数,没有析构函数等可能造成对象成员发生变化的函数
  • 拷贝构造函数

    • 需要在成员更新时,更新需要拷贝的成员,
  • 赋值函数

    • 通过swap来复用拷贝构造函数和析构函数

推荐一个零声教育学习教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习


姑且先写这么多吧,第一次写技术总结博客,感觉精神疲惫,深怕写错。
每天会陆续补一些,劳烦读者老爷们给点批评建议。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值