之前看《C++ Primer Plus》第6版这本书后所做的笔记,先开始复习,整理出来。
1.C++的const比C语言#define更好的原因?
首先,它能够明确指定类型,有类型检查功能。
其次,可以使用C++的作用域规则将定义限制在特定的函数或文件中。
第三,可以将const用于更复杂的类型,比如数组和结构。
C语言中也有const,其与C++中const的区别是:一是作用域规则不同;另一个是,在C++中可以用const值来声明数组长度。
2.为什么说前缀++/--比后缀++/--的效率高?
对于内置类型和当代的编译器而言,这看似不是什么问题。然而,C++允许您针对类定义这些运算符,在这种情况下,用户这样定义前缀函数:将值加1,然后返回结果;但后缀版本首先复制一个副本,将其加1,然后将复制的副本返回。因此,对于类而言,前缀版本的效率比后缀版本高。
总之,对于内置类型,采用哪种格式不会有差别,但对于用户定义的类型,如果有用户定义的递增和递减运算符,则前缀格式的效率更高。
3.逗号运算符
到目前为止,逗号运算符最常见的用途是将两个或更多的表达式放到一个for循环表达式中。逗号运算符的特性有下面几个:
它确保先计算第一个表达式,然后计算第二个表达式;
i = 20, j = 2 * i; // i set to 20, then j set to 40逗号表达式的值是第二部分的值。例如,上面表达式的值为40。
在所有运算符中,逗号运算符的优先级是最低的。例如:
cats = 17, 240;
被解释为:
(cats = 17), 240;
也就是说,将cats设置为17,后面的240不起作用。如果是cats = (17, 240);那么cats就是240了。
4.快排中中值的选取:
将元素每5个一组,分别取中值。在n/5个中值里面找到中值,作为partition的pivot。
为什么*不每3个一组?保证pivot左边右边至少3n/10个元素,这样最差O(n)。
5.C++存储方案:C++三种,C++11四种
自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量。
线程存储持续性(C++11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。本书不探讨并行编程。
动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store)或堆(heap)。
6.自己写string类注意事项:
关于记录已有对象数object_count
不要在类声明(即头文件)中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象组成部分。请注意,初始化语句指出了类型int(不可缺少),并使用了作用域运算符,但没有使用关键字static。
初始化是在方法文件中,而不是在类声明文件中进行的,这是因为类声明位于头文件中,可能被包含多次,这样若在头文件中进行初始化静态成员,将出现多个初始化语句副本,从而引发错误。
对于不能在类声明中初始化静态成员的一种例外情况是:静态数据成员为整型或枚举型const。即如果静态数据成员是整型或枚举型,则可以在类声明中初始化。注意重写拷贝构造函数和赋值运算符,其中赋值运算符的原型为:
Class_name & Class_name::operator=(const Class_name &);
它接受并返回一个指向类对象的引用,目的应该是方便串联使用。
7.何时调用赋值运算符:
将已有的对象赋给另一个对象时,将调用重载的赋值运算符。
初始化对象时,并不一定会使用赋值操作符:
StringBad metoo=knot; // use copy constructor, possibly assignment, too
这里,metoo是一个新创建的对象,被初始化为knot的值,因此使用赋值构造函数。不过,正如前面指出的,实现时也可能分两步来处理这条语句:使用复制构造函数创建一个临时对象,然后通过赋值操作符将临时对象的值复制到新对象中。这就是说,初始化总是会调用复制构造函数,而使用=操作符时也可能调用赋值构造函数。
8.赋值运算符和拷贝构造函数在实现上的区别:
- 由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]来释放这些数据。
- 函数应当避免将对象赋给自身;否则给对象重新赋值前,释放内存操作可能删除对象的内容。
- 函数返回一个指向调用对象的引用(方便串联使用),而拷贝构造函数没有返回值。
StringBad & StringBad::operator=(const StringBad & st)
{
if(this == & st)
return * this;
delete [] str;
len = st.len;
str = new char [len + 1];
strcpy(str,st.str);
return *this;
}
代码首先检查自我复制,这是通过查看赋值操作符右边的地址(&s)是否与接收对象(this)的地址相同来完成的,如果相同,程序将返回*this,然后结束。
如果不同,释放str指向的内存,这是因为稍后将把一个新字符串的地址赋给str。如果不首先使用delete操作符,则上述字符串将保留在内存中。由于程序程序不再包含指向字符串的指针,一次这些内存被浪费掉。
接下来的操作与复制构造函数相似,即为新字符串分配足够的内存空间,然后复制字符串。
赋值操作并不创建新的对象,因此不需要调整静态数据成员num_strings的值。
9.重载运算符最好声明为友元
比如将比较函数作为友元,有助于将String对象与常规的C字符串进行比较。例如,假设answer是String对象,则下面的代码:
if("love" == answer)
将被转换为:
if(operator == ("love", answer))
然后,编译器将使用某个构造函数将代码转换为:
if(operator == (String("love"), answer))
这与原型是相匹配的。
10.在重写string类时使用中括号访问字符时注意:
(1)为什么重载的[]返回值是个char &而不是char?
(2)为什么有两个重载[]的版本,另一个是const版本?
解答(1):
将返回类制声明为char &,便可以给特定元素陚值。例如,可以编写这样的代码:
String means ("might");
means [9] = ' r';
第二条语句将被转换为一个重载运算符函数调用:
means.operator[][0] = 'r';
这里将r陚给方法的返回值,而函数返回的是指向means.str[0]的引用,因此上述代码等同于下面的代码:
means.str[0] = 'r';
代码的最后一行访问的是私有数据,但由于operator 是类的一个方法,因此能够修改数组的内容。 最终的结果是“might”被改为“right”。
解答(2):
假设有下面的常量对象:
const String answer("futile");
如果只有上述operator定义,则下面的代码将出错:
cout << answer[1]; // compile-time error
原因是answer是常量,而上述方法无法确保不修改数据(实际上,有时该方法的工作就是修改数据, 因此无法确保不修改数据)。
但在重载时,C++将区分常量和非常量函数的特征标,因此可以提供另一个仅供const String对象使用 的 operator版本:
// for use with const String objects
const char & string::operator const {
return str[i];
}
有了上述定义后,就可以读/写常规String对象了 :而对于const Siring对象,则只能读取其数据。
11.关于智能指针
12.C++中的容器种类:
- 序列容器(7个)
vector:提供了自动内存管理功能(采用了STL普遍的内存管理器allocator),可以动态改变对象长度,提供随机访问。在尾部添加和删除元素的时间是常数的,但在头部或中间就是线性时间。
deque:双端队列(double-ended queue),支持随机访问,与vector类似,主要区别在于,从deque对象的开始位置插入和删除元素的时间也是常数的,所以若多数操作发生在序列的起始和结尾处,则应考虑使用deque数据结构。为实现在deque两端执行插入和删除操作的时间为常数时间这一目的,deque对象的设计比vector更为复杂,因此,尽管二者都提供对元素的随机访问和在序列中部执行线性时间的插入和删除操作,但vector容器执行这些操作时速度更快些。
list:双向链表(是循环的)。目的是实现快速插入和删除。
forward_list(C++11):实现了单链表,不可反转。相比于list,forward_list更简单,更紧凑,但功能也更少。
queue:是一个适配器类。queue模板让底层类(默认是deque)展示典型的队列接口。queue模板的限制比deque更多,它不仅不允许随机访问队列元素,甚至不允许遍历队列。与队列相同,只能将元素添加到队尾、从队首删除元素、查看队首和队尾的值、检查元素数目和测试队列是否为空。
priority_queue:是另一个适配器类,支持的操作与queue相同。
priority_queue模板类是另一个适配器类,它支持的操作与queue相同。两者之间的主要区别在于,在priority_queue中,最大的元素被移到对首。内部区别在于,默认的底层类是vector。可以修改用于确定哪个元素放到队首的比较方式,方法是提供一个可选的构造函数参数:priority_queue<int> pq1; // default version priority_queue<int> pg2(greater<int>); // use greater<int> to order greater<>函数是一个预定义的函数对象。
stack:与queue相似,stack也是一个适配器类,它给底层类(默认情况下为vector)提供了典型的栈接口。
- 关联容器
4种有序关联容器:set、multiset、map和multimap,底层基于树结构
C++11又增加了4种无序关联容器:unordered_set、unordered_multiset、unordered_map和unordered_multimap,底层基于hash。