做者:李春港
出處:https://www.cnblogs.com/lcgbk/p/14643010.htmlhtml
這篇文章是對C++的知識點作了一些簡單的總結,基本包含了全部的C++基礎知識點。如下提到的知識點並不是深刻講解,只是大概講解了各個知識點的基本使用。如須要深刻了解,能夠針對某個知識點去深刻學習。ios
cpp, .h, cc, cxx, hppc++
例子:用c++寫一個簡單計算器express
#include <iostream> int main(void) { int a=0, b=0; char c=0; std::cout<<"please input type: a+b" <<std::endl; std::cin>>a>>c>>b; switch(c) { case '+': std::cout<<a<<c<<b<<"="<<a+b<<std::endl; break; case '-': std::cout<<a<<c<<b<<"="<<a-b<<std::endl; break; case '*': std::cout<<a<<c<<b<<"="<<a*b<<std::endl; break; case '/': std::cout<<a<<c<<b<<"="<<a/b<<std::endl; break; } return 0; }
extern "C" { #include <stdlib.h> #include <string.h> }
靜態內存分配(全局變量, 局部變量), 動態內存分配(在 c 中用 malloc 分配的堆空間 free 來釋放)c++中用 new 分配堆空間 delete 釋放。ubuntu
char *name =(char*) malloc(100); free(name);
爲了確保程序中的全局實體的名字不會與某些庫中聲明的全局實體名衝突,引入了命名空間。windows
除main函數外全部函數, 變量, 類型。數組
namespace 空間名{ 函數,變量, 類型 } 例子: namespace class01 { std::string name="jack"; int age=19; int number = 123; }
一、 直接經過空間名::成員名 --標準使用--提倡使用的方法 class01::name -- ::所屬符號 二、using指示符指引 using namespace calss01; // 把class01空間了裏面的內容暴露在當前位置,當文件有變量與命名空間的成員同樣時,則後期使用該成員或變量時,程序運行時會報錯;但能編譯經過 三、 using聲明 using class01::number; // 當文件也有number變量時,則編譯的時候就報錯,至關於定義了兩次該變量。
所屬符號::
安全
namespace AAA{ namespace BBB { int number=0; } }
使用:數據結構
至關於全局變量直接使用(只能在本文中使用)
static。
定義:app
namespace { int data; }
匿名空間與static的異同:
static 沒法修飾自定義類型;static 產生當前 符號只在當前源文件有效,是由於它修改了符號的Bind屬性,使之變爲局部的;而匿名空間雖然能夠產生相同效果,可是符號仍是具備外部連接屬性。匿名命名空間內的變量與函數,只在當前源文件內有效;不一樣源文件的匿名命名空間,能夠存在同名符合。static須要在每一個變量加上
引用:就是某一變量(目標)的一個別名,對引用的操做與對變量直接操做徹底同樣。
引用的聲明方法:類型標識符 &引用名=目標變量名;(別名)
int a = 10;
int &ra = a; (ra 就是 a 的引用 ,也稱 a 的別名)
1.引用做爲參數
引用的一個重要做用就是做爲函數的參數。之前的 C 語言中函數參數傳遞是值傳遞,若是有大塊數據做爲參數傳遞的時候,採用的方案每每是指針,由於這樣能夠避免將整塊數據所有壓棧,能夠提升程序的效率。可是如今(C++中)又增長了一種一樣有效率的選擇
2.常引用
常引用聲明方式:const 類型標識符 &引用名 = 目標變量名;
用這種方式聲明的引用,不能經過引用對目標變量的值進行修改,從而使引用的目標成爲 const,達到了引用的安全性。
3.引用做爲函數返回值
要以引用返回函數值,則函數定義時要按如下格式:
類型標識符 &函數名 (形參列表及類型說明){ 函數體 }
特色:
函數重載只能在同一個類中
int open();
int open(const char* filename);
int open(const char *filename , int flag);
c++中編譯程序的是檢測函數,經過函數名和參數列表
若是在一個文件中出現同名的函數但參數列表不一樣,那麼這些函數屬於重載
函數重載的依據:
函數名相同,
參數列表不一樣,(個數, 類型) add(int, doube) add(double, int)
若是參數是指針, 或引用,那麼const修飾也能夠做爲重載依據
函數的返回不能做爲函數重載的依據
參數是否爲默認參數不能做爲函數重載的依據
int open(const char *filename, int flag=10)
int open(const char *filename="c++", int flag=10)
int open(const char *filename="c++", int flag) 錯誤
注意: 若給某一參數設置了默認值,那麼在參數表中其後(也就是右邊)全部的參數都必須也設置默認值
(1)定義:
class 類名{ 類的特徵(屬性) 成員變量 類的行爲(功能) 成員方法, 函數 }; 注意:當類裏面的成員參數函數有默認值時,若須要在外部定義該函數時,不能寫默認值,默認值只能在類裏面聲明的時候寫默認值。 例子: class Tdate { public: void Set(int m, int d, int y ) { month = m ; day = d ; year = y ; } private: int month; int day; int year; };
struct和class在C++都能定義類,其區別:
(2)類成員權限控制
(3)類內/外訪問
定義與成員訪問:
class Tdate { public: int num; void set(int m, int d, int y ) { month = m ; day = d ; year = y ; } private: int month; int day; int year; }; //定義對象 Tdate A; Tdate *B = new Tdate( ); //對象成員訪問 A.set(1,1,1); A.num = 2; B->set(1,1,1); B->num = 2;
構造函數是成員函數,函數名與類名相同,函數沒有返回值, 函數不須要用戶調用,在建立對象的時候自動調用。
(1)若是建立一個類你沒有寫任何構造函數,則系統會自動生成默認的無參構造函數,函數爲空,什麼都不作。
(2)只要你寫了一個下面的某一種構造函數,系統就不會再自動生成這樣一個默認的構造函數,若是但願有一個這樣的無參構造函數,則須要本身顯示地寫出來
(3)參數列表初始化:只有構造函數纔有參數列表初始化。若要在類內聲明,類外定義構造函數,且使用參數列表初始化參數時,則在類內聲明的時候不容許參數列表初始化,只能類外定義的時候進行參數列表初始化
(4)函數默認參數:不管是成員函數仍是構造函數,若須要類內聲明和類外定義的時候,默認參數值在聲明或者定義的時候均可賦值,但聲明和定義的參數不能有默認值
class Complex { public: Complex()//構造函數定義 { m_real = 0.0; m_imag = 0.0; } Complex(int a,int b)//重載構造函數定義 { m_real = a; m_imag = b; } private : double m_real; double m_imag; }; //建立對象 Complex A;//這個時候不能有() Complex A(1,1); Complex *A = new Complex( );//能夠有()也能夠沒有 Complex *A = new Complex(1,1);
Student(string n="name", int num=1000) :name(n),number(num){ //name = n; //number = num; } 注意: * name、number:是本類裏面的成員; * n、num:是對成員賦的值或者變量; * 不能在類裏面聲明的時候用參數列初始化,聲明的時候能夠加默認值; 對對象成員進行列表初始化: class A { public: A(int a,int b){} } class B:public A { A a; public: B(int c,int d ):a(c,d){} }
使用緣由及用處:
(1)若是沒有自定義拷貝構造函數,系統會默認生成一個拷貝構造函數(淺拷貝構造函數,不會拷貝堆空間)
Student Jack; //構造函數 Student Rose = Jack; //拷貝構造函數 Student Tom (Jack); //拷貝構造函數 後面兩種拷貝構造函數不會再次調用構造函數
(2)深拷貝構造函數
class Student { public: Student(int age, const char *n){ this->age = age; this->name = new char[32]; strcpy(this->name, n); cout<<"Student()"<<endl; }//this指針就是函數調用者 ~Student(){ delete []this->name; cout<<"~Student()"<<endl; } //深拷貝構造函數 Student(Student& s) { cout<<"Student(Student&)"<<endl; this->age = s.age; this->name = new char[32]; strcpy(this->name, s.name); //this->name = s.name; } private: int age; char *name; };
例子:
class Complex { public: Complex( )//構造函數定義 { cout << "complex" << endl; } ~ Complex( )//析構函數定義 { cout << "~complex" << endl; } }; int main( ) { Complex a; Complex *p = new Complex(); delete p; return 0; } 結果: complex complex ~complex ~complex
類自己是一個數據類型,在沒有定義對象前是不佔用內存空間的,定義對象的時候纔會分配空間。
繼承:
做用:
繼承能夠減小重複的代碼。好比父類已經提供的方法,子類能夠直接使用,沒必要再去實現。
類的繼承格式:
class 子類名 :繼承方式 父類 { 子類成員 };
例如:
class Base { public: Base() {} int b; }; class Child: public Base { };
繼承方式: 公有繼承, 保護繼承, 私有繼承
注意1: 私有繼承在下一次被繼承時,全部從父類繼承而來的都會別隱藏,而保護繼承在下次被繼承時根據繼承的屬性其中的數據可能被重新利用,因此私有繼承的保護性更增強。
注意2: 不管那種繼承子類的大小爲子類+父類(全部成員都要加起來,包括私有成員)
由於有父類纔有子類,因此調用順序以下:
構造函數的調用順序父類構造函數—對象成員構造函數—子類構造函數。
析構函數則相反。
注意:
例子一: 父類構造函數 public: Person(string name, string sex, int age):name(name),sex(sex),age(age) { cout<<"Person()"<<endl; } 子類構造函數 public: Student( ):Person("jack","man",19){ cout<<"Student()"<<endl; } //==>Person(); 例子二: class Animal { public: Animal(int w, const char *color, int age){ this->weight = w; this->age = age; strcpy(this->color, color); } protected: int weight; int age; char color[32]; }; class Cat:public Animal { public: Cat(int w, const char *color, int age, const char *type):Animal(w, color, age) { strcpy(this->type, type); } void show() { cout << "weight=" << weight << "\nage=" << age << "\ncolor=" << color <<endl; } protected: char type[32]; };
例如:
#include <iostream> using namespace std; class A{ public: A(){cout << "Afather\n";} ~A(){cout << "~Afather\n";} void fun( ){ cout << "father fun\n"; } }; class B:public A{ public: B(){cout << "Bchildren\n";} ~B(){cout << "~~Bchildren\n";} void fun(){ cout << "children fun\n"; } }; int main( ) { B x; x.A::fun( );//調用父類的fun return 0; } 輸出結果: Afather Bchildren father fun ~~Bchildren ~Afather
(1)語法:
class <派生類名>:<繼承方式1> <基類名1>,<繼承方式2><基類名2>,… { <派生類類體> };
(2)例子1:
class A{ public: A(){cout<<"A()"<<endl;} ~A(){cout<<"~A()"<<endl;} protected: int dataA; }; class B{ public: B(){cout<<"B()"<<endl;} ~B(){cout<<"~B()"<<endl;} protected: int dataB; }; class C:public A, public B { public: C(){cout<<"C()"<<endl;} ~C(){cout<<"~C()"<<endl;} protected: int dataC; };
注意:建立子類對象構造順序 A->B->C
若是改成:class C:public B, public A,建立子類對象構造順序 B->A->C
(3)例子2: 若是父類構造函數帶參數
繼承關係class C:public A, public B 父類帶參數 A(int a):dataA(a){cout<<"A()"<<endl;} B(int b):dataB(b){cout<<"B()"<<endl;} C(int a, int b, int c):A(a), B(b),dataC(c){cout<<"C()"<<endl;}
(4)多個父類有同名的成員
多個父類有同名的成員, 在子類中訪問會出現歧義
語法:
class D :virtual public B{ //虛擬繼承 ... };
多繼承中多級繼承時候多個父類同時繼承同一個基類出現二義性問題--用虛擬繼承解決。
例如:
class A{ public: void fun(){} }; class B:virtual public A{ }; class C:virtual public A{ }; class D:public B,public C{ }; int main(void) { D x; x.fun();//若是不是虛繼承,會出現二異性,由於在D類繼承了兩次A類 return 0; }
定義: 在類的成員函數聲明前面添加virtual
virtual void show(){cout<<data<<endl;}
例子1:觀察輸出的最後結果是什麼(必定要看)
#include <iostream> using namespace std; class Base { public: Base(){} virtual ~Base(){} public: virtual void show(int a=123){ cout<<"Base::show()"<<a<<endl; } }; class Child:public Base { public: Child(){} ~Child(){} virtual void show(int a=321){ cout<<"Child::show()"<<a<<endl; } virtual void info() { cout<<"Child::info()"<<endl; } }; int main() { Child c; Base *p = &c; p->show(); return 0; } 結果: Child::show()123 注意: (1)當show函數不是虛繼承時,輸出結果爲Base::show()123,由於父類的指針只能調用本身的成員,若是有虛繼承,則虛表裏面父類的show函數的地址會被子類的show函數地址覆蓋,被覆蓋的前提是:兩個函數的名稱和參數類型、個數和返回值類型同樣。
例子2:經過指針調用虛表中的虛函數(在ubuntu下運行,虛表地址經過qt調試查看)
#include <iostream> using namespace std; class Base { public: Base(){} virtual ~Base(){} protected: virtual void show(int a= 0){ cout<<"Base::show()"<<endl; } }; class Child:public Base { public: Child(){} ~Child(){} virtual void show(){ cout<<"Child::show()"<<endl; } virtual void info() { cout<<"Child::info()"<<endl; } }; int main() { Child c; typedef void (*Fun)(); c.show(); Fun f = (Fun)(((long*)(*((long*)(&c))))[2]); f(); return 0; } 結果: Child::show() Base::show()
(1)純虛函數--虛函數不須要實現直接賦值爲0,純虛函數有時稱爲抽象函數。
定義:
virtual void run()=0;
(2)抽象類
例子:線程獲取時間
#include <iostream> #include <pthread.h> #include <windows.h> #include <time.h> using namespace std; class Thread{ public: Thread(){} ~Thread(){} void start(); virtual void run()=0; protected: pthread_t id; }; void *handle(void *arg) { Thread* th = (Thread*)arg; th->run(); } void Thread::start() { int ret = pthread_create(&id, NULL, handle, (void*)this); if(ret < 0) { cout<<"create fail"<<endl; } } //派生一個線程子類--獲取系統時間 class TimeThread: public Thread{ public: virtual void run() { while(1) { cout<<"TimeThread::run()"<<endl; Sleep(1000); time_t t; time(&t); cout<<pthread_self()<<"-----------"<<ctime(&t)<<endl; } } }; int main() { TimeThread tth; tth.start(); TimeThread tt; tt.start(); while(1){} return 0; }
<1>概念
C++中,多態性是指具備不一樣功能的函數能夠用同一個函數名,這樣就能夠用一個函數名調用不一樣內容的函數。
在面向對象方法中通常是這樣表述多態性的:向不一樣的對象發送同一消息(調用函數),不一樣的對象在接收時會產生不一樣的行爲(即方法,不一樣的實現,即執行不一樣的函數)。能夠說多態性是「一個接口,多種方法」。
多態性分爲兩類:
(1)靜態多態性:在程序編譯時系統就能決定調用的是哪一個函數,所以又稱爲編譯時的多態性,經過函數的重載實現(運算符重載實際上也是函數重載);
(2)動態多態性:在程序運行過程當中才動態地肯定操做所針對的對象,又稱爲運行時多態性,經過虛函數實現。
區別:函數重載是同一層次上的同名函數(首部不一樣,即參數個數或類型不一樣),虛函數是不一樣層次上的同名函數(首部相同)。
<2>動態多態性和虛函數
父類引用(指針變量)指向子類對象時,調用的方法仍然是父類中的方法。若是將父類中的該方法定義爲virtual,則調用的方法就是子類中的方法了。
說明:原本,父類指針是用來指向父類對象的,若是指向子類對象,則進行類型轉換,將子類對象的指針轉爲父類的指針,因此父類指針指向的是子類對象中的父類部分,也就沒法經過父類指針去調用子類對象中的成員函數。可是,虛函數能夠突破這一限制!若是不使用虛函數,企圖經過父類指針調用子類的非虛函數是絕對不行的!
注意:父類中非虛函數被子類重寫後,父類指針調用的是父類的成員函數,子類指針調用的是子類中的成員函數,這並非多態!由於沒有用到虛函數!
例如:
#include <iostream> using namespace std; class Person{ public: Person(){} ~Person(){} virtual void work(){cout<<"Person::work()"<<endl;} protected: int data; }; class ChildPerson: public Person{ public: ChildPerson(){} ~ChildPerson(){} virtual void show(){cout<<"ChlidPerson::show()"<<endl;} virtual void work(){cout<<"ChildPerson::work()"<<endl;} virtual void info(){} protected: int number; }; class A: public Person{ public: A(){} ~A(){} virtual void show(){cout<<"A::show()"<<endl;} virtual void work(){cout<<"A::work()"<<endl;} virtual void info(){} protected: int num; }; int main() { ChildPerson cp; A a; Person* p = &a; Person* pson = &cp; pson->work(); //ChildPerson::work(); p->work();//A:work(); return 0; }
多態的時候,用父類指針指向子類對象, 在delete 父類指針的時候默認只會調用父類析構函數,子類析構函數沒有執行(可能會致使子類的內存泄漏)--經過設置父類析構函數爲虛函數類解決,執行子類析構函數後,自動執行父類析構函數。
例如:
#include<iostream> using namespace std; class Base { public: Base(){cout<<"create Base"<<endl;} virtual ~Base(){cout<<"delete Base"<<endl;} }; class Der : public Base { public: Der(){cout<<"create Der"<<endl;} ~Der(){cout<<"Delete Der"<<endl;} }; int main(int argc, char const* argv[]) { Base *b = new Der; delete b; return 0; }
友元:是c++裏面一個特性,爲了解決在函數中能夠訪問類的私有,或保護成員。
友元函數是能夠直接訪問類的私有成員的非成員函數。它是定義在類外的普通函數,它不屬於任何類。
第一種定義狀況:類外定義:例如
class Data { public: Data() {} void setA(int a) { this->a = a; } protected: int a; private: int b; //在Data類中聲明函數fun爲友元函數 friend void fun(); }; void fun() { Data data; //data.a = 120; data.setA(120); data.b = 220; } 友元聲明只放類內部聲明, 能夠放在類內任意位置
第二種定義狀況:類內定義例如:
class Data { public: Data() {} void setA(int a) { this->a = a; } protected: int a; private: int b; //在Data類中聲明函數fun爲友元函數 friend void fun(); //聲明show爲友元函數,show不是成員函數 friend void show() { Data data; data.a = 130; cout<<data.a<<" "<<data.b<<endl; } }; void show(); //在外面聲明函數
在一個類中的成員函數能夠訪問另一個類中的全部成員好比在 A 類中的成員函數能夠訪問 B 類中的全部成員。有兩種方法以下:
(1)在 B 類中設置 A 類爲友元類。
(2)A::fun 要訪問B類中的全部成員, 把A::fun函數聲明爲B類的友元函數。
(1)例如:在 B 類中設置 A 類爲友元類,A類成員函數能夠訪問B類的protected、private成員,B類不能訪問A類,若是要雙向訪問則要在兩個類中聲明對方爲友元類。友元關係不能被繼承。
class B { public: B(){} friend class A;//在 B 類中聲明 A 類爲友元類 private: int bdata; }; class A { public: A(){} void showB(B &b) { b.bdata = 100;//在 A 類中成員函數使用 B 類的私有數據 } private: int adata; };
(2)A::fun 要訪問B類中的全部成員, 把A::fun函數聲明爲B類的友元函數
#include <iostream> using namespace std; //前向聲明----只能用於函數形參, 定義指針, 引用,不能使用類具體成員 class B; class A{ public: void fun(B& b); }; class B{ public: B(){} protected: int mb; private: int nb; friend void A::fun(B& b);//在B類中聲明fun爲友元函數 }; void A::fun(B& b){ cout<<b.mb<<b.nb<<endl; } int main() { cout << "Hello World!" << endl; return 0; }
運算符重載 關鍵子函數operator
一、那些運算能重載
二、那些運算符不能重載
注意:
三、格式:
返回類型說明符 operator 運算符符號(<參數表>) { 函數體 }
四、重載方式:
1.重載方式---成員函數重載
Complex C = A+B; --》A.operator+(B); 規定:左值是函數調用者, 右值函數的參數
2.重載方式--友元重載(普通函數重載)(能夠在類裏面定義,也能夠在類外定義類內聲明)
Complex C = A-B; -->operator-(A, B); 規定:左值爲第一個參數, 右值爲第二個參數
#include <iostream> using namespace std; class Complex { public: Complex(int r, int i):real(r), image(i) {} //成員重載實現重載加法+ Complex operator+ (Complex &B) { cout<<"operator"<<endl; Complex C(0,0); C.real = this->real + B.real; C.image = this->image + B.image; return C; } //成員重載實現 對象加一個整型數 int operator+(const int &a) { this->real += a; this->image += a; return this->real; } private: int real; int image; friend Complex operator- (Complex &A, Complex &B); }; //友元重載實現重載減法 Complex operator- (Complex &A, Complex &B) { Complex C(0,0); C.real = A.real - B.real; C.image = A.image - B.image; return C; } int main() { Complex A(2,2); Complex B(1,1); Complex C = A+B; //==>A.operator+(B) int c = A+100; //==>A.operator+(100) Complex D = A-B; //operator-(A, B) return 0; }
#include <iostream> using namespace std; class Point { public: Point (int x=0, int y=0):x(x),y(y){} void show() { cout<<"("<<x<<","<<y<<")"<<endl; } private: int x, y; //聲明友元函數 friend ostream& operator<<(ostream &out, Point& p); friend istream& operator>>(istream &in, Point& p); }; //重載輸出 ostream& operator<<(ostream &out, Point& p) { out<<"("<<p.x<<","<<p.y<<")"<<endl; return out; } //輸入重載 istream& operator>>(istream &in, Point& p) { in>>p.x>>p.y; return in; } int main() { Point p(10,20); p.show(); cout<<p<<endl; //==> ostream& operator<<(cout, p) Point A(0,0); cin>>A; cout<<A; return 0; }
成員函數重載
#include <iostream> using namespace std; class Data { public: Data(int d=0):data(d) {} //重載A++ Data operator++(int) { Data old(*this);//保存原先的數捍 this->data += 1;//對原數進行自劍 return old;//返回未加以前的數捍 } //重載++A Data& operator++() { this->data += 1;//對原數進行自劍 return *this; } private: int data; friend ostream &operator<<(ostream& out, Data &d); }; ostream &operator<<(ostream& out, Data &d) { out<<d.data<<endl; return out; } int main() { Data A; Data d = A++; //==>A.operator++(int) cout<<d<<A<<endl; Data &c = ++A; cout<<c<<A<<endl; return 0; }
友元函數重載
#include <iostream> using namespace std; class A { int data; public: A(int d = 0):data(d) {} void show() { cout << this->data << endl; } //友元函數重載++A friend A& operator++ (A &a); //友元函數重載A++ friend A operator++ (A &b,int); //友元函數重載<< friend ostream& operator<< (ostream &out,A &a); }; //友元函數重載++A A& operator++ (A &a) { a.data += 1; return a; } //友元函數重載A++ A operator++ (A &b,int) { A old(b); b.data += 1; return old; } //友元函數重載<< ostream& operator<< (ostream &out,A &a) { out << a.data; return out; } int main(int argc,char **argv) { A a(5); A b = ++a; cout << a << " " << b << endl; A c(5); A d = c++; cout << c << " " << d << endl; return 0; }
#include <iostream> using namespace std; class Array { public: Array(int n):length(n) { this->ptr = new int[this->length]; } ~Array(){ delete []this->ptr; } //拷貝構造函數---深拷貝(類的成員有指針指向堆空間) Array(Array& array) { this->length = array.length; this->ptr = new int[this->length]; memcpy(this->ptr, array.ptr , this->length); } //重載[] int& operator[](int i) { cout<<i<<endl; return this->ptr[i];//返回第i個對象 } private: int length; int *ptr; }; int main() { Array mArr(10); mArr[0] = 100; // return 0; }
轉換構造函數的做用:是將一個其餘類型的數據轉換成一個類的對象。 當一個構造函數只有一個參數,並且該參數又不是本類的const引用時,這種構造函數稱爲轉換構造函數。 轉換構造函數是對構造函數的重載。
例如:
#include <iostream> using namespace std; class Complex { public: Complex():real(0),imag(0){cout << "test1\n";} Complex(double r, double i):real(r),imag(i){cout << "test2\n";} // 定義轉換構造函數 Complex(double r):real(r),imag(0){cout << "test3\n";} /* // 拷貝構造函數 Complex(Complex &a){ cout << "test4\n"; }//當此函數存在時,Complex c = 1;Complex c2 = c1 + 3.1;編譯時都會報錯 */ void Print(){ cout<<"real = " << real <<" image = "<<imag<<endl; } Complex operator+(Complex c){ Complex ret(this->real + c.real, this->imag + c.imag); return ret; } private: double real; double imag; }; int main() { Complex c; c = 4; // 調用轉換構造函數將1.2轉換爲Complex類型,此時會調用轉換構造函數 c.Print(); Complex c1(2.9, 4.2); Complex c2 = c1 + 3.1; // 調用轉換構造函數將3.1轉換爲Complex類型 c2.Print(); return 0; } 輸出結果: test1 test3 real = 4 image = 0 test2 test3 test2 real = 6 image = 4.2
注意:
用轉換構造函數能夠將一個指定類型的數據轉換爲類的對象。可是不能反過來將一個類的對象轉換爲一個其餘類型的數據(例如將一個Complex類對象轉換成double類型數據)。而類型轉換函數就是專門用來解決這個問題的!
類型轉換函數的做用是將一個類的對象轉換成另外一類型的數據。
#include <iostream> using namespace std; class Person { public: Person(int age=0):age(age) {} operator int() //經過運算符重載來實現數據類型轉換 { cout<<"int"<<endl; return age; } operator long() //經過運算符重載來實現數據類型轉換 { cout<<"long"<<endl; return num; } int getAge(){return age;} private: int age; long num; }; int main() { Person Jack(19); int age = Jack; int a = Jack.getAge(); long b = Jack; return 0; }
注意:
一、概念: 若是一個函數實現的功能相似,可是函數參數個數相同類型不一樣,這樣就能夠把實在該功能的函數設計爲模板函數。
二、格式:
template <typename T> //T爲類型名 數據類型 函數名(參數列表){ 函數體 }
三、注意:
例子1:
//設計一個模板函數實現兩個對象交換 template <typename T> void mswap(T &a, T &b) { T c = a; a = b; b = c; } int main() { int a = 10; int b = 20; cout<<a<<" "<<b<<endl; mswap(a, b); cout<<a<<" "<<b<<endl; return 0;
例子2:
//錯誤,由於不可以肯定返回值的類型 template <class T, class Tp> Tp fun(T &a) { return a; } //修改,但返回值定死了 template <class T, class Tp = int> Tp fun(T &a) { return a; } //調用函數時指定類型 template <class T, class Tp = int> Tp fun(T &a) { return a; } int main(void) { int a = 2; double ret = fun<int, double>(a) }
四、模板函數與函數普通同時存在該如何調用
template <typename T> void mswap(T &a, T &b) { cout<<"template"<<endl; T c = a; a = b; b = c; } //普通函數 void mswap(int &a, int &b) { cout<<"std"<<endl; int c = a; a = b; b = c; } 調用(1) int a = 10; int b = 20; cout<<a<<" "<<b<<endl; mswap(a, b);//---普通函數 cout<<a<<" "<<b<<endl; 調用(2) double a = 10; double b = 20; cout<<a<<" "<<b<<endl; mswap(a, b);//---模板函數 cout<<a<<" "<<b<<endl; 若是模板函數和普通函數同時存在, 調用的時候會根據參數選擇最優函數
//設計一個模板類 -模板類的類名 A<T> //template< class T , class Ty> //A<T, Ty> template< class T > //A<T> class A { public: A() {} protected: T dataA; }; int main() { A<int> a;//定義模板類對象 return 0; }
注意:
例如: 用模板類設計一個順序表(數組)
#include <iostream> using namespace std; template< class T > class MVector{ public: MVector(){ this->size = 1024; this->count = 0; this->ptr = new T[this->size]; } ~MVector(){ delete []this->ptr; } //拷貝構造函數 MVector(MVector& mv){ this->size = mv.size; this->ptr = new T[this->size]; memcpy(this->ptr, mv.ptr, this->size*sizeof(T)); } //添加數據 void append(const T &data){ this->ptr[this->count] = data; this->count++; } //重載<<追加數據 void operator<<(int data) { this->ptr[this->count] = data; this->count++; } #if 1//在類裏面定義重載函數 //聲明友元重載輸出<< friend ostream& operator<<(ostream& out, MVector &mv) { for(int i=0; i<mv.count; i++) { out<<mv.ptr[i]<<" "; } out<<endl; return out; } #endif //template<class Ty> //friend ostream& operator<<(ostream& out, MVector<Ty> &mv); protected: int count; int size; T* ptr; }; #if 0 //在類內聲明,在類外定義重載函數 //重載輸出<<運算符 template< class Ty > ostream& operator<<(ostream& out, MVector<Ty> &mv) { for(int i=0; i<mv.count; i++) { out<<mv.ptr[i]<<" "; } out<<endl; return out; } #endif //模板函數在使用(不是運行)該函數的時候纔會檢查語法 int main() { MVector<int> mvs; mvs.append(100); mvs<<200; cout<<mvs; return 0; }
若是在派生子類的時候父類類沒有肯定class B: public A
例如:
#include <iostream> using namespace std; //設計一個模板類A<T> template< class T > class A { public: A(T a) {} protected: T data; }; //設計一個子類B 繼承模板類A<T> --B類也是模板類 template< class T > class B: public A<T> { public: B(T a):A<T>(a){} protected: int datab; }; //設計一個子類C 繼承模板類A<int> --C類就是一個具體類 class C: public A<int> { public: C():A<int>(10){} }; int main() { A<char> a(10); //模板類建立對象 B<string> b("hello"); //模板類子類建立對象 C c; return 0; }
編譯時根據模板生成的不一樣類的靜態成員是不一樣內存空間的;在同一個類中建立的對象的靜態成員是共用一個內存空間的。
以下:
#include <iostream> using namespace std; template<class T> class Data { public: Data() {} void show(T msg) { data = msg; cout<<data<<endl; } public: static T data; }; //類外初始化靜態成員 template<class T> T Data<T>::data ; int main() { //建立一個對象 Data<int> mydata; //編譯的時候會生成一個 T爲int的類 mydata.show(100); Data<string>::data = "hello"; //編譯的時候會生成一個T 爲string的類 cout<<Data<string>::data<<endl; Data<string> mystr; cout<<mystr.data<<endl; return 0; }
注意:以上,若是轉換失敗的時候會返回空
#include <iostream> using namespace std; int main() { const int a = 10; const int *p = &a; int *ptr = (int*)(&a);//c語言轉換(在c語言能夠這樣寫:int *ptr=&a,只是會警告,同樣能夠操做,c++不容許) *ptr = 1; cout<<a<<endl; cout<<*ptr<<endl; int &ra = const_cast<int&>(a); ra = 2; cout<<a<<endl; cout<<ra<<endl; int *x = const_cast<int*>(p); *x = 3; cout<<a<<endl; cout<<*x<<endl; return 0; } 輸出結果: 10 1 10 2 10 3 解釋:由於a是const修飾的,此時a的值會存在符號表中,也就是改變a地址所指向的值,也不會改變a的值,當調用a的時候,編譯器回到符號表中取值,而不是從a的地址取值。
(1)爲什麼要去除const限定
緣由(1)是,咱們可能調用了一個參數不是const的函數,而咱們要傳進去的實際參數確實const的,可是咱們知道這個函數是不會對參數作修改的。因而咱們就須要使用const_cast去除const限定,以便函數可以接受這個實際參數。
例如:
#include <iostream> using namespace std; void Printer (int* val,string seperator = "\n") { cout << val<< seperator; } int main(void) { const int consatant = 20; //Printer(consatant);//Error: invalid conversion from 'int' to 'int*' Printer(const_cast<int *>(&consatant)); return 0; }
緣由(2):
還有一種我能想到的緣由,是出如今const對象想調用自身的非const方法的時候,由於在類定義中,const也能夠做爲函數重載的一個標示符。
static_cast < type-id > ( expression )該運算符把expression轉換爲type-id類型,但沒有運行時類型檢查來保證轉換的安全性。它主要有以下幾種用法:
注意: static_cast不能轉換掉expression的const、volatile、或者__unaligned屬性
例如
#include <iostream> using namespace std; int main() { char a = 'a'; int b = (int)a; double g = static_cast<int>(a); //爲何不能轉換普通類型指針,卻能轉換對象指針和void指針(規定的) void *pp; double *pp1 = static_cast <double*>(pp); int *xx; void *xx1 = static_cast <void*>(xx); //double *xx2 = static_cast <double*>(xx);//錯誤寫法 return 0; }
reinterpret_cast
type-id 必須是一個指針、引用、算術類型、函數指針或者成員指針。它能夠把一個指針轉換成一個整數,也能夠把一個整數轉換成一個指針(先把一個指針轉換成一個整數,再把該整數轉換成原類型的指針,還能夠獲得原先的指針值)。
例如1:
#include <iostream> using namespace std; class A { public: int m_a; }; class B { public: int m_b; }; class C : public A, public B {}; int main() { int n= 1231651 ; double *d; cout << d << endl; d=reinterpret_cast<double*> (&n); //爲何d和n的地址同樣但爲何地址裏面的值不同? //是由於double類型數據存儲的方式不同,用*d訪問時, //系統會以讀取double類型數據來讀取。 cout << d << " " << &n << endl; cout << *d << " " << n << endl; cout << "---------------------------\n"; //將一個32位的整數轉換成一個指針 char *n_p = reinterpret_cast<char*>(10); //reinterpret_cast和static_cast的主要區別在於多繼承 C c; printf("%p, %p, %p", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));//前兩個的輸出值是相同的,最後一個則會在原基礎上偏移4個字節,這是由於static_cast計算了父子類指針轉換的偏移量,並將之轉換到正確的地址(c裏面有m_a,m_b,轉換爲B*指針後指到m_b處),而reinterpret_cast卻不會作這一層轉換。 return 0; }
結果:
例如2:
//強制類型轉換//編寫一個程序跳轉到地址0x12345678運行 typedef void(*Fun)(void);//定義一個函數指針數據類型 Fun fun = reinterpret_cast<Fun>( 0x12345678 ); fun();
dynamic_cast < type-id > ( expression )
說明: 該運算符把expression轉換成type-id類型的對象。Type-id必須是類的指針、類的引用或者void ;若是type-id是類指針類型,那麼expression也必須是一個指針,若是type-id是一個引用,那麼expression也必須是一個引用。
使用場景: dynamic_cast主要用於類層次間的上行轉換和下行轉換,還能夠用於類之間的交叉轉換。在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是同樣的;在進行下行轉換時,dynamic_cast具備類型檢查的功能,比static_cast更安全。
注意:
① dynamic_cast是動態轉換,只有在基類指針轉換爲子類指針時纔有意義。
② dynamic_cast<>須要類成爲多態,即包括「虛」函數,並所以而不能成爲void*。
③ static_cast和dynamic_cast能夠執行指針到指針的轉換,或實例自己到實例自己的轉換,但不能在實例和指針之間轉換。static_cast只能提供編譯時的類型安全,而dynamic_cast能夠提供運行時類型安全。
例如:
//dynamic---用於繼承過程當中把父類指針轉換爲子類指針 #include <iostream> using namespace std; class A { public: A() {} virtual ~A(){} }; class B:public A { public: B() {} ~B(){} }; //調用 int main() { A *p = new B(); //用戶子類指針初始化父類指針 A *a = new A();//建立一個A類對象 //如下兩句必須在基類有虛析構的狀況下才正確,不然編譯的時候報錯 B *bptr = dynamic_cast<B*>(a);//把新的父類指針賦值子類指針(nullptr) B *bptr1 = dynamic_cast<B*>(p);//p是A類型指向B對象空間,把p轉回B類 if(bptr1 == nullptr)//爲nullptr時轉換不成功 { cout<<"fail"<<endl; } else { cout<<"success"<<endl; } return 0; }
在閱讀別人開發的項目中,也許你會常常看到了多處使用異常的代碼,也許你也不多碰見使用異常處理的代碼。那在何時該使用異常,又在何時不應使用異常呢?在學習完異常基本概念和語法以後,後面會有講解。
//1.拋出異常 throw 異常對象 //2.異常捕捉 try{ 可能會發生異常的代碼 }catch(異常對象){ 異常處理代碼 }
class Data { public: Data() {} }; void fun(int n) { if(n==0) throw 0;//拋異常 int異常 if(n==1) throw "error"; //拋字符串異常 if(n==2) { Data data; throw data; } if(n>3) { throw 1.0; } } int main() { try { fun(6);//當異常發生fun裏面,fun如下代碼就不會再執行,調到catch處執行異常處理代碼,後繼續執行catch之外的代碼。當throw拋出異常後,沒有catch捕捉,則整個程序會退出,不會執行整個程序的如下代碼 cout<<"*************"<<endl; }catch (int i) { cout<<i<<endl; }catch (const char *ptr) { cout<<ptr<<endl; }catch(Data &d) { cout<<"data"<<endl; }catch(...)//抓取 前面異常之外的全部其餘異常 { cout<<"all"<<endl; } return 0; }
#include <iostream> using namespace std; int main() { try { char* p = new char[0x7fffffff]; //拋出異常 } catch (exception &e){ cout << e.what() << endl; //捕獲異常,而後程序結束 } return 0; }
輸出結果:
當使用new進行開空間時,申請內存失敗,系統就會拋出異常,不用用戶自定義異常類型,此時捕獲到異常時,就可告訴使用者是哪裏的錯誤,便於修改。
#include <iostream> #include <exception> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> using namespace std; class FileException :public exception { public: FileException(string msg) { this->exStr = msg; } virtual const char*what() const noexcept//聲明這個函數不能再拋異常 { return this->exStr.c_str(); } protected: string exStr; }; void fun() { int fd = ::open("./open.txt",O_RDWR); if(fd<0) { FileException openFail("open fail"); //建立異常對象 throw openFail;//拋異常 } } int main( ) { try { fun(); } catch (exception &e) {//通常須要使用引用 cout<<e.what()<<endl; } cout<<"end"<<endl; return 0; }
當文件不存在時,輸出結果:
若是在Linux上運行,上述代碼須要根據環境修改:
98標準寫法
~FileException()throw(){}//必需要 virtual const char*what() const throw()//聲明這個函數不能再拋異常 { return this->exStr.c_str(); } //編譯 g++ main.cpp
2011標準寫法
~FileException()noexcept{}//必需要 virtual const char*what() const noexcept//聲明這個函數不能再拋異常 { return this->exStr.c_str(); } //編譯 g++ main.cpp -std=c++11 指定用c++11標準編譯
1. 使用異常處理的優勢:
2. 使用異常的缺點:
3. 何時使用異常?
4. 程序全部的異常均可以catch到嗎?
容器:
#include <iostream> #include <vector> using namespace std; int main() { //建立vector對象 //大小變化:1024 2048 4096(開始會以2倍數增長,後面慢慢以1/三、1/5等的形式增長) vector<string> names; //賦值,3個jack names.assign(3,"Jack"); //Jack, Jack, Jack //插入數據 //建立一個迭代器 vector<string>::iterator it = names.begin(); //insert以後迭代器it已經改變,返回值爲插入值的位置 it = names.insert(++it, "Rose"); //結果:Jack Rose Jack Jack it = names.insert(++it,"Jim");//結果:Jack Rose Jim Jack Jack it = names.insert(++it, "lcg"); //結果:Jack Rose Jim lcg Jack Jack it = names.insert(names.end(), "Jack"); //結果:Jack Rose Jim lcg Jack Jack Jack //查詢數據/刪除數據---迭代器遍歷使用vector, list, map, set //names.end()爲順序表最後一個元素的下一個地址 for(it = names.begin(); it != names.end(); ++it) { if(*it == "Jack") { cout<<*it<<" "; //擦除,返回擦出元素的下一個位置 //例如a b c,刪除b後,返回迭代器指向c it=names.erase(it); --it; } } cout<<endl; //遍歷---順序表 for(int i=0; i<names.size(); i++) { cout<<names[i]<<" "; } cout<<endl; return 0; }