c++知識點累積

輸入輸出

輸入

  • cin >>

跳過空白字符(空格、換行符、製表符)。

#include <iostream>
#include <string>

int main()
{
	using std::cout;
	using std::cin;
	using std::endl;
	using std::string;

	string tmp("");
	cout << "input:";
	cin >> tmp;
	cout << "output:" << tmp <<endl;

	return 0;
}

輸出結果:

 

  • cin.get()

該函數的聲明有5種,如下。

int_type get();
_Myt& get(_Elem *_Str, streamsize _Count);
_Myt& get(_Elem *_Str, streamsize _Count, _Elem _Delim);
_Myt& get(_Elem& _Ch);
_Myt& get(_Mysb& _Strbuf);
_Myt& get(_Mysb& _Strbuf, _Elem _Delim);

輸入流中取字符或者字符串。不跳過空白字符。如果是字符串,那麼 最多隻能輸入_Count-1個字符,然後最後補上一個字符串的結束符。_Delim是結束符。get函數停止接收字符的條件有2個,遇到文件結尾EOF或者結束符,或者已經取到_Count-1個字符。哪個條件新到達,取字符結束。

  • cin.getline()

友元

類的機制實現了數據的隱藏與封裝。類的數據成員一般定義爲私有成員,成員函數一般定義爲公有成員。提供類與外界間的通信接口。但有時需要定義一些函數,這些函數不是類的一部分,但又需頻繁訪問類的數據成員,這時可以將這些函數定義爲該函數的友元函數。除了友元函數外,還有友元類,兩者統稱爲友元。友元的作用是提高了程序的運行效率(即減少了類型檢查和安全性檢查等都需要時間開銷),但它也破壞了類的封裝性和隱藏性,使得非成員函數可以訪問類的私有成員。

  • 友元函數

友元函數 :可以直接訪問類的私有成員的非成員函數。它是定義在類外的普通函數,它不屬於任何類,但需要在類的定義中加以聲明,聲明時只需在友元的名稱前加上關鍵字friend,其格式如下:

friend 函數返回類型 函數名(形式參數);

注意:1.友元函數的聲明可以放在類的私有部分,也可以放在公有部分,沒有區別的,都說明是該類的一個友元函數
2. 一個函數可以是多個類的友元函數,只需要在各個類中分別聲明
3. 友元函數的調用與一般函數的調用方式和原理一致

  • 友元類

友元類 :友元類的所有成員函數都是另一個類的友元函數,都可以訪問另一個類中的隱藏信息(包括私有成員和保護成員)。當希望一個類可以存取另一個類的私有成員時,可以將該類聲明爲另一類的友元類。定義友元類的語句格式如下:

friend class 類名;  //friend和class是關鍵字,類名必須是程序中的一個已定義過的類。

例:

class B;
class A
{
    …
    public:
        friend class B;
    …
};

class C : public A{}; // C直接繼承A,但是無法實例化,友元關係不能傳遞
class B : public A{}; // B直接繼承A,可以實例化
class D : public B{}; // D直接繼承B,可以實列化,相當於D間接繼承A

類B的所有成員函數都是類A的友元函數,能存取類A的私有成員和保護成員。使用友元類時注意:1.友元關係不能被繼承 
2. 元關係是單向的,不具有交換性。若類B是類A的友元,類A不一定是類B的友元,要看在類中是否有相應的聲明
3. 友元關係不具有傳遞性。若類B是類A的友元,類C是B的友元,類C不一定是類A的友元,同樣要看類中是否有相應的申明

友元類特點:(上例)將導致A無法被除B以外的其它任何類直接繼承以後實例化。可事實是,下面這段代碼可以正常編譯與運行。

#include <iostream>
using namespace std;
class A
{
public:
	A(){_a = 100; _b = 200;}
	A(int x, int y) : _a(x), _b(y) {}

	void print_pub_a()
	{
		cout << "mem1 =" << _a;
		cout << "mem2_=" << _b;
		cout << endl;
	}
private:
	int _a, _b;
	void print_priv_a()
	{
		cout << "mem1 =" << _a;
		cout << "mem2_=" << _b;
		cout << endl;
	}

	friend class B;
};

class B : public A
{
public:
	B() : _c(9988) {}

	void print_pub_b()
	{
		cout << "class B print function." << endl;
		a1.print_priv_a();
	}

private:
	int _c;
	A a1;
};

class C : public A
{
public:
	void print_pub_c()
	{
		cout << "public print function in CLASS C." << endl;
	}
};

int main(void)
{
	C c1;
	c1.print_pub_a();
}

把上面的代碼稍作改動(把Class A的構造函數全部放入private域) ,如下,編譯失敗。

#include <iostream>
using namespace std;
class A
{
public:
	void print_pub_a()
	{
		cout << "mem1 =" << _a;
		cout << "mem2_=" << _b;
		cout << endl;
	}
private:
	A(){_a = 100; _b = 200;}
	A(int x, int y) : _a(x), _b(y) {}

	int _a, _b;
	void print_priv_a()
	{
		cout << "mem1 =" << _a;
		cout << "mem2_=" << _b;
		cout << endl;
	}

	friend class B;
};

class B : public A
{
public:
	B() : _c(9988) {}

	void print_pub_b()
	{
		cout << "class B print function." << endl;
		a1.print_priv_a();
	}

private:
	int _c;
	A a1;
};

class C : public A
{
public:
	void print_pub_c()
	{
		cout << "public print function in CLASS C." << endl;
	}
};

int main(void)
{
	C c1;
	c1.print_pub_a();
}

再對上面的代碼稍作改動(main函數中代碼全部註釋掉) ,編譯成功。

#include <iostream>
using namespace std;
class A
{
public:
	void print_pub_a()
	{
		cout << "mem1 =" << _a;
		cout << "mem2_=" << _b;
		cout << endl;
	}
private:
	A(){_a = 100; _b = 200;}
	A(int x, int y) : _a(x), _b(y) {}

	int _a, _b;
	void print_priv_a()
	{
		cout << "mem1 =" << _a;
		cout << "mem2_=" << _b;
		cout << endl;
	}

	friend class B;
};

class B : public A
{
public:
	B() : _c(9988) {}

	void print_pub_b()
	{
		cout << "class B print function." << endl;
		a1.print_priv_a();
	}

private:
	int _c;
	A a1;
};

class C : public A
{
public:
	void print_pub_c()
	{
		cout << "public print function in CLASS C." << endl;
	}
};

int main(void)
{
	//C c1;
	//c1.print_pub_a();
}

從上面的例子可以知道,class C的實例化必須要調用Class的構造函數,如果構造函數在private域,那麼自然不能實例化,但是可以繼承。

class C是直接從A繼承,如果 讓class c從B繼承又會怎麼樣呢?看下面的代碼,C從B處間接繼承A,因爲B是A的友元函數,所以即使A的構造函數在private域,那麼C也是可以調用到,因此可實例化。

#include <iostream>
using namespace std;
class A
{
public:
	void print_pub_a()
	{
		cout << "mem1 =" << _a;
		cout << "mem2_=" << _b;
		cout << endl;
	}
private:
	A(){_a = 100; _b = 200;}
	A(int x, int y) : _a(x), _b(y) {}

	int _a, _b;
	void print_priv_a()
	{
		cout << "mem1 =" << _a;
		cout << "mem2_=" << _b;
		cout << endl;
	}

	friend class B;
};

class B : public A
{
public:
	B() : _c(9988) {}

	void print_pub_b()
	{
		cout << "class B print function." << endl;
		a1.print_priv_a();
	}

private:
	int _c;
	A a1;
};

class C : public B
{
public:
	void print_pub_c()
	{
		cout << "public print function in CLASS C." << endl;
	}
};

int main(void)
{
	C c1;
	c1.print_pub_a();
}

在上面的代碼中,如果不讓C通過B的友元間接繼承A,那麼可以像下面這樣實現。A的構造函數放在private域,B是A的友元類,同時B從A共有繼承(使用virtual)。那麼沒有其他的類可以直接或者間接的從A處繼承。下面編譯會失敗。

#include <iostream>
using namespace std;
class A
{
public:
	void print_pub_a()
	{
		cout << "mem1 =" << _a;
		cout << "mem2_=" << _b;
		cout << endl;
	}
private:
	A(){_a = 100; _b = 200;}
	A(int x, int y) : _a(x), _b(y) {}

	int _a, _b;
	void print_priv_a()
	{
		cout << "mem1 =" << _a;
		cout << "mem2_=" << _b;
		cout << endl;
	}

	friend class B;
};

class B : virtual public A
{
public:
	B() : _c(9988) {}

	void print_pub_b()
	{
		cout << "class B print function." << endl;
		a1.print_priv_a();
	}

private:
	int _c;
	A a1;
};

class C : public B
{
public:
	void print_pub_c()
	{
		cout << "public print function in CLASS C." << endl;
	}
};

int main(void)
{
	C c1;
	c1.print_pub_a();
}

在上面的代碼中,實例化c1時,C直接調用A的構造函數,而不是間接調用B的構造,所以會實例化失敗。

1. 將A的構造函數放入private區域。2. 聲明子類B爲A的友元。3. 定義A爲virtual public繼承B。    

C++關鍵字

  • final 指定符 (C++11 起)

在C++11中關鍵字final,用來指定派生類不能覆寫虛函數,或類不能被繼承。應用到成員函數時,標識符final 在類定義中的成員函數聲明或成員函數定義的語法中,立即出現於聲明器後。應用到類時,標識符 final 出現在類定義的起始,立即在類名後。例如下面leveldb代碼中,類PosixLogger不能被其他的類繼承了

namespace leveldb {

class PosixLogger final : public Logger {
 public:
  // Creates a logger that writes to the given file.
  //
  // The PosixLogger instance takes ownership of the file handle.
  explicit PosixLogger(std::FILE* fp) : fp_(fp) {
    assert(fp != nullptr);
  }

  ~PosixLogger() override {
    std::fclose(fp_);
  }
  
.......
.......
.......

 private:
  std::FILE* const fp_;
};

}  // namespace leveldb

#endif  // STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_
  • override 指定符(C++11 起)

在C++11中關鍵字override,它用來指定基類中的虛成員函數需要在子類中顯式地重寫,如果沒有重寫則編譯器報錯。例如在上面代碼中就有關鍵字override的使用

  • default 指定符

在C++11中關鍵字default,它用來顯式默認化的函數定義,令編譯器爲類生成特殊成員函數或比較運算符 (C++20 起)的顯式指令。其中特殊成員函數有:默認構造函數、複製構造函數、移動構造函數 (C++11 起)、複製賦值運算符、移動賦值運算符 (C++11 起)、析構函數。即,如果某個特殊成員函數在聲明時使用了default,那麼編譯器會爲該函數自動生成函數體。例如leveldb代碼

namespace leveldb 
{

  class LEVELDB_EXPORT Cache;

  // Create a new cache with a fixed size capacity.  This implementation
  // of Cache uses a least-recently-used eviction policy.
  LEVELDB_EXPORT Cache* NewLRUCache(size_t capacity);

  class LEVELDB_EXPORT Cache {
  public:
    Cache() = default;

    Cache(const Cache&) = delete;
    Cache& operator=(const Cache&) = delete;

    // Destroys all existing entries by calling the "deleter"
    // function that was passed to the constructor.
    virtual ~Cache();

    // Opaque handle to an entry stored in the cache.
    struct Handle { };

  ......
  ......

}//class Cache

}//namespace leveldb

關於cache的定義時,使用了該關鍵字。

  • delete 指定符

在C++11中關鍵字delete,它的定義與default是一樣的,但是功能不一樣。delete指示禁止使用特殊成員函數。在上面的代碼中使用到了關鍵字delete

  • static

在類中聲明static變量或者函數時,初始化時使用作用域運算符來標明它所屬類,因此,靜態數據成員是類的成員,而不是對象的成員,這樣就出現以下作用:
(1)類的靜態成員函數是屬於整個類而非類的對象,所以它沒有this指針,這就導致它僅能訪問類的靜態數據和靜態成員函數。     
(2)不能將靜態成員函數定義爲虛函數。      
(3)由於靜態成員聲明於類中,操作於其外,所以對其取地址操作,就多少有些特殊,變量地址是指向其數據類型的指針,函數地址類型是一個“nonmember函數指針”。
(4)由於靜態成員函數沒有this指針,所以就差不多等同於nonmember函數,結果就產生了一個意想不到的好處:成爲一個callback函數,使得我們得以將C++和C-based X W indow系統結合,同時也成功的應用於線程函數身上。(這條沒遇見過)
(5)static並沒有增加程序的時空開銷,相反她還縮短了子類對父類靜態成員的訪問時間,節省了子類的內存空間。      
(6)靜態數據成員在<定義或說明>時前面加關鍵字static。
(7)靜態數據成員是靜態存儲的,所以必須對它進行初始化。(程序員手動初始化,否則編譯時一般不會報錯,但是在Link時會報錯誤) 
(8)靜態成員初始化與一般數據成員初始化不同:
初始化在類體外進行,而前面不加static,以免與一般靜態變量或對象相混淆;
初始化時不加該成員的訪問權限控制符private,public等;        
初始化時使用作用域運算符來標明它所屬類;
所以我們得出靜態數據成員初始化的格式:<數據類型><類名>::<靜態數據成員名>=<值>
(9)爲了防止父類的影響,可以在子類定義一個與父類相同的靜態變量,以屏蔽父類的影響。這裏有一點需要注意:我們說靜態成員爲父類和子類共享,但我們有重複定義了靜態成員,這會不會引起錯誤呢?不會,編譯器採用了一種絕妙的手法:name-mangling 用以生成唯一的標誌。

#include <iostream>
using namespace std;

class UnitTest
{
public:
	UnitTest* GetInstance(void)
	{
		static UnitTest instance;
		return &instance;
	}
};
int main(void)
{
	UnitTest u;

	cout << u.GetInstance() << endl;
	cout << u.GetInstance() << endl;
}

上面 ,在函數GetInstance(void)中,不論調用多少次,instance只定義一次(第一次被調用時),後面每一次調用時得到相同的instance。

  • const

const修飾成員函數,不能修改任何的成員變量(mutable修飾的變量除外)
const成員函數不能調用非const成員函數,因爲非const成員函數可以會修改成員變量

//google test source code
const TestInfo* current_test_info() const GTEST_LOCK_EXCLUDED_(mutex_);
  • explicit

explicit可以抑制內置類型隱式轉換,所以在類的構造函數中,最好儘可能多用explicit關鍵字,防止不必要的隱式轉換.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章