刚开始要学习的c++的时候看了一下primer。写了一些代码。有的东西似是而非的。最近一个月又看了一些相关书籍。
1. Inside c++ Object Model
已经写了一篇笔记。在后面看书的过程中,发现书中该书中讨论的很多东西还是很有用的。站在编译器的角度来理解c++的一些规则。
对于trival ctor等,bitwise copy 都有涉及。这在每本书书讲到深入的时候都是要涉及的。bitwise又与深拷贝,浅拷贝相关(已做笔记)
回头这本书还得翻翻。
2. template
先看了primer,感觉没有头绪。看了编程思想的模板相关章节,再回来看primer。好多了。
编程思想有利于理解。但是作为工具书,在完整性方面primer更胜一筹。比如类特化里面再特化方法,不再需要template<> ,在primer有,而编程思想没有。 p568 在类特化外部定义成员数时, 成员之前不能加template<> 标记。
关于偏特化:
template<typename ....> 里面的类型不需要跟非特化版本一样多,只要写出需要的数目。当全部不需要了,写成template<>,也就是完全特化了。
..funtion<typename .., char,,,>()。 而在特化函数体或者类的名字后面跟上类型。类型的数目跟非特化版本一致。前面的类型与本特化的类型列表一致,后面的则跟上char,void等自己需要的版本。
关于traits:
泛型编程很重要的概念就是traits。取得其真正的类型。这是打开stl源码大门的钥匙,在stl 源码剖析里面讲解得挺清楚的。
举个例子: 一个类型有其指针,怎么获得其类型呢?
可以定义一个函数, void func(T * p). 将该指针传入该函数,那么现在T就是指针的真正类型了。traits的原理类似。不过更加高级。。
typedef 得到别名。 在一个比如在一个类A typedef 得到一个value_type。那么想引用A的value_type则使用 A::value_type
template <class T>
struct type_addition //非特化版本
{
typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef const T* const_pointer;
typedef const T& const_reference;
};
template <>
struct type_addition<void>
{
typedef void value_type;
typedef void* pointer;
typedef const void* const_pointer;
};
template <class T>
struct type_addition<T*> //对于指针类型进行特化。那么如果获得的是一个指针,这里的value_type指向的依旧是其原始类型
{
typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef const T* const_pointer;
typedef const T& const_reference;
};
template <class T>
struct type_addition<T&> //对引用特化
{
typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef const T* const_pointer;
typedef const T& const_reference;
};
template <class T>
struct type_addition<const T*>
{
typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef const T* const_pointer;
typedef const T& const_reference;
};
typedef typename N::length_type length_type; typedef typename N::data_type data_type; typedef typename N::data_pointer data_pointer; typedef typename N::const_data_pointer const_data_pointer; typedef typename trait::type_addition<N>::value_type node_type;
以上代码源自管景伟教程。源代码写得不错。关键是整个思考过程。可以多加学习。这代码同时也显示了namespace的使用方法。
#include ...
namespace candidate { // 可以在多个头文件使用相同的namespace。。
namespace trait {
//实际代码
}
}
3. stl 源码剖析
stl源码剖析里面迭代器分为5类。不同算法,不同的迭代器都会导致代码的不同。
stl算法使用大量仿函数(函数对象)。使用起来像个函数,其实是对象,针对operator()进行重载。
stl源码剖析对于平衡二叉树讲的挺清楚。红黑树是stl各个容器的基础。红黑树讲得非常不错。懂了。好书。
关于内存管理,提到::operater new, ::operator delete ,placement new 等。allocate,内存管理。 讲到深入处都有关于这方面的话题。so,腾讯的place new问题不是考得太细,而是研究不够深入。primer后面也有相关章节。
4. 复制控制(拷贝构造函数,析构, operator=)
要不要自己定义拷贝构造函数, 问题的重点跟深拷贝浅拷贝是一个话题。看有没有进行空间的配置等(待补充)
5. 引用计数, 智能指针
int *ip = new int(42); HasPtr ptr(ip, 10); delete ip; ptr.set_ptr_val(0); //disaster这里的问题是ip和ptr中的指针指向同一对象,删除了该对象ptr的指针不再指向有效对象(垂悬指针)。但是,没有办法这个对象是否无效了。所以需要使用引用技术等方法。
6. 句柄
c++ primer p504
c++中面向对象编程一个颇具讽刺意味的地方是,不能使用对象支持面向对象编程。相反,必须使用指针或引用。 但是指针或引用的使用会加重类用户的负担。
解决方法:c++中一个同用的技术是定义包装类或者句柄类。句柄类存储和管理基类指针。
一下代码源自primer Handle.h。 不仅保存基类指针,而且记录引用次数
#include <stdexcept> template<class T> class Handle { public: Handle(T *p = 0) : ptr(p), use(new size_t(1)) { } T& operator*(); T* operator->(); const T& operator*() const; const T* operator->() const; Handle(const Handle& h) : ptr(h.ptr), use(h.use) { ++*use; } Handle& operator=(const Handle&); ~Handle() {rem_ref();} private: T* ptr; //shared object size_t *use; // count of how many Handles point to *ptr void rem_ref() { if(--*use == 0) { delete ptr; delete use; } } }; template <class T> inline Handle<T>& Handle<T>::operator=(const Handle& rhs) { ++*rhs.use; rem_ref(); ptr = rhs.ptr; use = rhs.use; return *this; } template<class T> inline T& Handle<T>::operator*() { if(ptr) return *ptr; throw std::runtime_error("dereference of unbound handle"); } template<class T> inline T* Handle<T>::operator->() { if(ptr) return ptr; throw std::runtime_error("access through unbound Handle"); } template<class T> inline const T& Handle<T>::operator*() const { if(ptr) return *ptr; throw std::runtime_error("deference of unbound Handle"); } template<class T> inline const T* Handle<T>::operator->() const { if(ptr) return ptr; throw std::runtime_error("access through unbound Handle"); }
class Sales_item { friend bool operator<(const Sales_item& lhs, const Sales_item& rhs); friend class Basket; public: Sales_item() : h() { } Sales_item(const Item_base& item) : h(item.clone()) { } const Item_base& operator*() const { return *h; } const Item_base* operator->() const { return h.operator->(); } private: Handle<Item_base> h; };
class Basket { std::multiset<Sales_item> items;}
在面试宝典中:句柄是指针的指针。在这里不是很适用。但是应该是一个道理。。
7. const
1)替换#define
2)在类中的const,可以实现每个对象不同。如
class A {
const size;
A(int t):size(t) { }
}
这就是必须使用初始化列表而不使用赋值初始化的地方。(初始化列表不用生成临时变量,效率也更高)
3)编译期间常量 static const 在定义的时候就必须赋值。 如static const size = 10;
4) const 函数可以被const对象和非const对象调用。但是,const 对象不能调用非const常数。(编译器的角度思考。。)
5)考虑
void u(const int * cip) {
int *ip2 = cip; //不合法的
}
将const的指针赋给非const是不合法的。
8. 异常
待看笔记完善。。。
9. 关于effective c++
里面很多对于实际编程很有意义的条款,可以避开一些错误。有空再看看。据说对笔试面试很有用。
10. 操作符重载
声明为友元与成员变量的不同之处。
++ 后置的占位符问题
特殊符号。operator-> 返回的是一个指针 a->fun();其实是 (a->fun) ();
杂项:
private定义拷贝构造函数,而不去实现,可以拒绝复制。
public protected private继承的含义。public表示“是一个”。。 private 继承通常 就像继承某些工具集。详见effective c++
有时候很多接口对外隐藏。那么把接口声明为private。这样只有成员函数可以调用(对用户公开的接口)。外部的客户通过对象是不可以直接使用该接口的。private成员变量道理也一样,只有内部成员函数,友元可以直接使用。
两个类相互依存,只要在一个类的前面先声明另一个类就可以了。如primer的Folder 和 Message类。