面向對象特徵:
抽象,多態,繼承,封裝
1.條件編譯
#if 0 #if 1 #ifdef #ifndef
... ... ... ...
#endif #else #endif #else
... ...
#endif #endif
***********/
2.輸入輸出流,名字空間
using namespace std;
using std::cin;
std::cin>>i;
3.訪問和內部作用域變量同名的全局變量,要用全局作用域限定 ::
#include <iostream>
using namespace std;
double a = 128;
int main (){
double a = 256;
cout << "Global a: " <<::a << endl; //::是全局作用域限定
return 0;
}
4. 通過 try-catch處理異常情況 :正常代碼放在try塊,catch中捕獲try塊拋出的異常
try { //放置正常代碼,但可能出現問題
if (a > 100) throw 100; //throw 拋出異常(也稱爲拋棄異常),100爲異常對象,出現異常則下面的語句不會執行
if (a < 10) throw 10; //throw後下面語句便不執行了
throw "hello";
}
catch (int result) { //捕獲異常
........
}
catch (char * s) { //捕獲字符串
........
}
5. 引用(別名),引用主要用於函數形參,C語言函數形參都是值參數
int a = 3, &r = a; //r就是a
void swap(int &x, int &y) { //利用別名,形參x就是實參a
int t = x;
x = y;
y = t;
}
6.默認形參
void print(char ch, int n = 1) { //n爲默認形參,默認形參必須在右邊。n若不傳遞參數就爲1,傳遞則是傳遞的數
for (int i = 0; i < n; i++)
cout << ch;
}
7 函數重載 :允許有同名函數存在,只要形參不同,注意:不能根據返回類型區分同名函數
函數重載,函數重寫,同名隱藏
https://blog.csdn.net/inter_peng/article/details/53940179
https://blog.csdn.net/u014725884/article/details/47213651
8函數模板,類模板
template<typename T> //函數模板, typename 也可寫成class
T add(T x, T y) {
return x + y;
}
int main() {
// cout << add<int>(5, 3) << endl;
cout << add(5.3, 7.8) << endl;
cout << add((double)5, 7.8) << endl; //歧義性 ,//函數模板是不允許隱式類型轉換的,調用時類型必須嚴格匹配
}
//類模板
template<class T>
class Array {
T size;
T *data;
public:
Array(int s) { //構造函數
size = s;
data = new T[s];
}
virtual ~Array() {
delete[] data;
}
};
9.string,vector,迭代器iterator
string類具體操作: https://www.cnblogs.com/X-Do-Better/p/8628492.html
//vector
#include <iostream>
#include <vector> // vector是一個類模板
using namespace std;
int main() {
vector<int> v = { 7, 5, 16, 8 };
vector<int > V2;
v.push_back(25); //push_back(),最後添加一個元素 //成員函數size()、下標運算符[]
int i = v.size();
v.pop_back(); //刪除最後元素
v.resize(2);
}
string::const_iterator cii; //const迭代器,不能修改, iterator能修改 string::iterator cii;
10.new/delete
int *p = new int; delete p; //防止內存泄漏
int *p = new int[n]; delete[] p;
11. this指針: 指向類對象自己,成員函數實際上隱含一個this指針
#include <iostream>
#include <string>
using namespace std;
struct student {
string name; double score;
void print() {
cout << this->name << " " << this->score << endl;
}
};
int main() {
student stu;
stu.name = "Li Ping"; stu.score = 78.5;
stu.print(); // print(&stu);
}
12.訪問控制、類接口
struct和class區別: struct裏的成員默認是public(公開的) class裏的成員默認是private(私有的)
接口:public的公開成員(一般是成員函數)稱爲這個類的對外接口,外部函數只能通過這些接口訪問類對象,
private等非public的包含內部內部細節,不對外公開,從而可以封裝保護類對象!
13.構造函數 ,拷貝構造函數,析構函數
構造函數是和類名同名且沒有返回類型的函數,在定義對象時會自動被調用,而不需要在單獨調用專門的初始化函數如init
構造函數用於初始化類對象成員,包括申請一些資源,如分配內存、打開某文件等
如果不寫構造函數,編譯器會默認生成一個String(){ }
析構函數是在類對象銷燬時被自動調用,用於釋放該
爲了防止內存泄漏的發生,最好將基類的析構函數寫成virtual虛析構函數。
默認的“拷貝構造函數”是“硬拷貝”或“逐成員拷貝”,指針指向同一塊內存區域,當多次釋放同一塊內存就出錯了!
#include <iostream>
using namespace std;
class String {
char *data; //C風格的字符串
int n; //字符個數
public:
~String() {
..........
if(data)
delete[] data;
}
String(const String &s) { // 重定義拷貝構造函數 硬拷貝{data=s.data; s=s.n},
//執行默認拷貝構造函數時s3和str2指向同一個地址,改變s3那麼str2也改變
cout << "拷貝構造函數!\n";
//當需要申請資源時,後需要將資源釋放掉,最好重新定義拷貝構造函數,防止析構時多次釋放
data = new char[s.n + 1];
........
}
String(const char *s=0) { //構造函數 ,:函數名和類名相同且無返回類型的成員函數
. . . . .
}
............
};
ostream& operator<<(ostream &o, String s) {
for (int i = 0; i < s.size(); i++)
cout << s[i];
return o;
}
void f() {
String str,str2("hello world");
str2[1] = 'E';
// cout << str2 << endl;
String s3 = str2; //拷貝構造函數 執行默認拷貝構造函數時s3和str2指向同一個地址,改變s3那麼str2也改變
cout << str2 << endl; //str2傳遞給s調用ostream& operator<<(ostream &o, String s) 時 會默認調用拷貝構造函數
//調用時s和str2指向同一個地址,結束後調用析構函數s銷燬,
//當f函數結束時,str2會銷燬,造成了同一個內存多次銷燬 所以要自定義拷貝構造函數
}
14.運算符重載:針對用戶定義類型重新定義運算符函數
運算符與普通函數在調用時的不同之處是:對於普通函數,參數出現在圓括號內;而對於運算符,參數出現在其左、右側。
Complex a, b, c;
…
c = Add(a, b); // 用普通函數
c = a + b; // 用運算符+
class Point{
double x, y;
public:
double operator[](int i) const{ //const函數 。無法給通過下標賦值,爲了和下面不一樣。加了const,返回x複製品
if (i == 0) return x;
else if (i == 1) return y;
else throw "下標非法!"; //拋出異常
}
double& operator[](int i) { //下標可修改,返回x本身
if (i == 0) return x;
else if (i == 1) return y;
else throw "下標非法!"; //拋出異常
}
Point(double x_,double y_) //構造函數
x = x_; y = y_;
}
Point operator+(const Point q) { //作爲類的內部函數第一個參數就是調用操作本身。
return Point(this->x+q[0],this->y + q[1]);
}
friend ostream & operator<<(ostream &o, Point p); //友元函數
friend istream & operator>>(istream &i, Point &p);
};
ostream & operator<<(ostream &o, Point p) {
o <<p.x << " " << p.y<< endl;
return o;
}
istream & operator>>(istream &i, Point &p) {
i >> p.x >> p.y;
return i;
}
/* Point operator+(const Point p,const Point q) { return Point(p[0] + q[0], p[1] + q[1]); } */
int main() {
Point p(3.5, 4.8),q(2.0,3.0); //自動調用構造函數
cout << p;
cout << p[0] << "-" << p[1] << endl; //p.operator[](0)
p[0] = 3.45; p[1] = 5.67;
cout << p<<q;
Point s = p + q; //p.operator+(q) 內部調用vs operator+(p,q)外部調用
cout << s;
}
友元函數:爲了使其他類的成員函數直接訪問該類的私有變量。即:允許外面的類或函數去訪問類的私有變量和保護變量,從而使兩個類共享同一函數。
實際上具體大概有下面兩種情況需要使用友元函數:(1)運算符重載的某些場合需要使用友元。(2)兩個類要共享數據的時候
15. Inheritance繼承(Derivation派生): 一個派生類(derived class)
多重繼承: 從一個類派生出多個不同的類
多重派生: 從多個不同的類派生出一個類來
. 從1個或多個父類(parent class) / 基類(base class)繼承,即繼承父類的屬性和行爲,但也有自己的特有屬性和行爲.
#include <iostream>
#include <string>
using namespace std;
class Employee{
string name;
public:
Employee(string n);
void print();
};
class Manager: public Employee{ //子類
int level;
public:
Manager(string n, int l = 1);
//void print();
};
Employee::Employee(string n) :name(n)//初始化成員列表
{
//name = n;
}
void Employee::print() {
cout << name << endl;
}
Manager::Manager(string n, int l) :Employee(n), level(l) {
}
//通過調用基類的構造函數對基類成員初始化
//派生類的構造函數只能描述它自己的成員和其直接基類的初始式,不能去初始化基類的成員。
Manager::Manager(string n, int l) : name(n), level(l) { //錯的
}
int main() {
Manager m("Zhang",2);
Employee e("Li");
m.print();
e.print();
}
//子類和父類有同名函數
class Manager : public Employee
{
int level;
public:
Manager(string n, int l = 1);
void print();
};
Manager::Manager(string n, int l) :Employee(n), level(l) {
}
void Manager::print() {
cout << level << "\t";
Employee::print();
}
int main() {
Manager m("Zhang");
Employee e("Li");
m.print(); //打印的是manage的print
e.print();
}
派生類的指針可以自動轉化爲基類指針
int main() {
Employee *p;
Manager m("Zhang", 1);
p = &m;
p->print(); //此時打印的是employee中成員不是manage的
}
16.虛函數Virtual Functions,多態
上面代碼可以將print聲明爲虛函數Virtual Functions :用virtual關鍵字,根據實際指向的類型輸出,多態性,否則指針輸出的是基類
靜態成員函數不能是虛函數。
1.基類中必須包含虛函數(在成員函數之前加上virutal 關鍵字),並且在派生類中對基類中的虛函數進行重寫
2.在派生類中重寫的函數在基類中必須是虛函數(派生類中可以加virtual 關鍵字,不加的時候仍然會保持虛函數特性,但是建議加上)
3.派生類中虛函數必須與基類中虛函數的原型保持一致(返回值,函數名,參數列表和返回值都要相同,如果不同則會構成同名隱藏)
例外:析構函數(函數名不同)
例外:協變:基類的虛函數返回基類的引用或指針,派生類的虛函數返回派生類的引用或指針
4.基類與派生類中函數的訪問權限可以不同,不過基類中虛函數必須是public權限
5.通過基類的指針或者引用來調用虛函數
6.靜態成員函數不能定義爲虛函數
多態:對於通過基類指針調用基類和派生類中都有的同名、同參數表的虛函數的語句,編譯時並不確定要執行的是基類還是派生類的虛函數;而當程序運行到該語句時,如果基類指針指向的是一個基類對象,則基類的虛函數被調用,如果基類指針指向的是一個派生類對象,則派生類的虛函數被調用。這種機制就叫作“多態。
17.純虛函數和抽象類
主要作用:相當於上述的基類,抽象類專門用來存放多態的接口,它是所有要實現多態的類的基類。
一 般的抽象類只是爲了構成多態,並且其中的純虛函數只是爲了在其它的類種進行重寫。
如果一個類繼承於一個虛函數,那麼這個函數種必須要重寫抽象類中的純虛函數。
寫法:只有public的成員函數,並且只是聲明,而且聲後面加上 = 0,此函數叫做純虛函數。
構成:以純虛函數作爲成員函數的類叫做抽象類,抽象類不能直接實例化,但是抽象類的指針是可以定義的。
18.內聯函數:inline
內聯是以代碼膨脹(複製)爲代價,僅僅省去了函數調用的開銷,從而提高函數的執行效率。如果執行函數體內代碼的時間,相比於函數調用的開銷較大,那麼效率的收穫會很少。另一方面,每一處內聯函數的調用都要複製代碼,將使程序的總代碼量增大,消耗更多的內存空間。
void Foo(int x, int y);
inline void Foo(int x, int y) // inline與函數定義體放在一起
{
…
}
對於任何內聯函數,編譯器在符號表裏放入函數的聲明(包括名字、參數類型、返回值類型)。如果編譯器沒有發現內聯函數存在錯誤,那麼該函數的代碼也被放入符號表裏。在調用一個內聯函數時,編譯器首先檢查調用是否正確(**進行類型安全檢查,或者進行自動類型轉換,**當然對所有的函數都一樣)。如果正確,內聯函數的代碼就會直接替換函數調用,於是省去了函數調用的開銷。這個過程與預處理有顯著的不同,因爲預處理器不能進行類型安全檢查,或者進行自動類型轉換。假如內聯函數是成員函數,對象的地址(this)會被放在合適的地方,這也是預處理器辦不到的。
C++ 語言的函數內聯機制既具備宏代碼的效率,又增加了安全性,而且可以自由操作類的數據成員。所以在C++ 程序中,應該用內聯函數取代所有宏代碼,“斷言assert”恐怕是唯一的例外。assert 是僅在Debug版本起作用的宏,它用於檢查“不應該”發生的情況。爲了不在程序的Debug版本和Release版本引起差別,assert 不應該產生任何副作用。如果assert是函數,由於函數調用會引起內存、代碼的變動,那麼將導致Debug版本與Release版本存在差異。所以assert 不是函數,而是宏。