我要好offer之 C++大總結

0. Google C++編程規範

英文版:http://google-styleguide.googlecode.com/svn/trunk/cppguide.xmlhtml

中文版:http://zh-google-styleguide.readthedocs.org/en/latest/google-cpp-styleguide/contents/node

 

1. C++函數的林林總總

2. Effective C++學習筆記

(1) 習慣c++,const特性linux

(2) 構造、析構、賦值、copy and swapios

(3) RAII資源管理c++

(4) 傳值or傳引用、禁止返回局部對象指針、實現swapgit

(5) 少轉型、異常安全、inline、編譯依賴(pimpl手法)github

(6) 繼承本質、接口繼承、實現繼承算法

 

3. Effective STL學習筆記

(1) 容器:分類、區間操做優於單元素循環操做、容器不是線程安全shell

(2) vector優於數組、string優於char*、vector的reverse函數、swap空容器技巧編程

(3) 關聯容器:不要使用[]操做,c++11標準hash容器std::unordered_map

(4) 迭代器: 提供越界檢查、連續型容器使用 distance在 迭代器切換 idx下標

(5) 算法:多用標準庫算法、各類排序相關算法、各類二分查找相關算法

(6) 函數對象:推薦陳碩大大: std::function std::bind替代虛函數

(7) 多使用STL,容器函數優於算法庫函數,list的sort函數

 

4. C++ std::string 代碼實現

陳碩大大 std::string實現

gcc std::string 詳解

class Mystring {
    public:
        Mystring() : data_(new char[1]) {
            *data = '\0';
        }

        Mystring(const char* str) : data_(new char[strlen(str) + 1]) {
            strcpy(data_, str);
        }

        Mystring(const Mystring& str) : data_(new char[str.size() + 1]) {
            strcpy(data_, str.c_str());
        }

        ~Mystring() {
            delete[] data_;
        }
        
        // 重載賦值,採用copy and swap手法,舊式寫法
        Mystring& operator=(const Mystring& str) {
            Mystring tmp(str);
            swap(tmp);
            return *this;
        }

        // 重載賦值,採用copy and swap手法,新式寫法
        Mystring& operator=(Mystring& str) {
            swap(str);
            return *this;
        }

        int size() const {
            return (int)strlen(data_);
        }
        const char* c_str() const {
            return data_;
        }

        void swap(Mystring& str) {
            std::swap(data_, str.data_);
        }
    private:
        char* data_;
};

 

5. C++ 智能指針 代碼實現

c++智能指針的實現

智能指針類與普通指針同樣,但它藉由自動化內存管理保證了安全性,避免了諸如懸掛指針、內存泄露和分配失敗等問題。

智能指針有好幾種實現方式,STL和Boost庫裏都有實現,好比使用句柄類和引用計數方式。

咱們如今使用引用計數定義智能指針,智能指針類將一個計數器與類指向的對象相關聯。使用計數跟蹤該類有多少個對象共享同一指針。

使用計數爲0時,刪除對象。使用計數有時也稱爲引用計數(reference count)。

 

使用一個計數變量,並將其置一,每新增一個對象的引用,該變量會加一,移除一個引用則減一

即當對象做爲另外一對象的副本而建立時,複製構造函數複製指針並增長與之相應的使用計數的值

當對一個對象進行賦值時(=操做符),覆寫=操做符,這樣才能將一箇舊的智能指針覆值給另外一指針,舊的引用計數減一,新的智能指針的引用計數則加一

#include <memory>
#include <stdlib.h>

template<typename T>
class SmartPointer {
    public:
        SmartPointer<T>(T* ptr) {
            ref = ptr;
            ref_count = (unsigned*)malloc(sizeof(unsigned));
            *ref_count = 1;
        }

        SmartPointer<T>(SmartPointer<T>& sptr) {
            ref = sptr.ref;
            ref_count = sptr.ref_count;
            ++(*ref_count);
        }

        SmartPointer<T>& operator=(SmartPointer<T>& sptr) {
            if (this == &sptr) {
                return *this;
            }

            --(*ref_count);
            if (*ref_count == 0) {
                clear();
            }

            ref = sptr.ref;
            ref_count = sptr.ref_count;
            ++(*ref_count);
            return *this;
        }

        ~SmartPointer<T>() {
            --(*ref_count);
            if (*ref_count == 0) {
                clear();
            }
        }

        T* GetValue() {
            return ref;
        }

    private:
        void clear() {
            delete ref;
            free(ref_count);
            ref = NULL;
            ref_count = NULL;
        }

    private:
        T* ref;
        unsigned* ref_count;
};

int main() {
    int* ip1 = new int();
    *ip1 = 1111;
    int* ip2 = new int();
    *ip2 = 2222;

    SmartPointer<int> sp1(ip1);
    SmartPointer<int> sp2(ip2);
    SmartPointer<int> spa = sp1;
    sp2 = spa;
    return 0;
}

 

6. POD、迭代器萃取、模板偏特化

POD維基百科

POD(Plain Old Data):標量類型 或 傳統的C struct 類型,POD類型必然 擁有 默認的ctor、dtor、copy、assign

POD類類型就是指 class、struct、union,且不具備用戶定義的構造函數、析構函數、拷貝算子、賦值算子;不具備繼承關係,所以沒有基類;不具備虛函數,因此就沒有虛表;非靜態數據成員沒有私有或保護屬性的、沒有引用類型的、沒有非POD類類型的(即嵌套類都必須是POD)、沒有指針到成員類型的(由於這個類型內含了this指針)。

 

咱們能夠對 POD 型別採起最有效率的複製手法,而對 non-POD 型別採起最保險安全的做法

首先 利用迭代器萃取手法 萃取出迭代器 的 value type,而後判斷該型別是否爲 POD 型別

typedef typename __type_traits<T>::is_POD_type is_POD;

 

迭代器所指對象的型別,稱爲該迭代器的 value type

「模板參數推導機制」 只能針對於 函數參數類型,不能推導 函數的返回值類型

不是全部的迭代器都是 class type,原聲指針就不是!!!

STL(泛型思惟)絕對必須接受原生指針做爲一種迭代器,這個時候就須要針對特殊狀況(原生指針)作特化處理,即模板偏特化

 

模板偏特化:在泛化設計中提供一個特化版本(將泛化版本中的某些template參數賦予特殊的指定)

迭代器traits手法本質上就是 模板偏特化,實質代碼以下:

template <class T>
struct iterator_traits {
    typedef typename T::value_type value_type;
}

如今不論是class type 仍是 原生指針int* , 咱們均可以經過traits萃取出 迭代器的 value_type

 

7. C++ 單例模式 代碼實現

c++ singleton神文

個人singleton博文

 

8. C++ 實現不被繼承的類

個人博文

注:能夠直接使用C++11的final關鍵字

9. c++ 虛函數 多態

多態兩必要條件:(1) virtual函數 (2) 基類指針(引用)指向派生類對象

多態知識點:

(1) 任何含有 virtual函數的類及其派生類 均在類實例對象的首地址 安插一個指向虛函數表的指針(虛表指針,vptr)

爲何在 類對象的首地址(即this指針處) 存放 虛表指針呢? 

Base* pBase = new Derived;  // Base對象首地址、Derived對象首地址、this指針、虛表指針 均相同,很是方便的定位 虛表指針

(2) Derived類 會繼承 Base類 的虛表,當Derived類 重定義了 Base類的某個函數,這時 Derived對應的虛函數表

Base::foo() ==> Derived::foo()  // Derived::foo() 覆蓋從基類 繼承而來的 虛函數 Base::foo()

c++ this指針、虛函數

// c++中 全部non-static成員變量 和 virtual成員函數 都必須經過 this指針訪問
    class test {
        public:
            test(int value) : val(value) { }

            void foo1() {           // 正確:non-virtual函數地址編譯期肯定,傳入this指針,this指針爲空,可是這個函數沒有 訪問 this空指針
                fprintf(stdout, "hello world\n");
            }

            void foo2() {           // 錯誤:non-virtual函數地址編譯期肯定,傳入this指針,this指針爲空,可是這個函數 訪問了 this空指針(由於 this->val)
                fprintf(stdout, "%d\n", val);
            }

            virtual void foo3() {  // 錯誤:全部 virtual函數都須要經過 虛函數表肯定,虛函數表須要 this指針來肯定,所以 訪問了 this空指針
                fprintf(stdout, "hello world\n");
            }

            static  void foo4() {  // 正確:全部static函數都沒有this指針,因此不能訪問 non-static成員變量
                fprintf(stdout, "hello world\n");
            }
        private:
            int val;
    };

    test* pTest = NULL;  // this指針爲空
    pTest->foo1();
    pTest->foo2();
    pTest->foo3();
    pTest->foo4();

 

c/c++類型轉換

class Base {};
class Derived {};

Base* pBase = new Derived;         
Base* pBase = new Derived[10];
/*
1. 信息 = 比特位 + 解釋方式,類型轉換隻是改變了 解釋方式,數據未變
2. 數組和結構體(struct、class)訪問成員變量和成員函數 都是 首地址 + offset偏移
type[i] ==> type(首地址) + i * sizeof(type)

3. new Derived返回指向Derived類型的指針
4. pBase = pDerived 指針的類型轉換
5. 通常狀況下,sizeof(Base) 不等於 sizeof(Derived)

6. 爲何第一句正確呢? 
由於 這一句 只分配了一個對象,而且 vptr位於對象首地址,所以 Base的首地址 == Derived的首地址 == this指針,故正確找到 vptr

7. 爲何第二句通常狀況下錯誤呢?
一般狀況下,sizeof(Base) 不等於 sizeof(Derived), 所以 pBase[i] 不等於 pDerived[i],因爲類型轉換改變了解釋方式,致使找不到Derived的正確this指針位置,
所以vptr、析構函數就徹底不正確了

注:當 sizeof(Base) == sizeof(Derived) 時,能夠正確運行,但這是一個未定義行爲,因此禁止這樣的寫法
*/
 

 

10. STL空間配置器

#ifndef _JJALLOC_
#define _JJALLOC_

#include <new>      // for placement new
#include <cstddef>  // for ptrdiff_t, size_t
#include <cstdlib>  // for exit()
#include <climits>  // for UINT_MAX
#include <iostream> // for cerr

namespace JJ
{
    template<class T>
    inline T* _allocate(ptrdiff_t size, T*) {
        std::set_new_handler(0);
        T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
        if (tmp == 0) {
            std::cerr << "out of memory" << std::endl;
            exit(1);
        }
        return tmp;
    }

    template<class T>
    inline void _deallocate(T* buffer) {
        ::operator delete(buffer);
    }

    template<class T1, class T2>
    inline void _construct(T1* p, const T2& value) {
        new(p) T1(value);
    }
    template<class T>
    inline void _destroy(T* ptr) {
        ptr->~T();
    }

    // std::allocator的標準接口
    template<class T>
    class allocator {
        public:
            typedef T         value_type;
            typedef T*        pointer;
            typedef const T*  const_pointer;
            typedef T&        reference;
            typedef const T&  const_reference;
            typedef size_t    size_type;
            typedef ptrdiff_t difference_type;

            template<class U>
            struct rebind {
                typedef allocator<U> other;
            };

            pointer allocate(size_type n, const void* hint = 0) {
                return _allocate((difference_type)n, (pointer)0);
            }

            void deallocate(pointer p, size_type n) {
                _deallocate(p);
            }

            void construct(pointer p, const T& value) {
                _construct(p, value);
            }

            void destroy(pointer p) {
                _destroy(p);
            }

            pointer address(reference x) {
                return (pointer)&x;
            }

            const_pointer const_address(const_reference x) {
                return (const_pointer)&x;
            }

            size_type max_size() const {
                return size_type(UINT_MAX/sizeof(T));
            }
    };
}

#endif    // _JJALLOC_

使用配置器:

std::vector<int, JJ::allocator<int>> vec

通常而言,咱們習慣的c++內存配置操做和釋放操做:

class Foo {...};
Foo* pf = new Foo;  // 配置內存,而後構造對象
delete pf;          // 析構對象,而後釋放內存

new關鍵字包含兩階段操做:

(1) 調用 ::operator new 配置內存

(2) 調用 Foo::Foo()構造對象內容

 

delete關鍵字包含兩階段操做:

(1) 調用 Foo::~Foo() 將對象析構

(2) 調用 ::operator delete 釋放內存

 

10.1  vector數據結構

template<class T, class Alloc = alloc>
class vector {
    public:
        typedef T            value_type;
        typedef value_type*  iterator;
    protected:
        iterator start;               // 表示目前使用空間的頭
        iterator finish;              // 表示目前使用空間的尾
        iterator end_of_storage;      // 表示目前可用空間的尾
};
std::vector<int> vec;
sizeof(vec) = 12;  // 32位

 

11. 陳碩大大的C++博文學習 每篇都是經典:D

https://cloud.github.com/downloads/chenshuo/documents/CppPractice.pdf

 

11.1 C++ RAII封裝Mutex

Linux C++ 多線程編程

 

MutexLock 封裝臨界區(Critical secion),這是一個簡單的資源類,用 RAII 手法 [CCS:13]封裝互斥器的建立與銷燬。

臨界區在 Windows 上是 CRITICAL_SECTION,是可重入的;在 Linux 下是 pthread_mutex_t,默認是不可重入的。MutexLock 通常是別的 class 的數據成員。

MutexLockGuard 封裝臨界區的進入和退出,即加鎖和解鎖。MutexLockGuard 通常是個棧上對象,它的做用域恰好等於臨界區域。

#include <pthread.h>

#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
    TypeName(const TypeName&) \
    TypeName& operator=(const TypeName&)

class MutexLock {
    public:
        MutexLock() {
            pthread_mutex_init(&mutex_, NULL);
        }

        ~MutexLock() {
            pthread_mutex_destroy(&mutex_);
        }

        void lock() {
            pthread_mutex_lock(&mutex_);
        }

        void unlock() {
            pthread_mutex_unlock(&mutex_);
        }

        pthread_mutex_t* getPthreadMutex() {
            return &mutex_;
        }

    private:
        pthread_mutex_t mutex_;
        DISALLOW_COPY_AND_ASSIGN(MutexLock);
};

class MutexLockGuard {
    public:
        explicit MutexLockGuard(MutexLock& mutex) : mutex_(mutex) {
            mutex_.lock();
        }

        ~MutexLockGuard() {
            mutex_.unlock();
        }
    private:
        MutexLock& mutex_;
        DISALLOW_COPY_AND_ASSIGN(MutexLockGuard);
};

#define MutexLockGuard(x) static_assert(false, "missing mutex guard var name")

 

11.2 std::map學習

std::map學習

rb_tree 的迭代器的每次遞增或遞減不能保證是常數時間,最壞狀況下多是對數時間(即與樹的深度成正比)

rb_tree_node* rb_tree_increment(rb_tree_node* node) {
    if (node == NULL) {
        return NULL;
    }
    if (node->right != NULL) {
        node = node->right;
        while (node->left != NULL) {  // 右子樹最左下節點
            node = node->left;
        }
    } else {
        rb_tree_node* parent = node->parent;
        while (node == parent->right) {  // 一直上溯
            node = parent;
            parent = node->parent;
        }
        if (node->right != parent) { 
            node = parent;
        }
    }
    return node;
}

那麼用 begin()/end() 迭代遍歷一棵樹仍是不是 O(N)?

換言之,迭代器的遞增或遞減是不是分攤後的(amortized)常數時間?

利用數學概括法能夠獲知:

對於深度爲 n 的滿二叉樹,有 2^n - 1 個元素,從 begin() 到 end() 須要走 f(n) 步。那麼 f(n) = 2*f(n-1) + n。

而後,用遞推關係求出 f(n) = sum(i * 2 ^ (n-i)) = 2^(n+1) - n - 2(這個等式能夠用概括法證實)。

即對於深度爲 n 的滿二叉樹,從頭至尾遍歷的步數小於 2^(n+1) - 2,而元素個數是 2^n - 1,兩者一除,獲得平均每一個元素須要 2 步。

所以能夠說 rb_tree 的迭代器的遞增遞減是分攤後的常數時間。

彷佛還有更簡單的證實方法,在從頭至尾遍歷的過程當中,每條邊(edge)最多來回各走一遍,一棵樹有 N 個節點,那麼有 N-1 條邊,最多走 2*(N-1)+1 步,也就是說平均每一個節點須要 2 步

相關文章
相關標籤/搜索