99.重点记录/课后习题重点记录

C++ learning

第九十九章 重点记录/课后习题重点记录

1.第一章的重点及练习

2.第二章的重点及练习

3.第三章的重点及练习

  • 头文件中不应该包含using声明。这样使用了该头文件的源码也会使用这个声明,会带来风险。

  • 字符串字面值和string是不同的类型。

  • str[x],[]输入参数为string::size_type类型,给出int整型也会自动转化为该类型 ,数组下标的类型:size_t

  • 但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。

  • 初始化:char input_buffer[buffer_size];,长度必须是const表达式,或者不写,让编译器自己推断。

  • 使用数组时,编译器一般会把它转换成指针。

  • 标准库类型限定使用的下标必须是无符号类型,而内置的下标可以处理负值。

  • 在表达式中使用数组名时,名字会自动转换成指向数组的第一个元素的指针。

  • 下面的程序有何作用?它合法吗?如果不合法?为什么?不合法。使用下标访问空字符串是非法的行为。

1
2
string s;
cout << s[0] << endl;
  • 将标点符号去除后输出字符串剩余的部分。关键代码if (!ispunct(x))

  • 下面的范围for语句合法吗?如果合法,c的类型是什么? 要根据for循环中的代码来看是否合法,c是string 对象中字符的引用,s是常量。因此如果for循环中的代码重新给c赋值就会非法,如果不改变c的值,那么合法。

1
2
const string s = "Keep out!";
for(auto &c : s){ /* ... */ }
  • 下列的vector对象各包含多少个元素?这些元素的值分别是多少?
1
2
3
4
5
6
7
vector<int> v1;         // size:0,  no values.
vector<int> v2(10);     // size:10, value:0
vector<int> v3(10, 42); // size:10, value:42
vector<int> v4{ 10 };     // size:1,  value:10
vector<int> v5{ 10, 42 }; // size:2,  value:10, 42
vector<string> v6{ 10 };  // size:10, value:""
vector<string> v7{ 10, "hi" };  // size:10, value:"hi"
  • 在100页的二分搜索程序中,为什么用的是 mid = beg + (end - beg) / 2, 而非 mid = (beg + end) / 2 ; ? 因为两个迭代器相互之间支持的运算只有 - ,而没有 + 。 但是迭代器和迭代器差值(整数值)之间支持 +
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
假设`txt_size`是一个无参函数,它的返回值是`int`。请回答下列哪个定义是非法的,为什么?


unsigned buf_size = 1024;
(a) int ia[buf_size];
(b) int ia[4 * 7 - 14];
(c) int ia[txt_size()];
(d) char st[11] = "fundamental";


解:

-   (a) 非法。维度必须是一个常量表达式。
-   (b) 合法。
-   (c) 非法。txt\_size() 的值必须要到运行时才能得到。
-   (d) 非法。数组的大小应该是12

假定p1p2 都指向同一个数组中的元素,则下面程序的功能是什么?什么情况下该程序是非法的?

1
p1 += p2 - p1;

解:

p1 移动到 p2 的位置。任何情况下都合法。

4

求值顺序:int i = f1() + f2() 先计算f1() + f2(),再计算int i = f1() + f2()。但是f1和f2的计算先后不确定 但是,如果f1、f2都对同一对象进行了修改,因为顺序不确定,所以会编译出错,显示未定义

取余运算m%n,结果符号与m相同

短路求值:逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。先左再右 小技巧,声明为引用类型可以避免对元素的拷贝,如下,如string特别大时可以节省大量时间。 vector text; for(const auto &s: text){ cout«s; }

赋值运算符满足右结合律,这点和其他二元运算符不一样。 ival = jval = 0;等价于ival = (jval = 0);

复合赋值运算符,复合运算符只求值一次,普通运算符求值两次。(对性能有一点点点点影响) 任意复合运算符op等价于a = a op b;

iter++等价于(iter++),递增优先级较高

ptr->mem等价于(ptr).mem ,注意.运算符优先级大于,所以记得加括号

可以嵌套使用,右结合律,从右向左顺序组合

finalgrade = (grade > 90) ? “high pass” : (grade < 60) ? “fail” : “pass”; //等价于 finalgrade = (grade > 90) ? “high pass” : ((grade < 60) ? “fail” : “pass”); 输出表达式使用条件运算符记得加括号,条件运算符优先级太低。

有符号数负值可能移位后变号,所以强烈建议位运算符仅用于无符号数。

逗号运算符从左向右依次求值,左侧求值结果丢弃,逗号运算符结果是右侧表达式的值。

5.

C++语言没有明确规定大多数二元运算符的求值顺序,给编译器优化留下了余地。这种策略实际上是在代码生成效率和程序潜在缺陷之间进行了权衡,你认为这可以接受吗?请说出你的理由。 可以接受。C++的设计思想是尽可能地“相信”程序员,将效率最大化。然而这种思想却有着潜在的危害,就是无法控制程序员自身引发的错误。因此 Java 的诞生也是必然,Java的思想就是尽可能地“不相信”程序员。

1
2
3
for: for语句可以省略掉 init-statement, condition和 expression的任何一个;甚至全部。

什么是“悬垂else”?C++语言是如何处理else子句的? 解: 用来描述在嵌套的if else语句中,如果if比else多时如何处理的问题。C++使用的方法是else匹配最近没有配对的if 
1
2
3
4
5
6
12 / 3 * 4 + 5 * 15 + 24 % 4 / 2
解:
((12 / 3) * 4) + (5 * 15) + ((24 % 4) / 2) = 16 + 75 + 0 = 91

逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。这种策略称为 短路求值。
相等性运算符未定义求值顺序。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
解释在下面的if语句中条件部分的判断过程

const char *cp = "Hello World";
if (cp && *cp)
解:

首先判断cpcp 不是一个空指针,因此cp为真。然后判断*cp*cp 的值是字符'H',非0。因此最后的结果为真。

执行下述 if 语句后将发生什么情况?

if (42 = i)   // 编译错误。赋值运算符左侧必须是一个可修改的左值。而字面值是右值。
if (i = 42)   // true.

尽管下面的语句合法,但它们实际执行的行为可能和预期并不一样,为什么?应该如何修改?

if (p = getPtr() != 0)
if (i = 1024)
解:

if ((p=getPtr()) != 0)
if (i == 1024)
1
2
3
4
5
6
7
8
说明前置递增运算符和后置递增运算符的区别。

解:

前置递增运算符将对象本身作为左值返回,而后置递增运算符将对象原始值的副本作为右值返回。


(c) vec[ival++] <= vec[ival]  (c) 表达式有误。C++并没有规定`<=`运算符两边的求值顺序,应该改为`vec[ival] <= vec[ival+1]`
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

因为运算符的优先级问题,下面这条表达式无法通过编译。根据4.12节中的表指出它的问题在哪里?应该如何修改?


string s = "word";
string pl = s + s[s.size() - 1] == 's' ? "" : "s" ;


解:

加法运算符的优先级高于条件运算符。因此要改为:


string pl = s + (s[s.size() - 1] == 's' ? "" : "s") ;
1
2
3
4
5
6
7
8
推断下面代码的输出结果并说明理由。实际运行这段程序,结果和你想象的一样吗?如不一样,为什么?

int x[10];   int *p = x;
cout << sizeof(x)/sizeof(*x) << endl;
cout << sizeof(p)/sizeof(*p) << endl;
解:

第一个输出结果是 10。 第二个结果取决于机器。sizeof(p)即int*的内存空间大小, 32位机4B, 64位机8B; sizeof(*p)即sizeof(int), 通常为4B, C++标准规定其不得小于2B。

6.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
说明形参、局部变量以及局部静态变量的区别。编写一个函数,同时达到这三种形式。

解:

形参定义在函数形参列表里面;局部变量定义在代码块里面; 局部静态变量在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止时才被销毁。

// 例子
int count_add(int n)       // n是形参
{
    static int ctr = 0;    // ctr 是局部静态变量
    ctr += n;
    return ctr;
}

int main()
{
    for (int i = 0; i != 10; ++i)  // i 是局部变量
      cout << count_add(i) << endl;

    return 0;
}

7

1
2
3
4
5
使用class和struct时有区别吗?如果有,是什么?

解:

class和struct的唯一区别是默认的访问级别不同。'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
封装是何含义?它有什么用处?

解:

将类内部分成员设置为外部不可见,而提供部分接口给外面,这样的行为叫做封装。

用处:

1.确保用户的代码不会无意间破坏封装对象的状态。
2.被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。

8.IO库

1
2
3
4
s.eof()	若流s的eofbit置位,则返回true
s.fail()	若流s的failbit置位,则返回true
s.bad()	若流s的badbit置位,则返回true
s.good()	若流s处于有效状态,则返回true
1
2
3
什么情况下,下面的while循环会终止?
while (cin >> i) /*  ...    */
解:如badbit、failbit、eofbit 的任一个被置位,那么检测流状态的条件会失败。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
istream& func(istream &is)
{
    std::string buf;
    while (is >> buf)
        std::cout << buf << std::endl;
    is.clear();
    return is;
}

有些情况下我们希望提供cin作为接受istream&参数的构造函数的默认实参,请声明这样的构造函数。

解:

Sales_data(std::istream &is = std::cin) { read(is, *this); }
1
2
3
4
5
如果接受string的构造函数和接受istream&的构造函数都使用默认实参,这种行为合法吗?如果不,为什么?

解:

不合法。当你调用Sales_data()构造函数时,无法区分是哪个重载。    
1
2
3
4
5
6
下面这条声明合法吗?如果不,为什么?

vector<NoDefault> vec(10);//vec初始化有10个元素
解:

不合法。因为NoDefault没有默认构造函数。

9

const_iterator 可以读取元素但不能修改元素的迭代器类型

C c1(c2);或C c1 = c2; 构造c2的拷贝c1

使用非成员版本的swap是一个好习惯。

当我们用一个对象去初始化容器或者将对象插入到容器时,实际上放入的是对象的拷贝。

emplace开头的函数是新标准引入的,这些操作是构造而不是拷贝元素。

会改变容器大小,不适用于array。 forward_list有特殊版本的erase forward_list不支持pop_back vector和string不支持pop_front

reverse_iterator 按逆序寻址元素的迭代器 const_reverse_iterator 不能修改元素的逆序迭代器 c.rbegin(), c.rend() 返回指向c的尾元素和首元素之前位置的迭代器 c.crbegin(), c.crend() 返回const_reverse_iterator

迭代器 迭代器范围:begin到end,即第一个元素到最后一个元素的后面一个位置。 左闭合区间:[begin, end)

容器操作可能使迭代器失效 在向容器添加元素后: 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针、引用都会失效。 对于deque,插入到除首尾位置之外的任何位置都会导致指向容器的迭代器、指针、引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在元素的引用和指针不会失效。 对于list和forward_list,指向容器的迭代器、指针和引用依然有效。 在从一个容器中删除元素后: 对于list和forward_list,指向容器其他位置的迭代器、引用和指针仍然有效。 对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、指针、引用都会失效;如果是删除deque的尾元素,则尾后迭代器会失效,但其他不受影响;如果删除的是deque的头元素,这些也不会受影响。 对于vector和string,指向被删元素之前的迭代器、引用、指针仍然有效。 注意:当我们删除元素时,尾后迭代器总是会失效。 注意:使用失效的迭代器、指针、引用是严重的运行时错误! 建议:将要求迭代器必须保持有效的程序片段最小化。 建议:不要保存end返回的迭代器。

定义在stack头文件中。 stack默认基于deque实现,也可以在list或vector之上实现。

定义在stack头文件中。 stack默认基于deque实现,也可以在list或vector之上实现。

有时可能希望操作可以接受更多的参数。

lambda表达式表示一个可调用的代码单元,可以理解成是一个未命名的内联函数。

形式:[capture list](parameter list) -> return type {function body}。

其中capture list捕获列表是一个lambda所在函数定义的局部变量的列表(通常为空)。不可忽略。 return type是返回类型。可忽略。 parameter是参数列表。可忽略。 function body是函数体。不可忽略。 auto f = [] {return 42;}

只读算法 只读取范围中的元素,不改变元素。 如 find和 accumulate(在numeric中定义,求和)。 find_first_of,输入:两对迭代器标记两段范围,在第一段中找第二段中任意元素,返回第一个匹配的元素,找不到返回第一段的end迭代器。 通常最好使用cbegin和cend。 equal:确定两个序列是否保存相同的值。

隐式捕获:让编译器推断捕获列表,在捕获列表中写一个&(引用方式)或=(值方式)。auto f3 = [=] {return v1;}

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy