數據描寫敘述: ios
半徑。周長,面積均用實型數表示c++
數據處理:面試
輸入半徑 r。算法
計算周長 = 2*π*r ;編程
計算面積 = π* r2 。設計模式
輸出半徑,周長,面積;數組
方法1:用結構化方法編程,求圓的周長和麪積緩存
// count the girth and area of circle安全 #include<iostream.h>數據結構 using name std; void main () { double r, girth, area ; const double PI = 3.1415 ; cout << "Please input radius:\n" ; //操做符重載 cin >> r ; //輸入 girth = 2 * PI * r ; area = PI * r * r ; cout << "radius = " << r << endl ; cout << "girth = " << girth << endl ; cout << "area = " << area << endl ; }
|
方法2:用面向對象方法編程,求圓的周長和麪積
#include<iostream.h> using name std; class Circle { double radius ; //成員變量 public : //類的訪問控制 void Set_Radius( double r ) { radius = r ; } //成員函數 double Get_Radius() { return radius ; } //經過成員函數設置成員變量 double Get_Girth() { return 2 * 3.14f * radius ; } //經過成員函數獲取成員變量 double Get_Area() { return 3.14f * radius * radius ; } } ; void main() { Circle A, B ; //用類定義對象 A.Set_Radius( 6.23 ) ; //類的調用 cout << "A.Radius = " << A.Get_Radius() << endl ; cout << "A.Girth = " << A.Get_Girth() << endl ; cout << "A.Area = " << A.Get_Area() << endl ; B.Set_Radius( 10.5 ) ; cout << "B.radius = " << B.Get_Radius() << endl ; cout << "B.Girth=" << B.Get_Girth() << endl ; cout << "B.Area = " << B.Get_Area() << endl ; }
|
總結:創建類、對象、成員變量、成員函數,輸入輸入流基本概念。
// demo02_circle_err.cpp #include<iostream> using namespace std;//c++的命名空間 class circle { public:
double r;
double pi = 3.1415926; double area = pi*r*r;
};
int main() { circle pi; cout << "請輸入area" << endl; cin >> pi.r;
cout << pi.area << endl; //亂碼
system("pause"); return 0; } |
總結: 從內存四區的角度。解釋爲何會出現亂碼
理解爲何需要成員函數
面向過程的結構化程序設計方法
l 設計思路
– 自頂向下、逐步求精。採用模塊分解與功能抽象,自頂向下、分而治之。
l 程序結構:
– 按功能劃分爲若干個基本模塊,造成一個樹狀結構。
– 各模塊間的關係儘量簡單,功能上相對獨立;每一模塊內部均是由順序、選擇和循環三種基本結構組成。
– 其模塊化實現的詳細方法是使用子程序。
l 長處:
有效地將一個較複雜的程序系統設計任務分解成不少易於控制和處理的子任務,便於開發和維護。
l 缺點:可重用性差、數據安全性差、難以開發大型軟件和圖形界面的應用軟件
– 把數據和處理數據的過程分離爲相互獨立的實體。
– 當數據結構改變時,所有相關的處理過程都要進行對應的改動。
– 每一種相對於老問題的新方法都要帶來額外的開銷。
– 圖形用戶界面的應用程序,很是難用過程來描寫敘述和實現。開發和維護也都很是困難。
面向對象的方法
l 將數據及對數據的操做方法封裝在一塊兒,做爲一個相互依存、不可分離的整體——對象。
l 對同類型對象抽象出其共性。造成類。
l 類經過一個簡單的外部接口,與外界發生關係。
l 對象與對象之間經過消息進行通訊。
面向對象的基本概念
對象
l 通常意義上的對象:
– 是現實世界中一個實際存在的事物。
– 可以是有形的(比方一輛汽車),也可以是無形的(比方一項計劃)。
– 是構成世界的一個獨立單位,具備
l 靜態特徵:可以用某種數據來描寫敘述
l 動態特徵:對象所表現的行爲或具備的功能
l 面向對象方法中的對象:
– 是系統中用來描寫敘述客觀事物的一個實體。它是用來構成系統的一個基本單位。對象由一組屬性和一組行爲構成。
– 屬性:用來描寫敘述對象靜態特徵的數據項。
– 行爲:用來描寫敘述對象動態特徵的操做序列。
類
l 分類——人類一般的思惟方法
l 分類所根據的原則——抽象
– 忽略事物的非本質特徵,僅僅注意那些與當前目標有關的本質特徵。從而找出事物的共性。把具備共同性質的事物劃分爲一類,得出一個抽象的概念。
– 好比,石頭、樹木、汽車、房屋等都是人們在長期的生產和生活實踐中抽象出的概念。
l 面向對象方法中的"類"
– 具備一樣屬性和服務的一組對象的集合
– 爲屬於該類的全部對象提供了抽象的描寫敘述。包含屬性和行爲兩個主要部分。
– 類與對象的關係:
宛如模具與鑄件之間的關係,一個屬於某類的對象稱爲該類的一個實例。
封裝
也就是把客觀事物封裝成抽象的類,並且類可以把本身的數據和方法僅僅讓可信的類或者對象操做。對不可信的進行信息隱藏。
l 把對象的屬性和服務結合成一個獨立的系統單元。
l 儘量隱蔽對象的內部細節。對外造成一個邊界(或者說一道屏障),僅僅保留有限的對外接口使之與外部發生聯繫。
l 繼承對於軟件複用有着重要意義,是面向對象技術能夠提升軟件開發效率的重要緣由之中的一個。
l 定義:特殊類的對象擁有其通常類的全部屬性與服務。稱做特殊類對通常類的繼承。
l 好比:將輪船做爲一個通常類,客輪即是一個特殊類。
多態
多態是指在通常類中定義的屬性或行爲,被特殊類繼承以後,可以具備不一樣的數據類型或表現出不一樣的行爲。這使得同一個屬性或行爲在通常類及其各個特殊類中具備不一樣的語義。
面向對象的軟件project
l 面向對象的軟件project是面向對象方法在軟件project領域的全面應用。它包含:
– 面向對象的分析(OOA)
– 面向對象的設計(OOD)
– 面向對象的編程(OOP)
– 面向對象的測試(OOT)
– 面向對象的軟件維護(OOSM)
總結:
面向過程程序設計:數據結構 + 算法
主要解決科學計算問題,用戶需求簡單而固定
特色:
分析解決這個問題所需要的步驟
利用函數實現各個步驟
依次調用函數解決這個問題
問題:
軟件可重用性差
軟件可維護性差
構建的軟件沒法知足用戶需求
面向對象程序設計:由現實世界創建軟件模型
將現實世界中的事物直接映射到程序中,可直接知足用戶需求
特色:
直接分析用戶需求中涉及的各個實體
在代碼中描寫敘述現實世界中的實體
在代碼中關聯各個實體協同工做解決這個問題
優點:
構建的軟件能夠適應用戶需求的不斷變化
直接利用面向過程方法的優點而避開其劣勢
C語言是在實踐的過程當中逐步無缺起來的 沒有深思熟慮的設計過程 使用時存在很是多「灰色地帶」 殘留量過多低級語言的特徵 直接利用指針進行內存操做 |
C語言的目標是高效 終於程序運行效率的高效 |
當面向過程方法論暴露愈來愈多的缺陷的時候,業界開始考慮在project項目中引入面向對象的設計方法。而第一個需要解決的問題就是:高效的面嚮對象語言,並且能夠兼容已經存在的代碼。 C語言 + 面向對象方法論===》Objective C /C++ |
|
C語言和C++並不是對立的競爭關係 C++是C語言的增強。是一種更好的C語言 C++是以C語言爲基礎的。並且全然兼容C語言的特性 |
學習C++並不會影響原有的C語言知識,相反會依據加深對C的認知。 學習C++可以接觸到不少其它的軟件設計方法,並帶來不少其它的機會。 1) C++是一種更強大的C,經過學習C++能夠掌握不少其它的軟件設計方法 2) C++是Java/C#/D等現代開發語言的基礎,學習C++後能夠高速掌握這些語言 3)C++是各大知名軟件企業挑選人才的標準之中的一個 |
所謂namespace,是指標識符的各類可見範圍。
C++標準程序庫中的所有標識符都被定義於一個名爲std的namespace中。
一 :<iostream>和<iostream.h>格式不同。前者沒有後綴,實際上,在你的編譯器include目錄裏面可以看到,兩者是兩個文件。打開文件就會發現,裏面的代碼是不同的。後綴爲.h的頭文件c++標準已經明白提出不支持了,早些的實現將標準庫功能定義在全局空間裏,聲明在帶.h後綴的頭文件中,c++標準爲了和C差異開,也爲了正確使用命名空間。規定頭文件不使用後綴.h。所以,
1)當使用<iostream.h>時,至關於在c中調用庫函數,使用的是全局命名空間,也就是早期的c++實現;
2)當使用<iostream>的時候。該頭文件未定義全局命名空間,必須使用namespace std;這樣才幹正確使用cout。
二: 由於namespace的概念,使用C++標準程序庫的不論什麼標識符時。可以有三種選擇:
一、直接指定標識符。好比std::ostream而不是ostream。完整語句例如如下:std::cout << std::hex << 3.4 << std::endl;
二、使用usingkeyword。 usingstd::cout; using std::endl; using std::cin; 以上程序可以寫成 cout<< std::hex << 3.4 << endl;
三、最方便的就是使用usingnamespace std; 好比: using namespace std;這樣命名空間std內定義的所有標識符都有效(曝光)。
就好像它們被聲明爲全局變量同樣。那麼以上語句可以例如如下寫: cout <<hex << 3.4 << endl;因爲標準庫很是的龐大,因此程序猿在選擇的類的名稱或函數名 時就很是有可能和標準庫中的某個名字一樣。
因此爲了不這樣的狀況所形成的名字衝突,就把標準庫中的一切都被放在名字空間std中。但這又會帶來了一個新問題。
無數原有的C++代碼都依賴於使用了多年的僞標準庫中的功能,他們都是在全局空間下的。因此就有了<iostream.h> 和<iostream>等等這種頭文件。一個是爲了兼容曾經的C++代碼。一個是爲了支持新的標準。命名空間std封裝的是標準程序庫的名稱,標準程序庫爲了和曾經的頭文件差異,通常不加".h"
/* 在C++中。名稱(name)可以是符號常量、變量、宏、函數、結構、枚舉、類和對象等等。爲了不,在大規模程序的設計中,以及在程序猿使用各類各樣的C++庫時,這些標識符的命名發生衝突。 標準C++引入了keywordnamespace(命名空間/名字空間/名稱空間/名域)。可以更好地控制標識符的做用域。 */ |
/* std是c++標準命名空間,c++標準程序庫中的所有標識符都被定義在std中,比方標準庫中的類iostream、vector 等都定義在該命名空間中,使用時要加上using聲明(using namespace std) 或using指示(如std::string、 std::vector<int>). */ |
/* C中的命名空間 在C語言中僅僅有一個全局做用域 C語言中所有的全局標識符共享同一個做用域 標識符之間可能發生衝突 C++中提出了命名空間的概念 命名空間將全局做用域分紅不一樣的部分 不一樣命名空間中的標識符可以同名而不會發生衝突 命名空間可以相互嵌套 全局做用域也叫默認命名空間 */ |
/* C++命名空間的定義: namespace name { … } */ |
/* C++命名空間的使用: 使用整個命名空間:using namespace name; 使用命名空間中的變量:using name::variable; 使用默認命名空間中的變量:::variable 默認狀況下可以直接使用默 認命名空間中的所有標識符 */ |
namespace NameSpaceA { int a = 0; }
namespace NameSpaceB { int a = 1;
namespace NameSpaceC { struct Teacher { char name[10]; int age; }; } }
int main() { using namespace NameSpaceA; using NameSpaceB::NameSpaceC::Teacher;
printf("a = %d\n", a); printf("a = %d\n", NameSpaceB::a);
NameSpaceB::NameSpaceC::Teacher t2 Teacher t1 = {"aaa", 3};
printf("t1.name = %s\n", t1.name); printf("t1.age = %d\n", t1.age);
system("pause"); return 0; }
|
1) 當使用<iostream>的時候。該頭文件未定義全局命名空間。必須使用namespace std;這樣才幹正確使用cout。
若不引入using namespace std ,需要這樣作。std::cout。
2) c++標準爲了和C差異開。也爲了正確使用命名空間,規定頭文件不使用後綴.h。
3) C++命名空間的定義: namespacename { … }
4) using namespace NameSpaceA;
5) namespce定義可嵌套。
#include "iostream" using namespace std;
//C語言中的變量都必須在做用域開始的位置定義! 。 //C++中更強調語言的「有用性」,所有的變量都可以在需要使用時再定義。
int main11() { int i = 0;
printf("ddd"); int k; system("pause"); return 0; } |
//registerkeyword 請求編譯器讓變量a直接放在寄存器裏面。速度快 //在c語言中 register修飾的變量 不能取地址,但是在c++裏面作了內容
/* //1 registerkeyword的變化 registerkeyword請求「編譯器」將局部變量存儲於寄存器中 C語言中沒法取得register變量地址 在C++中依舊支持registerkeyword C++編譯器有本身的優化方式,不使用register也可能作優化 C++中可以取得register變量的地址
//2 C++編譯器發現程序中需要取register變量的地址時,register對變量的聲明變得無效。
//3 早期C語言編譯器不會對代碼進行優化。所以register變量是一個很是好的補充。
*/ |
int main22() { register int a = 0;
printf("&a = %x\n", &a);
system("pause"); return 0; } |
其它補充:請閱讀《registerkeyword常識課外閱讀.docx》
/* 在C語言中,反覆定義多個同名的全局變量是合法的 在C++中,不一樣意定義多個同名的全局變量 C語言中多個同名的全局變量終於會被連接到全局數據區的同一個地址空間上 int g_var; int g_var = 1;
C++直接拒絕這樣的二義性的作法。 */ |
int main(int argc, char *argv[]) { printf("g_var = %d\n", g_var); return 0; } |
struct類型的增強: C語言的struct定義了一組變量的集合,C編譯器並不以爲這是一種新的類型 C++中的struct是一個新類型的定義聲明 |
struct Student { char name[100]; int age; };
int main(int argc, char *argv[]) { Student s1 = {"wang", 1}; Student s2 = {"wang2", 2}; return 0; } |
/* C++中所有的變量和函數都必須有類型 C語言中的默認類型在C++中是不合法的
函數f的返回值是什麼類型,參數又是什麼類型? 函數g可以接受多少個參數? */
//更換成.cpp試試
f(i) { printf("i = %d\n", i);
}
g() { return 5; }
int main(int argc, char *argv[]) {
f(10);
printf("g() = %d\n", g(1, 2, 3, 4, 5));
getchar(); return 0; } |
總結:
/*
在C語言中
intf( )。表示返回值爲int,接受隨意參數的函數
intf(void);表示返回值爲int的無參函數
在C++中
intf( );和int f(void)具備一樣的意義,都表示返回值爲int的無參函數
*/
C++更增強調類型。隨意的程序元素都必須顯示指明類型
4.2-4.6屬於語法級別的加強。
/* C++中的布爾類型 C++在C語言的基本類型系統之上添加了bool C++中的bool可取的值僅僅有true和false 理論上bool僅僅佔用一個字節, 假設多個bool變量定義在一塊兒。可能會各佔一個bit。這取決於編譯器的實現
true表明真值,編譯器內部用1來表示 false表明非真值,編譯器內部用0來表示
bool類型僅僅有true(非0)和false(0)兩個值 C++編譯器會在賦值時將非0值轉換爲true,0值轉換爲false */ |
int main(int argc, char *argv[]) { int a; bool b = true; printf("b = %d, sizeof(b) = %d\n", b, sizeof(b));
b = 4; a = b; printf("a = %d, b = %d\n", a, b);
b = -4; a = b; printf("a = %d, b = %d\n", a, b);
a = 10; b = a; printf("a = %d, b = %d\n", a, b);
b = 0; printf("b = %d\n", b);
system("pause"); return 0; } |
int main() { int a = 10; int b = 20;
//返回一個最小數 並且給最小數賦值成3 //三目運算符是一個表達式 。表達式不可能作左值 (a < b ? a : b )= 30;
printf("a = %d, b = %d\n", a, b);
system("pause");
return 0; } |
1)C語言返回變量的值 C++語言是返回變量自己
C語言中的三目運算符返回的是變量值。不能做爲左值使用
C++中的三目運算符可直接返回變量自己,所以可以出現在程序的不論什麼地方
2)注意:三目運算符可能返回的值中假設有一個是常量值,則不能做爲左值使用
(a < b ? 1 :b )= 30;
3)C語言怎樣支持相似C++的特性呢?
====>當左值的條件:要有內存空間;C++編譯器幫助程序猿取了一個地址而已
思考:怎樣讓C中的三目運算法當左值呢?
int main() { const int a; int const b;
const int *c; int * const d; const int * const e ;
return 0; }
Int func1(const ) 0基礎理解:const是定義常量==》const意味着僅僅讀 |
含義: //第一個第二個意思同樣 表明一個常整形數 //第三個 c是一個指向常整形數的指針(所指向的內存數據不能被改動,但是自己可以改動) //第四個 d 常指針(指針變量不能被改動。但是它所指向內存空間可以被改動) //第五個 e一個指向常整形的常指針(指針和它所指向的內存空間,均不能被改動) |
Const優勢 //合理的利用const, //1指針作函數參數。可以有效的提升代碼可讀性,下降bug。 //2清楚的分清參數的輸入和輸出特性 |
int setTeacher_err( const Teacher *p) Const改動形參的時候。在利用形參不能改動指針所向的內存空間 |
int main() { const int a = 10; int *p = (int*)&a; printf("a===>%d\n", a); *p = 11; printf("a===>%d\n", a);
printf("Hello......\n"); return 0; } |
解釋: C++編譯器對const常量的處理 當遇見常量聲明時。在符號表中放入常量 =è問題:那有怎樣解釋取地址 編譯過程當中若發現使用常量則直接以符號表中的值替換 編譯過程當中若發現對const使用了extern或者&操做符。則給相應的常量分配存儲空間(兼容C) ?聯想: int &a = 1(err) & const int &a = 10(ok)? |
C++中const符號表原理圖 |
注意: C++編譯器儘管可能爲const常量分配空間。但不會使用其存儲空間中的值。 |
結論: C語言中的const變量 C語言中const變量是僅僅讀變量,有本身的存儲空間 C++中的const常量 可能分配存儲空間,也可能不分配存儲空間 當const常量爲全局,並且需要在其餘文件裏使用 當使用&操做符取const常量的地址
|
//練習 解釋爲何 //#define N 10 int main() { const int a = 1; const int b = 2; int array[a + b ] = {0}; int i = 0;
for(i=0; i<(a+b); i++) { printf("array[%d] = %d\n", i, array[i]); }
getchar();
return 0; } |
C++中的const修飾的,是一個真正的常量,而不是C中變量(僅僅讀)。在const修飾的常量編譯期間,就已經肯定下來了。 |
對照加深 C++中的const常量相似於宏定義 const int c = 5; ≈ #define c 5 C++中的const常量與宏定義不一樣 const常量是由編譯器處理的,提供類型檢查和做用域檢查 宏定義由預處理器處理,單純的文本替換 |
//在func1定義a,在func2中能使用嗎? //在func1中定義的b,在func2中能使用嗎? |
練習 void fun1() { #define a 10 const int b = 20; //#undef a # undef }
void fun2() { printf("a = %d\n", a); //printf("b = %d\n", b); }
int main() { fun1(); fun2(); return 0; }
|
C語言中的const變量
C語言中const變量是僅僅讀變量,有本身的存儲空間
C++中的const常量
可能分配存儲空間,也可能不分配存儲空間
當const常量爲全局。並且需要在其餘文件裏使用,會分配存儲空間
當使用&操做符。取const常量的地址時。會分配存儲空間
當const int &a = 10; const修飾引用時。也會分配存儲空間
變量名回想
變量名實質上是一段連續存儲空間的別名。是一個標號(門牌號)
程序中經過變量來申請並命名內存空間
經過變量的名字可以使用存儲空間
問題1:對一段連續的內存空間僅僅能取一個別名嗎?
a) 在C++中新添加了引用的概念
b) 引用可以看做一個已定義變量的別名
c) 引用的語法:Type& name = var;
d) 引用作函數參數那?(引用做爲函數參數聲明時不進行初始化)
void main01() { int a = 10; //c編譯器分配4個字節內存。。。a內存空間的別名 int &b = a; //b就是a的別名。 。。 a =11; //直接賦值 { int *p = &a; *p = 12; printf("a %d \n",a); } b = 14; printf("a:%d b:%d", a, b); system("pause"); } |
屬於C++編譯器對C的擴展
問題:C中可以編譯經過嗎? int main() { int a = 0; int &b = a; //int * const b = &a b = 11; //*b = 11;
return 0; } |
結論:請不要用C的語法考慮 b=11 |
普通引用在聲明時必須用其餘的變量進行初始化, 引用做爲函數參數聲明時不進行初始化 |
//05複雜數據類型 的引用 struct Teacher { char name[64]; int age ; };
void printfT(Teacher *pT) { cout<<pT->age<<endl; }
//pT是t1的別名 ,至關於改動了t1 void printfT2(Teacher &pT) { //cout<<pT.age<<endl; pT.age = 33; }
//pT和t1的是兩個不一樣的變量 void printfT3(Teacher pT) { cout<<pT.age<<endl; pT.age = 45; //僅僅會改動pT變量 ,不會改動t1變量 } void main() { Teacher t1; t1.age = 35;
printfT(&t1);
printfT2(t1); //pT是t1的別名 printf("t1.age:%d \n", t1.age); //33
printfT3(t1) ;// pT是形參 ,t1 copy一份數據 給pT //---> pT = t1 printf("t1.age:%d \n", t1.age); //35
cout<<"hello..."<<endl; system("pause"); return ; } |
1)引用做爲其餘變量的別名而存在,所以在一些場合可以取代指針 2)引用相對於指針來講具備更好的可讀性和有用性 |
|
思考1:C++編譯器背後作了什麼工做?
int main() { int a = 10; int &b = a; //b是a的別名。請問c++編譯器後面作了什麼工做? b = 11; cout<<"b--->"<<a<<endl; printf("a:%d\n", a); printf("b:%d\n", b); printf("&a:%d\n", &a); printf("&b:%d\n", &b); //請思考:對同一內存空間可以取好幾個名字嗎? system("pause"); return 0; } |
單獨定義的引用時,必須初始化。說明很是像一個常量 |
思考2:普通引用有本身的空間嗎?
struct Teacer { int &a; int &b; }; int main() { printf("sizeof(Teacher) %d\n",sizeof(Teacer)); system("pause"); return 0; } |
引用是一個有地址。引用是常量。。。。。 char *const p
|
1)引用在C++中的內部實現是一個常指針
Type& name çèType*const name
2)C++編譯器在編譯過程當中使用常指針做爲引用的內部實現,所以引用所佔用的空間大小與指針一樣。
3)從使用的角度。引用會讓人誤會其僅僅是一個別名,沒有本身的存儲空間。這是C++爲了有用性而作出的細節隱藏
Int main()
{
int x = 10;
func(x);
}
4) 請細緻對照間接賦值成立的三個條件
1定義兩個變量(一個實參一個形參)
2創建關聯實參取地址傳給形參
3*p形參去間接的改動實參的值
1)引用在實現上。僅僅只是是把:間接賦值成立的三個條件的後兩步和二爲一
//當實參傳給形參引用的時候,僅僅只是是c++編譯器幫咱們程序猿手工取了一個實參地址,傳給了形參引用(常量指針)
2)當咱們使用引用語法的時,咱們不去關心編譯器引用是怎麼作的
當咱們分析奇怪的語法現象的時,咱們纔去考慮c++編譯器是怎麼作的
C++引用使用時的難點:
當函數返回值爲引用時
若返回棧變量
不能成爲其餘引用的初始值
不能做爲左值使用
若返回靜態變量或全局變量
可以成爲其它引用的初始值
就能夠做爲右值使用,也可做爲左值使用
C++鏈式編程中。經常用到引用,運算符重載專題
返回值是基礎類型。當引用
int getAA1() { int a; a = 10; return a; }
//基礎類型a返回的時候。也會有一個副本 int& getAA2() { int a; a = 10; return a; }
int* getAA3() { int a; a = 10; return &a; } |
返回值是static變量。當引用
//static修飾變量的時候,變量是一個狀態變量 int j() { static int a = 10; a ++; printf("a:%d \n", a); return a;
}
int& j1() { static int a = 10; a ++; printf("a:%d \n", a); return a; }
int *j2() { static int a = 10; a ++; printf("a:%d \n", a); return &a; }
void main22() { // j()的運算結果是一個數值,沒有內存地址。不能當左值。 。 。 。。 //11 = 100; //*(a>b?&a:&b) = 111; //當被調用的函數當左值的時候。必須返回一個引用。。。 。 。 j1() = 100; //編譯器幫咱們打造了環境 j1(); *(j2()) = 200; //至關於咱們程序猿手工的打造 作左值的條件 j2(); system("pause"); } |
返回值是形參,當引用
int g1(int *p) { *p = 100; return *p; }
int& g2(int *p) // { *p = 100; return *p; }
//當咱們使用引用語法的時候 ,咱們不去關心編譯器引用是怎麼作的 //當咱們分析亂碼這樣的現象的時候,咱們纔去考慮c++編譯器是怎麼作的。 。 。。 void main23() { int a1 = 10; a1 = g2(&a1);
int &a2 = g2(&a1); //用引用去接受函數的返回值,是否是亂碼,關鍵是看返回的內存空間是否是被編譯器回收了。。。。 printf("a1:%d \n", a1); printf("a2:%d \n", a2);
system("pause"); } |
|
返回值非基礎類型
struct Teachar
{
charname[64];
intage;
};
//假設返回引用不是基礎類型,是一個類。那麼狀況很賦值。。涉及到copy構造函數和=操做重載,拋磚。。。。
struct Teachar { char name[64]; int age; }; //假設返回引用不是基礎類型,是一個類,那麼狀況很賦值。。涉及到copy構造函數和=操做重載,拋磚。。。。
struct Teachar & OpTeacher(struct Teachar &t1) {
} |
#include "iostream" using namespace std;
struct Teacher { char name[64]; int age; };
int getTe(Teacher **myp ) { Teacher *p = (Teacher *)malloc(sizeof(Teacher));
if (p ==NULL) { return -1; } memset(p, 0, sizeof(Teacher)); p->age = 33;
*myp = p; // return 0; }
//指針的引用而已 int getTe2(Teacher* &myp) { myp = (Teacher *)malloc(sizeof(Teacher)); myp->age = 34;
return 0; }
void main333() { Teacher *p = NULL; //getTe(&p); getTe2(p);
printf("age:%d \n", p->age); system("pause"); } |
如下開始進入const引用難點
思考cost int &a = b PK const int &a = 10; ????問題:const引用, |
在C++中可以聲明const引用 const Type& name = var; const引用讓變量擁有僅僅讀屬性 |
案例1: int main() { int a = 10; const int &b = a;
//int *p = (int *)&b; b = 11; //err //*p = 11; //僅僅能用指針來改變了
cout<<"b--->"<<a<<endl; printf("a:%d\n", a); printf("b:%d\n", b); printf("&a:%d\n", &a); printf("&b:%d\n", &b); system("pause"); return 0; } |
案例2: void main41() { int a = 10;
const int &b = a; //const引用使用變量a初始化 a = 11; //b = 12; //經過引用改動a,對不起改動不了 system("pause"); }
struct Teacher1 { char name[64]; int age; };
void printTe2(const Teacher1 *const pt) {
}
//const引用讓變量(所指內存空間)擁有僅僅讀屬性 void printTe(const Teacher1 &t) { //t.age = 11; } void main42() { Teacher1 t1; t1.age = 33; printTe(t1); system("pause"); } |
思考: 一、用變量對const引用初始化。const引用分配內存空間了嗎? 二、用常量對const引用初始化,const引用分配內存空間了嗎? |
void main() { const int b = 10; printf("b:%d", &b);
//int &a1 = 19; 假設不加const編譯失敗 const int &a = 19; printf("&a:%d \n", &a);
system("pause"); } |
void main() { //普通引用 int a = 10; int &b = a; //常量引用:讓變量引用僅僅讀屬性 const int &c = a;
//常量引用初始化分爲兩種 //1 用變量 初始化 常量引用 { int x = 20; const int& y = x; printf("y:%d \n", y); }
//2 用常量 初始化 常量引用 { //int &m = 10; //引用是內存空間的別名 字面量10沒有內存空間 沒有方法作引用 const int &m = 10; } cout<<"hello..."<<endl; system("pause"); return ; } |
1)Const &int e 至關於 const int * conste
2)普通引用 至關於 int *const e1
3)當使用常量(字面量)對const引用進行初始化時。C++編譯器會爲常量值分配空間,並將引用名做爲這段空間的別名
4)使用字面量對const引用初始化後,將生成一個僅僅讀變量
興許課程介紹
int& j() { static int a = 0; return a; }
int& g() { int a = 0; return a; }
int main() { int a = g(); int& b = g(); j() = 10; printf("a = %d\n", a); printf("b = %d\n", b); printf("f() = %d\n", f()); system("pause"); return 0; } |
C++中的const常量可以替代宏常數定義,如: const int A = 3; #define A 3 C++中是否有解決方式替代宏代碼片斷呢?(替代宏代碼片斷就可以避免宏的反作用! ) |
C++中推薦使用內聯函數替代宏代碼片斷 C++中使用inlinekeyword聲明內聯函數 |
內聯函數聲明時inlinekeyword必須和函數定義結合在一塊兒,不然編譯器會直接忽略內聯請求。 //宏替換和函數調用差異 |
#include "iostream" using namespace std; #define MYFUNC(a, b) ((a) < (b) ? (a) : (b))
inline int myfunc(int a, int b) { return a < b ? a : b; }
int main() { int a = 1; int b = 3; //int c = myfunc(++a, b); //頭疼系統 int c = MYFUNC(++a, b);
printf("a = %d\n", a); printf("b = %d\n", b); printf("c = %d\n", c);
system("pause"); return 0; } |
說明1: 必須inline int myfunc(int a, int b)和函數體的實現,寫在一塊 |
說明2 |
C++編譯器可以將一個函數進行內聯編譯 被C++編譯器內聯編譯的函數叫作內聯函數 內聯函數在終於生成的代碼中是未定義的 C++編譯器直接將函數體插入在函數調用的地方 內聯函數沒有普通函數調用時的額外開銷(壓棧,跳轉,返回) |
說明3:C++編譯器不必定準許函數的內聯請求。 說明4 內聯函數是一種特殊的函數。具備普通函數的特徵(參數檢查,返回類型等) 內聯函數是對編譯器的一種請求。所以編譯器可能拒絕這樣的請求 內聯函數由 編譯器處理。直接將編譯後的函數體插入調用的地方 宏代碼片斷 由預處理器處理。 進行簡單的文本替換,沒有不論什麼編譯過程 |
說明5: 現代C++編譯器能夠進行編譯優化,所以一些函數即便沒有inline聲明,也可能被編譯器內聯編譯 另外,一些現代C++編譯器提供了擴展語法,能夠對函數進行強制內聯 如:g++中的__attribute__((always_inline))屬性 |
說明6: C++中內聯編譯的限制: 不能存在不論什麼形式的循環語句 不能存在過多的條件推斷語句 函數體不能過於龐大 不能對函數進行取址操做 函數內聯聲明必須在調用語句以前 |
編譯器對於內聯函數的限制並不是絕對的。內聯函數相對於普通函數的優點僅僅是省去了函數調用時壓棧,跳轉和返回的開銷。 所以。當函數體的運行開銷遠大於壓棧,跳轉和返回所用的開銷時,那麼內聯將無心義。 |
結論: 2)inline僅僅是一種請求。編譯器不必定贊成這樣的請求 3)內聯函數省去了普通函數調用時壓棧,跳轉和返回的開銷 |
/*1 C++中可以在函數聲明時爲參數提供一個默認值。 當函數調用時沒有指定這個參數的值,編譯器會本身主動用默認值取代 */ |
void myPrint(int x = 3) { printf("x:%d", x); } /*2 函數默認參數的規則 僅僅有參數列表後面部分的參數才幹夠提供默認參數值 一旦在一個函數調用中開始使用默認參數值。那麼這個參數後的所有參數都必須使用默認參數值 */
|
//默認參數 void printAB(int x = 3) { printf("x:%d\n", x); }
//在默認參數規則 ,假設默認參數出現。那麼右邊的都必須有默認參數 void printABC(int a, int b, int x = 3, int y=4, int z = 5) { printf("x:%d\n", x); } int main62(int argc, char *argv[]) { printAB(2); printAB(); system("pause"); return 0; } |
/* 函數佔位參數 佔位參數僅僅有參數類型聲明。而沒有參數名聲明 普通狀況下,在函數體內部沒法使用佔位參數 */ |
int func(int a, int b, int ) { return a + b; }
int main01() { //func(1, 2); //可以嗎? printf("func(1, 2, 3) = %d\n", func(1, 2, 3));
getchar(); return 0; }
|
/* 可以將佔位參數與默認參數結合起來使用 意義 爲之後程序的擴展留下線索 兼容C語言程序中可能出現的不規範寫法 */ //C++可以聲明佔位符參數,佔位符參數通常用於程序擴展和對C代碼的兼容 |
int func2(int a, int b, int = 0) { return a + b; } void main() { //假設默認參數和佔位參數在一塊兒,都能調用起來 func2(1, 2); func2(1, 2, 3); system("pause"); } |
結論://假設默認參數和佔位參數在一塊兒,都能調用起來 |
1 函數重載概念 函數重載(Function Overload) 用同一個函數名定義不一樣的函數 當函數名和不一樣的參數搭配時函數的含義不一樣
2 函數重載的推斷標準 /* 函數重載至少知足如下的一個條件: 參數個數不一樣 參數類型不一樣 參數順序不一樣 */ 3 函數返回值不是函數重載的推斷標準 實驗1:調用狀況分析;實驗2:推斷標準 |
//兩個難點:重載函數和默認函數參數混搭 重載函數和函數指針 /* int func(int x) { return x; }
int func(int a, int b) { return a + b; }
int func(const char* s) { return strlen(s); }
int main() { int c = 0;
c = func(1);
printf("c = %d\n", c);
c = func(1, 2);
printf("c = %d\n", c);
c = func("12345");
printf("c = %d\n", c);
printf("Press enter to continue ..."); getchar(); return 0; } */ |
/* 編譯器調用重載函數的準則 將所有同名函數做爲候選者 嘗試尋找可行的候選函數 精確匹配實參 經過默認參數能夠匹配實參 經過默認類型轉換匹配實參 匹配失敗 終於尋找到的可行候選函數不惟一,則出現二義性。編譯失敗。 沒法匹配所有候選者,函數沒有定義,編譯失敗。 */
/* 函數重載的注意事項 重載函數在本質上是相互獨立的不一樣函數(靜態鏈編) 重載函數的函數類型是不一樣的 函數返回值不能做爲函數重載的根據 函數重載是由函數名和參數列表決定的。 */ |
函數重載是發生在一個類中裏面 |
//當函數默認參數趕上函數重載會發生什麼 /* int func(int a, int b, int c = 0) { return a * b * c; }
int func(int a, int b) { return a + b; }
//1個參數的贊成嗎 int func(int a) { return a + b; }
int main() { int c = 0;
c = func(1, 2); // 存在二義性。調用失敗,編譯不能經過
printf("c = %d\n", c);
printf("Press enter to continue ..."); getchar(); return 0; } */
|
/* 函數重載與函數指針 當使用重載函數名對函數指針進行賦值時 依據重載規則挑選與函數指針參數列表一致的候選者 嚴格匹配候選者的函數類型與函數指針的函數類型 */ /* int func(int x) // int(int a) { return x; }
int func(int a, int b) { return a + b; }
int func(const char* s) { return strlen(s); }
typedef int(*PFUNC)(int a); // int(int a)
int main() { int c = 0; PFUNC p = func;
c = p(1);
printf("c = %d\n", c);
printf("Press enter to continue ..."); getchar(); return 0; } */ |
興許課程。
register:這個keyword請求編譯器儘量的將變量存在CPU內部寄存器中。而不是經過內存尋址訪問。以提升效率。注意是儘量。不是絕對。你想一想。一個CPU 的寄存器也就那麼幾個或幾十個,你要是定義了很是多很是多register 變量,它累死也可能不能全部把這些變量放入寄存器吧,輪也可能輪不到你。 1、皇帝身邊的小太監----寄存器 不知道什麼是寄存器?那見過太監沒有?沒有?事實上我也沒有。沒見過沒關係,見過就麻煩大了。^_^,你們都看過古裝戲,那些皇帝們要閱讀奏章的時候,大臣老是先將奏章交給皇帝旁邊的小太監,小太監呢再交給皇帝同志處理。這個小太監僅僅是個中轉站。並沒有別的功能。 那小太監就是咱們的寄存器了(這裏先不考慮CPU 的快速緩存區)。 數據從內存裏拿出來先放到寄存器,而後CPU 再從寄存器裏讀取數據來處理,處理完後相同把數據經過寄存器存放到內存裏。CPU不直接和內存打交道。這裏要說明的一點是:小太監是主動的從大臣手裏接過奏章,而後主動的交給皇帝同志。但寄存器沒這麼自覺,它從不主動幹什麼事。一個皇帝可能有好些小太監,那麼一個CPU 也可以有很是多寄存器,不一樣型號的CPU 擁有寄存器的數量不同。 寄存器事實上就是一塊一塊小的存儲空間,僅僅只是其存取速度要比內存快得多。進水樓臺先得月嘛。它離CPU 很是近。CPU 一伸手就拿到數據了,比在那麼大的一塊內存裏去尋找某個地址上的數據是否是快多了?那有人問既然它速度那麼快,那咱們的內存硬盤都改爲寄存器得了唄。我要說的是:你真有錢! |
2、舉例 register修飾符暗示編譯程序對應的變量將被頻繁地使用,假設可能的話,應將其保存在CPU的寄存器中。以加快其存儲速度。好比如下的內存塊拷貝代碼。 #ifdef NOSTRUCTASSIGN memcpy (d, s, l) { register char *d; register char *s; register int i; while (i--) *d++ = *s++; } #endif 但是使用register修飾符有幾點限制。 首先,register變量必須是能被CPU所接受的類型。這一般意味着register變量必須是一個單個的值。並且長度應該小於或者等於整型的長度。只是。有些機器的寄存器也能存放浮點數。 其次,因爲register變量可能不存放在內存中,因此不能用「&」來獲取register變量的地址。 由於寄存器的數量有限,而且某些寄存器僅僅能接受特定類型的數據(如指針和浮點數),所以真正起做用的register修飾符的數目和類型都依賴於執行程序的機器,而不論什麼多餘的register修飾符都將被編譯程序所忽略。 在某些狀況下,把變量保存在寄存器中反而會減小程序的執行速度。因爲被佔用的寄存器不能再用於其餘目的;或者變量被使用的次數不夠多,不足以裝入和存儲變量所帶來的額外開銷。 早期的C編譯程序不會把變量保存在寄存器中。除非你命令它這樣作。這時register修飾符是C語言的一種很是有價值的補充。然而,隨着編譯程序設計技術的進步,在決定那些變量應該被存到寄存器中時,現在的C編譯環境能比程序猿作出更好的決定。實際上,不少編譯程序都會忽略register修飾符。因爲雖然它全然合法。但它只是暗示而不是命令。 |
9 做業及強化訓練
1 複雜數據類型引用作函數參數
分析內存四區變化圖
2 代碼敲一遍
3 設計一個類, 求圓形的周長
4 設計一個學生類,屬性有姓名和學號,
可以給姓名和學號賦值
可以顯示學生的姓名和學號
C++學習技術路線及目標
研究C++編譯器管理類和對象的方法===》避免死角
c++編譯器對類對象的生命週期管理,對象建立、使用、銷燬
c++面向對象模型初探
c++面向對象多態原理探究
操做符重載
C++基礎課程學習完成之後,有沒有一個標準,來推斷本身有沒有入門。
面向抽象類(接口)編程
1)類、對象、成員變量、成員函數
2)面向對象三大概念
封裝、繼承、多態
3)編程實踐
類的定義和對象的定義。對象的使用
求圓形的面積
定義Teacher類,打印Teacher的信息(把類的聲明和類的實現分開)
1)封裝(Encapsulation)
A)封裝,是面向對象程序設計最主要的特性。把數據(屬性)和函數(操做)合成一個整體,這在計算機世界中是用類與對象實現的。
B)封裝,把客觀事物封裝成抽象的類,並且類可以把本身的數據和方法僅僅讓可信的類或者對象操做,對不可信的進行信息隱藏。
備註:有2層含義(把屬性和方法進行封裝對屬性和方法進行訪問控制)
C++中類的封裝
成員變量。C++中用於表示類屬性的變量
成員函數,C++中用於表示類行爲的函數
2)類成員的訪問控制
在C++中可以給成員變量和成員函數定義訪問級別
Public修飾成員變量和成員函數可以在類的內部和類的外部被訪問
Private修飾成員變量和成員函數僅僅能在類的內部被訪問
//類是把屬性和方法封裝 同一時候對信息進行訪問控制 //類的內部。類的外部 //咱們抽象了一個類。用類去定義對象 //類是一個數據類型。類是抽象的 //對象是一個詳細的變量。。 佔用內存空間。 class Circle { public: double r; double s; public: double getR() { a++; return r; } void setR(double val) { r = val; } public: double getS() //添加功能時,是在改動類, 改動類中的屬性或者是方法 { s = 3.14f*r*r; return s; } //private: int a; }; |
3)struct和classkeyword差異
在用struct定義類時。所有成員的默認屬性爲public
在用class定義類時,所有成員的默認屬性爲private
目標:面向過程向面向對象思想轉變
剛開始學習的人要細緻體會類和對象之間的關係。並經過適當練習鞏固和提升!
案例1 設計立方體類(cube),求出立方體的面積和體積
求兩個立方體。是否相等(全局函數和成員函數)
案例2 設計一個圓形類(AdvCircle),和一個點類(Point)。計算點在圓內部仍是圓外
即:求點和圓的關係(圓內和圓外)
案例3 對於第二個案例,類的聲明和類的實現分開
做業1:編寫C++程序完畢下面功能:
1)定義一個Point類。其屬性包含點的座標。提供計算兩點之間距離的方法;
2)定義一個圓形類。其屬性包含圓心和半徑。
3)建立兩個圓形對象。提示用戶輸入圓心座標和半徑,推斷兩個圓是否相交,並輸出結果。
做業2:設計並測試一個名爲Rectangle的矩形類,其屬性爲矩形的左下角與右上角兩個點的座標。依據座標能計算出矩形的面積
做業3:定義一個Tree類,有成員ages(樹齡),成員函數grow(int years)對ages加上years,age()顯示tree對象的ages的值。
前言
建立一個對象時,常常需要做某些初始化的工做,好比對數據成員賦初值。
注意,類的數據成員是不能在聲明類時初始化的。
爲了解決問題。C++編譯器提供了構造函數(constructor)來處理對象的初始化。構造函數是一種特殊的成員函數。與其它成員函數不一樣,不需要用戶來調用它。而是在創建對象時本身主動運行。
有關構造函數
1構造函數定義及調用
1)C++中的類可以定義與類名一樣的特殊成員函數。這樣的與類名一樣的成員函數叫作構造函數;
2)構造函數在定義時可以有參數;
3)沒有不論什麼返回類型的聲明。
2構造函數的調用
本身主動調用:普通狀況下C++編譯器會本身主動調用構造函數
手動調用:在一些狀況下則需要手工調用構造函數
有關析構函數
3)析構函數定義及調用
1)C++中的類可以定義一個特殊的成員函數清理對象,這個特殊的成員函數叫作析構函數
語法:~ClassName()
2)析構函數沒有參數也沒有不論什麼返回類型的聲明
3)析構函數在對象銷燬時本身主動被調用
4)析構函數調用機制
C++編譯器本身主動調用
代碼演示:dm01_構造函數的基礎.cpp
設計構造函數和析構函數的緣由
面向對象的思想是從生活中來。手機、車出廠時,是同樣的。
生活中存在的對象都是被初始化後才上市的。初始狀態是對象廣泛存在的一個狀態的
普通方案:
爲每個類都提供一個public的initialize函數。
對象建立後立刻調用initialize函數進行初始化。
優缺點分析
1)initialize僅僅是一個普通的函數,必須顯示的調用
2)一旦由於失誤的緣由,對象沒有初始化。那麼結果將是不肯定的
沒有初始化的對象,其內部成員變量的值是不定的
3)不能全然解決這個問題
//爲何對象需要初始化 有什麼樣的初始化方案 #include "iostream" using namespace std;
/* 思考爲何需要初始化 面向對象思想來自生活,手機、車、電子產品,出廠時有初始化 怎麼樣進行初始化?
方案1:顯示調用方法 缺點:易忘、麻煩。顯示調用init,不能全然解決這個問題 */ class Test21 { public: int m; int getM() const { return m; } void setM(int val) { m = val; }
int n; int getN() const { return n; } void setN(int val) { n = val; }
public: int init(int m,int n) { this->m = m; this->n = n; return 0; } protected: private: };
int main() { int rv =0; Test21 t1; //無參構造函數的調用方法 Test21 t2;
//t1.init(100, 200); //t2.init(300, 400); cout<<t1.getM()<<" "<<t1.getN()<<endl; cout<<t2.getM()<<" "<<t2.getN()<<endl;
//定義對象數組時,沒有機會進行顯示初始化 Test21 arr[3]; //Test arr_2[3] = {Test(1,3), Test(), Test()};
system("pause"); return rv; } |
C++編譯器給程序猿提供的對象初始化方案,高端大氣上檔次。
//有參數構造函數的三種調用方法 class Test { private: int a; int b;
public:
//無參數構造函數 Test() { ; }
//帶參數的構造函數 Test(int a, int b) { ; } //賦值構造函數 Test(const Test &obj) { ; }
public: void init(int _a, int _b) { a = _a; b = _b; } }; |
調用方法: Testt1, t2;
有參構造函數的三種調用方法
//有參數構造函數的三種調用方法 class Test5 { private: int a; public: //帶參數的構造函數 Test5(int a) { printf("\na:%d", a); } Test5(int a, int b) { printf("\na:%d b:%d", a, b); } public: };
int main55() { Test5 t1(10); //c++編譯器默認調用有參構造函數 括號法 Test5 t2 = (20, 10); //c++編譯器默認調用有參構造函數 等號法 Test5 t3 = Test5(30); //程序猿手工調用構造函數 產生了一個對象 直接調用構造構造函數法
system("pause"); return 0; } |
賦值構造函數的四種調用場景(調用時機)
第1和第2個調用場景
#include "iostream" using namespace std;
class AA { public: AA() //無參構造函數 默認構造函數 { cout<<"我是構造函數。本身主動被調用了"<<endl; } AA(int _a) //無參構造函數 默認構造函數 { a = _a; } AA(const AA &obj2) { cout<<"我也是構造函數,我是經過另一個對象obj2,來初始化我本身"<<endl; a = obj2.a + 10; } ~AA() { cout<<"我是析構函數,本身主動被調用了"<<endl; } void getA() { printf("a:%d \n", a); } protected: private: int a; }; //單獨搭建一個舞臺 void ObjPlay01() { AA a1; //變量定義
//賦值構造函數的第一個應用場景 //用對象1 初始化 對象2 AA a2 = a1; //定義變量並初始化 //初始化法
a2 = a1; //用a1來=號給a2 編譯器給咱們提供的淺copy }
|
第二個應用場景 //單獨搭建一個舞臺 void ObjPlay02() { AA a1(10); //變量定義
//賦值構造函數的第一個應用場景 //用對象1 初始化 對象2 AA a2(a1); //定義變量並初始化 //括號法
//a2 = a1; //用a1來=號給a2 編譯器給咱們提供的淺copy a2.getA(); } //注意:初始化操做 和 等號操做 是兩個不一樣的概念 |
第3個調用場景
#include "iostream" using namespace std;
class Location { public: Location( int xx = 0 , int yy = 0 ) { X = xx ; Y = yy ; cout << "Constructor Object.\n" ; } Location( const Location & p ) //拷貝構造函數 { X = p.X ; Y = p.Y ; cout << "Copy_constructor called." << endl ; } ~Location() { cout << X << "," << Y << " Object destroyed." << endl ; } int GetX () { return X ; } int GetY () { return Y ; } private : int X , Y ; } ;
//alt + f8 排版 void f ( Location p ) { cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl ; }
void mainobjplay() { Location A ( 1, 2 ) ; //形參是一個元素,函數調用,會運行實參變量初始化形參變量 f ( A ) ; }
void main() { mainobjplay(); system("pause"); }
|
第4個調用場景
第四個應用場景 #include "iostream" using namespace std; class Location { public: Location( int xx = 0 , int yy = 0 ) { X = xx ; Y = yy ; cout << "Constructor Object.\n" ; } Location( const Location & p ) //複製構造函數 { X = p.X ; Y = p.Y ; cout << "Copy_constructor called." << endl ; } ~Location() { cout << X << "," << Y << " Object destroyed." << endl ; } int GetX () { return X ; } int GetY () { return Y ; } private : int X , Y ; } ;
//alt + f8 排版 void f ( Location p ) { cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl ; }
Location g() { Location A(1, 2); return A; }
//對象初始化操做 和 =等號操做 是兩個不一樣的概念 //匿名對象的去和留。關鍵看。返回時怎樣接 void mainobjplay() { //若返回的匿名對象。賦值給另一個同類型的對象。那麼匿名對象會被析構 //Location B; //B = g(); //用匿名對象賦值給B對象,而後匿名對象析構
//若返回的匿名對象,來初始化另一個同類型的對象,那麼匿名對象會直接轉成新的對象 Location B = g(); cout<<"傳智掃地僧測試"<<endl; }
void main() { mainobjplay(); system("pause"); } |
二個特殊的構造函數
1)默認無參構造函數
當類中未定義構造函數時,編譯器默認提供一個無參構造函數,並且其函數體爲空
2)默認拷貝構造函數
當類中未定義拷貝構造函數時。編譯器默認提供一個默認拷貝構造函數,簡單的進行成員變量的值複製
1)當類中未定義不論什麼一個構造函數時。c++編譯器會提供默認無參構造函數和默認拷貝構造函數
2)當類中定義了拷貝構造函數時,c++編譯器不會提供無參數構造函數
3) 當類中定義了隨意的非拷貝構造函數(即:當類中提供了有參構造函數或無參構造函數)。c++編譯器不會提供默認無參構造函數
4 )默認拷貝構造函數成員變量簡單賦值
總結:僅僅要你寫了構造函數,那麼你必須用。
構造析構階段性總結
1)構造函數是C++中用於初始化對象狀態的特殊函數
2)構造函數在對象建立時本身主動被調用
3)構造函數和普通成員函數都遵循重載規則
4)拷貝構造函數是對象正確初始化的重要保證
5)必要的時候,必須手工編寫拷貝構造函數
========》1個對象的初始化講完了,添加一個案例。
Ø 默認複製構造函數可以完畢對象的數據成員值簡單的複製
Ø 對象的數據資源是由指針指示的堆時,默認複製構造函數僅做指針值複製
深拷貝淺拷貝現象出現的緣由
顯示提供copy構造函數
顯示操做重載=號操做,不使用編譯器提供的淺copy
class Name { public: Name(const char *pname) { size = strlen(pname); pName = (char *)malloc(size + 1); strcpy(pName, pname); } Name(Name &obj) { //用obj來初始化本身 pName = (char *)malloc(obj.size + 1); strcpy(pName, obj.pName); size = obj.size; } ~Name() { cout<<"開始析構"<<endl; if (pName!=NULL) { free(pName); pName = NULL; size = 0; } }
void operator=(Name &obj3) { if (pName != NULL) { free(pName); pName = NULL; size = 0; } cout<<"測試有沒有調用我。 。。。"<<endl;
//用obj3來=本身 pName = (char *)malloc(obj3.size + 1); strcpy(pName, obj3.pName); size = obj3.size; }
protected: private: char *pName; int size; };
//對象的初始化 和 對象之間=號操做是兩個不一樣的概念 void playObj() { Name obj1("obj1....."); Name obj2 = obj1; //obj2建立並初始化
Name obj3("obj3...");
//重載=號操做符 obj2 = obj3; //=號操做
cout<<"業務操做。。。5000"<<endl;
} void main61() { playObj(); system("pause"); } |
1)對象初始化列表出現緣由
1.必須這樣作:
假設咱們有一個類成員,它自己是一個類或者是一個結構,而且這個成員它僅僅有一個帶參數的構造函數,沒有默認構造函數。這時要對這個類成員進行初始化,就必須調用這個類成員的帶參數的構造函數,
假設沒有初始化列表。那麼他將沒法完畢第一步,就會報錯。
二、類成員中如有const修飾,必須在對象初始化的時候,給const int m 賦值
當類成員中含有一個const對象時。或者是一個引用時。他們也必須要經過成員初始化列表進行初始化。
因爲這兩種對象要在聲明後當即初始化,而在構造函數中,作的是對他們的賦值,這樣是不被贊成的。
2)C++中提供初始化列表對成員變量進行初始化
語法規則
Constructor::Contructor() : m1(v1),m2(v1,v2), m3(v3)
{
// some other assignment operation
}
3)注意概念
初始化:被初始化的對象正在建立
賦值:被賦值的對象已經存在
4)注意:
成員變量的初始化順序與聲明的順序相關,與在初始化列表中的順序無關
初始化列表先於構造函數的函數體運行
/* 1 C++中提供了初始化列表對成員變量進行初始化 2 使用初始化列表出現緣由: 1.必須這樣作: 假設咱們有一個類成員。它自己是一個類或者是一個結構,而且這個成員它僅僅有一個帶參數的構造函數, 而沒有默認構造函數,這時要對這個類成員進行初始化,就必須調用這個類成員的帶參數的構造函數, 假設沒有初始化列表,那麼他將沒法完畢第一步,就會報錯。
二、類成員中如有const修飾,必須在對象初始化的時候,給const int m 賦值 當類成員中含有一個const對象時,或者是一個引用時,他們也必須要經過成員初始化列表進行初始化, 因爲這兩種對象要在聲明後當即初始化。而在構造函數中,作的是對他們的賦值,這樣是不被贊成的。 */
//總結 構造和析構的調用順序
#include "iostream" using namespace std;
class ABC { public: ABC(int a, int b, int c) { this->a = a; this->b = b; this->c = c; printf("a:%d,b:%d,c:%d \n", a, b, c); printf("ABC construct ..\n"); } ~ABC() { printf("a:%d,b:%d,c:%d \n", a, b, c); printf("~ABC() ..\n"); } protected: private: int a; int b; int c; };
class MyD { public: MyD():abc1(1,2,3),abc2(4,5,6),m(100) //MyD() { cout<<"MyD()"<<endl; } ~MyD() { cout<<"~MyD()"<<endl; }
protected: private: ABC abc1; //c++編譯器不知道怎樣構造abc1 ABC abc2; const int m; };
int run() { MyD myD; return 0; }
int main_dem03() {
run(); system("pause"); return 0; } |
構造函數與析構函數的調用順序
1)當類中有成員變量是其餘類的對象時。首先調用成員變量的構造函數,調用順序與聲明順序一樣;以後調用自身類的構造函數
2)析構函數的調用順序與相應的構造函數調用順序相反
經過訓練,把所學知識點都穿起來
demo10_構造析構練習強化.cpp (解說)
展現分析過程。注意賦值構函數的調用
demo10_構造析構練習強化.cpp
1) 匿名對象生命週期
2) 匿名對象的去和留
3) 構造中調用構造
demo11_匿名對象練習強化.cpp
構造函數中調用構造函數,是一個蹩腳的行爲。
1)在軟件開發過程當中,常常需要動態地分配和撤銷內存空間。好比對動態鏈表中結點的插入與刪除。在C語言中是利用庫函數malloc和free來分配和撤銷內存空間的。C++提供了較簡便而功能較強的運算符new和delete來代替malloc和free函數。
注意: new和delete是運算符。不是函數,所以運行效率高。
2)儘管爲了與C語言兼容,C++仍保留malloc和free函數,但建議用戶不用malloc和free函數,而用new和delete運算符。new運算符的樣例:
new int; //開闢一個存放整數的存儲空間。返回一個指向該存儲空間的地址(即指針)
new int(100); //開闢一個存放整數的空間,並指定該整數的初值爲100。返回一個指向該存儲空間的地址
new char[10]; //開闢一個存放字符數組(包含10個元素)的空間,返回首元素的地址
new int[5][4]; //開闢一個存放二維整型數組(大小爲5*4)的空間。返回首元素的地址
float *p=new float (3.14159); //開闢一個存放單精度數的空間,並指定該實數的初值爲//3.14159,將返回的該空間的地址賦給指針變量p
3)new和delete運算符使用的通常格式爲:
用new分配數組空間時不能指定初值。假設由於內存不足等緣由而沒法正常分配空間,則new會返回一個空指針NULL。用戶可以依據該指針的值推斷分配空間是否成功。
4) 應用舉例
使用類名定義的對象都是靜態的,在程序執行過程當中,對象所佔的空間是不能隨時釋放的。
但有時人們但願在需要用到對象時才創建對象,在不需要用該對象時就撤銷它,釋放它所佔的內存空間以供別的數據使用。這樣可提升內存空間的利用率。
C++中。可以用new運算符動態創建對象,用delete運算符撤銷對象
比方:
Box *pt; //定義一個指向Box類對象的指針變量pt
pt=new Box; //在pt中存放了新建對象的起始地址
在程序中就可以經過pt訪問這個新建的對象。如
cout<<pt->height; //輸出該對象的height成員
cout<<pt->volume( ); //調用該對象的volume函數,計算並輸出體積
C++還贊成在運行new時,對新創建的對象進行初始化。如
Box *pt=new Box(12,15,18);
這樣的寫法是把上面兩個語句(定義指針變量和用new創建新對象)合併爲一個語句,並指定初值。
這樣更精煉。
新對象中的height,width和length分別得到初值12,15,18。
調用對象既可以經過對象名,也可以經過指針。
在運行new運算時。假設內存量不足,沒法開闢所需的內存空間,眼下大多數C++編譯系統都使new返回一個0指針值。僅僅要檢測返回值是否爲0,就可推斷分配內存是否成功。
ANSI C++標準提出,在運行new出現問題時,就「拋出」一個「異常」。用戶可依據異常進行有關處理。
但C++標準仍然贊成在出現new故障時返回0指針值。當前,不一樣的編譯系統對new故障的處理方法是不一樣的。
在再也不需要使用由new創建的對象時,可以用delete運算符予以釋放。如
delete pt; //釋放pt指向的內存空間
這就撤銷了pt指向的對象。此後程序不能再使用該對象。
假設用一個指針變量pt前後指向不一樣的動態對象,應注意指針變量的當前指向,以避免刪錯了對象。在運行delete運算符時。在釋放內存空間以前,本身主動調用析構函數。完畢有關善後清理工做。
//1 mallocfree函數 ckeyword
// newdelete 操做符號 c++的keyword
//2 new 在堆上分配內存 delete
//分配基礎類型 、分配數組類型、分配對象
//3 new和malloc 深刻分析
混用測試、異同比較
結論: malloc不會調用類的構造函數
Free不會調用類的析構函數
思考:每個變量。擁有屬性。
有沒有一些屬性,歸所有對象擁有?
1)定義靜態成員變量
Ø keyword static 可以用於說明一個類的成員,
靜態成員提供了一個同類對象的共享機制
Ø 把一個類的成員說明爲 static 時。這個類無論有多少個對象被建立。這些對象共享這個 static 成員
Ø 靜態成員局部於類,它不是對象成員
好比:
#include<iostream>
using namespace std;
class counter
{
static int num ; //聲明與定義靜態數據成員
public :
void setnum ( int i ) { num = i ;} //成員函數訪問靜態數據成員
void shownum() { cout <<num << '\t' ; }
} ;
int counter :: num = 0 ;//聲明與定義靜態數據成員
void main ()
{ counter a , b ;
a.shownum() ; //調用成員函數訪問私有靜態數據成員
b.shownum() ;
a.setnum(10) ;
a.shownum() ;
b.shownum() ;
}
從結果可以看出。訪問的是同一個靜態數據成員
2)使用靜態成員變量
// 例5-14 使用公有靜態數據成員
#include<iostream.h>
class counter
{ public :
counter (int a) { mem = a; }
int mem; //公有數據成員
static int Smem ; //公有靜態數據成員
} ;
int counter :: Smem = 1 ; //初始值爲1
void main()
{ counter c(5);
int i ;
for( i = 0 ; i < 5 ; i ++ )
{ counter::Smem += i ;
cout << counter::Smem << '\t' ; //訪問靜態成員變量方法2
}
cout<<endl;
cout<<"c.Smem = "<<c.Smem<<endl; //訪問靜態成員變量方法1
cout<<"c.mem = "<<c.mem<<endl;
}
1)概念
Ø 靜態成員函數數冠以keywordstatic
Ø 靜態成員函數提供不依賴於類數據結構的共同操做。它沒有this指針
Ø 在類外調用靜態成員函數用「類名 :: 」做限定詞。或經過對象調用
2)案例
3)疑難問題:靜態成員函數中,不能使用普通變量。
//靜態成員變量屬於整個類的,分不清楚,是那個詳細對象的屬性。
前言
C++對象模型可以歸納爲下面2部分:
1. 語言中直接支持面向對象程序設計的部分,主要涉及如構造函數、析構函數、虛函數、繼承(單繼承、多繼承、虛繼承)、多態等等。
2. 對於各類支持的底層實現機制。
在c語言中,「數據」和「處理數據的操做(函數)」是分開來聲明的,也就是說。語言自己並無支持「數據和函數」之間的關聯性。
在c++中,經過抽象數據類型(abstractdata type,ADT)。在類中定義數據和函數,來實現數據和函數直接的綁定。
歸納來講,在C++類中有兩種成員數據:static、nonstatic;三種成員函數:static、nonstatic、virtual。
C++中的class從面向對象理論出發,將變量(屬性)和函數(方法)集中定義在一塊兒,用於描寫敘述現實世界中的類。從計算機的角度。程序依舊由數據段和代碼段構成。 C++編譯器怎樣完畢面向對象理論到計算機程序的轉化? 換句話:C++編譯器是怎樣管理類、對象、類和對象之間的關係 詳細的說:詳細對象調用類中的方法,那,c++編譯器是怎樣區分,是那個詳細的類,調用這種方法那? |
思考一下程序結果 |
#include "iostream"
using namespace std;
class C1 { public: int i; //4 int j; //4 int k; //4 protected: private: }; //12
class C2 { public: int i; //4 int j; //4 int k; //4
static int m; //4 public: int getK() const { return k; } //4 void setK(int val) { k = val; } //4
protected: private: }; //12 16 24
struct S1 { int i; int j; int k; }; //
struct S2 { int i; int j; int k; static int m; }; //
int main() { printf("c1:%d \n", sizeof(C1)); printf("c2:%d \n", sizeof(C2)); printf("s1:%d \n", sizeof(S1)); printf("s2:%d \n", sizeof(S2));
system("pause"); } |
|
經過上面的案例,咱們可以的得出: 1)C++類對象中的成員變量和成員函數是分開存儲的 成員變量: 普通成員變量:存儲於對象中。與struct變量有一樣的內存佈局和字節對齊方式 靜態成員變量:存儲於全局數據區中 成員函數:存儲於代碼段中。 問題出來了:很是多對象共用一塊代碼?代碼是怎樣區分詳細對象的那?
換句話說:int getK() const { return k; },代碼是怎樣區分。詳細obj一、obj二、obj3對象的k值?
|
2)C++編譯器對普通成員函數的內部處理 |
請細緻思考。並說出你的總結! |
一、C++類對象中的成員變量和成員函數是分開存儲的。C語言中的內存四區模型仍然有效。 2、C++中類的普通成員函數都隱式包括一個指向當前對象的this指針。 三、靜態成員函數、成員變量屬於類 靜態成員函數與普通成員函數的差異 靜態成員函數不包括指向詳細對象的指針 普通成員函數包括一個指向詳細對象的指針 |
實驗1:若類成員函數的形參和 類的屬性。名字一樣,經過this指針來解決。
實驗2:類的成員函數可經過const修飾,請問const修飾的是誰
一、把全局函數轉化成成員函數,經過this指針隱藏左操做數
Testadd(Test &t1, Test &t2)===》Test add( Test&t2)
二、把成員函數轉換成全局函數,多了一個參數
voidprintAB()===》void printAB(Test *pthis)
三、函數返回元素和返回引用
Test& add(Test &t2) //*this //函數返回引用
{
this->a = this->a +t2.getA();
this->b = this->b + t2.getB();
return*this; //*操做讓this指針回到元素狀態
}
Test add2(Test &t2)//*this //函數返回元素
{
//t3是局部變量
Test t3(this->a+t2.getA(),this->b + t2.getB()) ;
return t3;
}
voidadd3(Test &t2) //*this //函數返回元素
{
//t3是局部變量
Test t3(this->a+t2.getA(),this->b + t2.getB()) ;
//return t3;
}
好比
class A1 { public: A1() { a1 = 100; a2 = 200; } int getA1() { return this->a1; } //聲明一個友元函數 friend void setA1(A1 *p, int a1); //這個函數是這個類的好朋友
protected: private: int a1; int a2; };
void setA1(A1 *p, int a1) { p->a1 = a1; } void main() { A1 mya1; cout<<mya1.getA1()<<endl; setA1(&mya1, 300); //經過友元函數 改動A類的私有屬性 cout<<mya1.getA1()<<endl;
system("pause"); } |
Ø 若B類是A類的友員類。則B類的所有成員函數都是A類的友員函數
Ø 友員類一般設計爲一種對數據操做或類之間傳遞消息的輔助類
Ø 某商店經銷一種貨物。貨物購進和賣出時以箱爲單位。各箱的重量不同,所以。商店需要記錄眼下庫存的總重量。現在用C++模擬商店貨物購進和賣出的狀況。
#include "iostream" using namespace std;
class Goods { public : Goods ( int w) { weight = w ; total_weight += w ; } ~ Goods() { total_weight -= weight ; } int Weight() { return weight ; } ; static int TotalWeight() { return total_weight ; } Goods *next ; private : int weight ; static int total_weight ; } ;
int Goods::total_weight = 0 ;
//r尾部指針 void purchase( Goods * &f, Goods *& r, int w ) { Goods *p = new Goods(w) ; p -> next = NULL ; if ( f == NULL ) f = r = p ; else { r -> next = p ; r = r -> next ; } //尾部指針下移或新結點變成尾部結點 } void sale( Goods * & f , Goods * & r ) { if ( f == NULL ) { cout << "No any goods!\n" ; return ; } Goods *q = f ; f = f -> next ; delete q ; cout << "saled.\n" ; }
void main() { Goods * front = NULL , * rear = NULL ; int w ; int choice ; do { cout << "Please choice:\n" ; cout << "Key in 1 is purchase,\nKey in 2 is sale,\nKey in 0 is over.\n" ; cin >> choice ; switch ( choice ) // 操做選擇 { case 1 : // 鍵入1,購進1箱貨物 { cout << "Input weight: " ; cin >> w ; purchase( front, rear, w ) ; // 從表尾插入1個結點 break ; } case 2 : // 鍵入2,售出1箱貨物 { sale( front, rear ) ; break ; } // 從表頭刪除1個結點 case 0 : break ; // 鍵入0,結束 } cout << "Now total weight is:" << Goods::TotalWeight() << endl ; } while ( choice ) ; }
|
目標:解決實際問題,訓練構造函數、copy構造函數等,爲操做符重載作準備
數組類的測試
#include "iostream" #include "Array.h" using namespace std;
int main() { Array a1(10);
for(int i=0; i<a1.length(); i++) { a1.setData(i, i); }
for(int i=0; i<a1.length(); i++) { printf("array %d: %d\n", i, a1.getData(i)); }
Array a2 = a1;
for(int i=0; i<a2.length(); i++) { printf("array %d: %d\n", i, a2.getData(i)); }
system("pause"); return 0; } |
數組類的頭文件
#ifndef _MYARRAY_H_ #define _MYARRAY_H_
class Array { private: int mLength; int* mSpace;
public: Array(int length); Array(const Array& obj); int length(); void setData(int index, int value); int getData(int index); ~Array(); };
#endif |
Ø 類通常用keywordclass定義。
類是數據成員和成員函數的封裝。
類的實例稱爲對象。
Ø 結構類型用keywordstruct定義,是由不一樣類型數據組成的數據類型。
Ø 類成員由private, protected, public決定訪問特性。
public成員集稱爲接口。
Ø 構造函數在建立和初始化對象時本身主動調用。
析構函數則在對象做用域結束時本身主動調用。
Ø 重載構造函數和複製構造函數提供了建立對象的不一樣初始化方式。
Ø 靜態成員是局部於類的成員,提供一種同類對象的共享機制。
Ø 友員用keywordfriend聲明。友員是對類操做的一種輔助手段。一個類的友員可以訪問該類各類性質的成員。
Ø 鏈表是一種重要的動態數據結構,可以在程序執行時建立或撤消數據元素。
所謂重載,就是又一次賦予新的含義。
函數重載就是對一個已有的函數賦予新的含義,使之實現新功能。所以,一個函數名就可以用來表明不一樣功能的函數,也就是」一名多用」。
運算符也可以重載。實際上,咱們已經在不知不覺之中使用了運算符重載。好比,你們都已習慣於用加法運算符」+」對整數、單精度數和雙精度數進行加法運算,如5+8, 5.8 +3.67等。事實上計算機對整數、單精度數和雙精度數的加法操做過程是很是不一樣樣的,但由於C++已經對運算符」+」進行了重載,因此就能適用於int, float, doUble類型的運算。
又如」<<「是C++的位運算中的位移運算符(左移),但在輸出操做中又是與流對象cout 配合使用的流插入運算符,」>>「也是位移運算符(右移),但在輸入操做中又是與流對象 cin 配合使用的流提取運算符。這就是運算符重載(operator overloading)。C++系統對」<<「和」>>「進行了重載,用戶在不一樣的場合下使用它們時。做用是不一樣的。
對」<<「和」>>「的重載處理是放在頭文件stream中的。所以,假設要在程序中用」<< 「和」>>」做流插入運算符和流提取運算符,必須在本文件模塊中包括頭文件stream(固然還應當包括」using namespace std「)。
現在要討論的問題是:用戶是否能依據本身的需要對C++已提供的運算符進行重載,賦予它們新的含義。使之中的一個名多用。?
1爲何會用運算符重載機制
用複數類舉例
//Complex c3 =c1 + c2;
//緣由 Complex是用戶本身定義類型。編譯器根本不知道怎樣進行加減
//編譯器給提供了一種機制。讓用戶本身去完畢,本身定義類型的加減操做。
。
。
。。
//這個機制就是運算符重載機制
2 運算符重載的本質是一個函數
class Complex { public: int a; int b; friend Complex operator+(Complex &c1, Complex &c2); public: Complex(int a=0, int b=0) { this->a = a; this->b = b; }
public: void printCom() { cout<<a<<" + "<<b<<"i "<<endl; }
private: };
/* Complex myAdd(Complex &c1, Complex &c2) { Complex tmp(c1.a+ c2.a, c1.b + c2.b); return tmp; } */
Complex operator+(Complex &c1, Complex &c2) { Complex tmp(c1.a+ c2.a, c1.b + c2.b); return tmp; }
void main() { Complex c1(1, 2), c2(3, 4);
//Complex c3 = c1 + c2; //用戶本身定義類型 編譯器沒法讓變量相加 //Complex myAdd(Complex &c1, Complex &c2);
//1 普通函數 //Complex c3 = myAdd(c1, c2); //c3.printCom();
//2 operator+ 函數名稱 //Complex c3 = operator+(c1, c2); //c3.printCom();
//3 +替換 函數名 Complex c3 = c1 + c2; //思考C++編譯器怎樣支持操做符重載機制的 (依據類型) c3.printCom(); { int a =0, b = 0, c; //基礎類型C++編譯器知道怎樣加減 c = a +b; }
//4 把Complex類變成私有屬性 //友元函數的應用場景 //friend Complex operator+(Complex &c1, Complex &c2);
cout<<"hello..."<<endl; system("pause"); return ; } |
好比:
//全局函數 完畢 +操做符 重載
Complex operator+(Complex &c1, Complex&c2)
//類成員函數 完畢 -操做符 重載
Complex operator-(Complex &c2)
好比1:
//經過類成員函數完畢-操做符重載
//函數聲明 Complexoperator-(Complex &c2)
//函數調用分析
//用類成員函數實現-運算符重載
Complex c4 = c1 - c2;
c4.printCom();
//c1.operator-(c2);
好比2:
//經過全局函數方法完畢+操做符重載
//函數聲明 Complexoperator+(Complex &c1, Complex &c2)
//函數調用分析
int main()
{
Complex c1(1, 2), c2(3, 4);
//Complex c31 = operator+(c1,c2);
Complex c3 = c1 + c2;
c3.printCom();
}
好比3: 學員本身練習 實現 * /
好比3
//前置++操做符 用全局函數實現
Complex& operator++(Complex&c1)
{
c1.a ++;
c1.b ++;
return c1;
}
//調用方法
++c1 ; //=è需要寫出操做符重載函數原形
c1.printCom();
//運算符重載函數名定義
//首先認可操做符重載是一個函數 定義函數名èoperator++
//分析函數參數 依據左右操做數的個數,èoperator++(Complex &c1)
//分析函數返回值è Complex&operator++(Complex &c1) 返回它自身
好比4
//4.1前置—操做符 成員函數實現
Complex&operator--()
{
this->a--;
this->b--;
return *this;
}
//4.2調用方法
--c1;
c1.printCom();
//4.3前置—運算符重載函數名定義
//c1.operator--()
好比5
//5.1//後置++ 操做符用全局函數實現
Complex operator++(Complex &c1, int)
{
Complex tmp = c1;
c1.a++;
c1.b++;
return tmp;
}
//5.2 調用方法
c1 ++ ; //先使用後++
//5.3 後置++運算符重載函數名定義
Complex operator++(Complex&c1, int) //函數佔位參數 和 前置++ 相差異
好比6
//6.1 後置— 操做符 用類成員函數實現
Complexoperator--(int)
{
Complextmp = *this;
this->a--;
this->b--;
returntmp;
}
//6.2 調用方法
c1 ++ ; //先使用後++
//6.3 後置--運算符重載函數名定義
Complex operator--(int) //函數佔位參數和 前置-- 相差異
前置和後置運算符總結
C++中經過一個佔位參數來區分前置運算和後置運算
全局函數、類成員函數方法實現運算符重載步驟
1)要認可操做符重載是一個函數。寫出函數名稱operator+ ()
2)依據操做數,寫出函數參數
3)依據業務,無缺函數返回值(看函數是返回引用 仍是指針 元素),及實現函數業務
1)友元函數和成員函數選擇方法
Ø 當沒法改動左操做數的類時,使用全局函數進行重載
Ø =, [], ()和->操做符僅僅能經過成員函數進行重載
2)用友元函數重載 << >>操做符
Ø istream 和 ostream 是 C++ 的提早定義流類
Ø cin 是 istream 的對象,cout 是 ostream 的對象
Ø 運算符 << 由ostream 重載爲插入操做,用於輸出基本類型數據
Ø 運算符 >> 由 istream 重載爲提取操做,用於輸入基本類型數據
Ø 用友員函數重載 << 和 >> 。輸出和輸入用戶本身定義的數據類型
a)用全局函數方法實現 << 操做符
ostream& operator<<(ostream &out, Complex &c1)
{
//out<<"12345,生活真是苦"<<endl;
out<<c1.a<<"+ "<<c1.b<<"i "<<endl;
return out;
}
//調用方法
cout<<c1;
//鏈式編程支持
cout<<c1<<"abcc";
//cout.operator<<(c1).operator<<("abcd");
//函數返回值充當左值 需要返回一個引用
b)類成員函數方法沒法實現 <<操做符重載
//因拿到cout這個類的源代碼
//cout.operator<<(c1);
3) 友元函數重載操做符使用注意點
a) 友員函數重載運算符常用於運算符的左右操做數類型不一樣的狀況
b)其它
Ø 在第一個參數需要隱式轉換的情形下,使用友員函數重載運算符是正確的選擇
Ø 友員函數沒有 this 指針,所需操做數都必須在參數表顯式聲明,很是easy實現類型的隱式轉換
Ø C++中不能用友員函數重載的運算符有
= () [] ->
4 )友元函數案例vector類
#include <iostream> using namespace std;
//爲vector類重載流插入運算符和提取運算符 class vector { public : vector( int size =1 ) ; ~vector() ; int & operator[]( int i ) ; friend ostream & operator << ( ostream & output , vector & ) ; friend istream & operator >> ( istream & input, vector & ) ; private : int * v ; int len ; };
vector::vector( int size ) { if (size <= 0 || size > 100 ) { cout << "The size of " << size << " is null !\n" ; abort() ; } v = new int[ size ] ; len = size ; }
vector :: ~vector() { delete[] v ; len = 0 ; }
int &vector::operator[]( int i ) { if( i >=0 && i < len ) return v[ i ] ; cout << "The subscript " << i << " is outside !\n" ; abort() ; } ostream & operator << ( ostream & output, vector & ary ) { for(int i = 0 ; i < ary.len ; i ++ ) output << ary[ i ] << " " ; output << endl ; return output ; } istream & operator >> ( istream & input, vector & ary ) { for( int i = 0 ; i < ary.len ; i ++ ) input >> ary[ i ] ; return input ; }
void main() { int k ; cout << "Input the length of vector A :\n" ; cin >> k ; vector A( k ) ; cout << "Input the elements of vector A :\n" ; cin >> A ; cout << "Output the elements of vector A :\n" ; cout << A ; system("pause"); } |
C++編譯器是怎樣支持操做符重載機制的?
Ø 賦值運算符重載用於對象數據的複製
Ø operator= 必須重載爲成員函數
Ø 重載函數原型爲:
類型 & 類名 :: operator= ( const 類名 & ) ;
案例:無缺Name類,支持=號操做。
結論:
1//先釋放舊的內存
2返回一個引用
3=操做符 從右向左
//obj3 = obj1; // C++編譯器提供的 等號操做 也屬 淺拷貝 // obj4 = obj3 = obj1 //obj3.operator=(obj1)
Name& operator=(Name &obj1) { //1 先釋放obj3舊的內存 if (this->m_p != NULL) { delete[] m_p; m_len = 0; } //2 依據obj1分配內存大小 this->m_len = obj1.m_len; this->m_p = new char [m_len+1];
//3把obj1賦值給obj3 strcpy(m_p, obj1.m_p); return *this; }
|
重載[]和()運算符
Ø 運算符 [] 和 () 是二元運算符
Ø [] 和 () 僅僅能用成員函數重載。不能用友元函數重載
重載下標運算符 []
[] 運算符用於訪問數據對象的元素
重載格式 類型 類 ::operator[] ( 類型 ) ;
設 x 是類 X 的一個對象,則表達式
x [ y ]
可被解釋爲
x . operator [ ] ( y )
() 運算符用於函數調用
重載格式 類型 類 ::operator() ( 表達式表 ) ;
例1
設 x 是類 X 的一個對象,則表達式
x( arg1, arg2, … )
可被解釋爲
x. operator () (arg1, arg2, … )
案例:
//例2:用重載()運算符實現數學函數的抽象
#include <iostream>
class F
{public :
double operator( ) ( double x , double y ) ;
} ;
double F :: operator ( ) ( double x , double y )
{return x * x + y * y ; }
void main ( )
{
F f ;
f.getA();
cout << f (5.2 , 2.5 ) << endl ; // f .operator() (5.2, 2.5)
}
比較普通成員函數
//例3 用重載()運算符實現 pk 成員函數
#include <iostream.h>
class F
{public :
double memFun ( double x, double y ) ;
} ;
double F :: memFun ( double x, double y )
{return x * x + y * y ; }
void main ( )
{
F f ;
cout << f.memFun( 5.2 , 2.5 ) << endl ;
}
理論知識:
1)&&和||是C++中很特殊的操做符
2)&&和||內置實現了短路規則
3)操做符重載是靠函數重載來完畢的
4)操做數做爲函數參數傳遞
5)C++的函數參數都會被求值,沒法實現短路規則
#include <cstdlib> #include <iostream>
using namespace std;
class Test { int i; public: Test(int i) { this->i = i; }
Test operator+ (const Test& obj) { Test ret(0);
cout<<"運行+號重載函數"<<endl; ret.i = i + obj.i; return ret; }
bool operator&& (const Test& obj) { cout<<"運行&&重載函數"<<endl; return i && obj.i; } };
// && 從左向右 void main() { int a1 = 0; int a2 = 1;
cout<<"注意:&&操做符的結合順序是從左向右"<<endl;
if( a1 && (a1 + a2) ) { cout<<"有一個是假,則不在運行下一個表達式的計算"<<endl; }
Test t1 = 0; Test t2 = 1;
If ( t1 && (t1 + t2) ) {
=è T1.operator&&( t1 + t2) ) T1.operator&&( t1.operator+(t2) )
//t1 && t1.operator+(t2)
// t1.operator( t1.operator(t2) ) cout<<"兩個函數都被運行了,而且是先運行了+"<<endl; }
system("pause"); return ; } |
|
加入<< >>
構造函數要求
//C語言中 沒有字符串這樣的類型。是經過數組來模擬字符串
//C++中 咱們來設計一個字符串類 以零結尾的字符串
//若len爲0,表示空串
MyString a; //空串「」
MyString a(「dddd」);
MyString b = a;
b = 「aaaaaa」
b = a;
if (a > b)
if (a == b)
b[i] = ‘a’;
常用的操做符
<< >> != == > < =
//C語言中 沒有字符串這樣的類型,是經過數組來模擬字符串 //C++中 咱們來設計一個字符串 以零結尾的字符串
class MyString { friend ostream& operator<<(ostream &out, const MyString &s); public: //構造和析構 MyString(int len = 0); MyString(const char *p); MyString(const MyString& obj); ~MyString();
public: //操做符重載 MyString& operator=(const char *p); MyString& operator=(const MyString& obj); char& operator[](int index) const;
public: bool operator==(const char* p) const; bool operator!=(const char* p) const; bool operator==(const MyString& s) const; bool operator!=(const MyString& s) const;
public: //string to c char *c_str(); const char* c_str() const; int length() { return m_len; }
public: int operator<(const char *p); int operator>(const char *p);
int operator<(const MyString &s); int operator>(const MyString &s);
private: int m_len; char *m_p; }; |
1問題拋出
指針使用過程當中,經常會出現內存泄漏和內存屢次被釋放常
2 解決方式:好比:boost庫的智能指針
項目開發中,要求開發人員使用預先編寫的智能指針類對象取代C語言中的原生指針
3 智能指針思想
project中的智能指針是一個類模板
經過構造函數接管申請的內存
經過析構函數確保堆內存被及時釋放
經過重載指針運算符* 和 -> 來模擬指針的行爲
經過重載比較運算符 == 和 != 來模擬指針的比較
class Test { public: Test() { this->a = 10; } void printT() { cout<<a<<endl; }
private: int a; };
class MyTestPointer { public: public: MyTestPointer() { p = NULL; } MyTestPointer(Test* p) { this->p = p; } ~MyTestPointer() { delete p; } Test* operator->() { return p; } Test& operator*() { return *p; }
protected: Test *p; };
void main01_classp() { Test *p = new Test; p->printT(); delete p;
MyTestPointer myp = new Test; //構造函數 myp->printT(); //重載操做符 ->
}; |
class MyIntPointer { public: public: MyIntPointer() { p = NULL; } MyIntPointer(int* p) { this->p = p; } ~MyIntPointer() { delete p; } int* operator->() { return p; } int& operator*() { return *p; }
protected: int *p; };
void main02_intp() { int *p = new int(100); cout<<*p<<endl; delete p;
MyIntPointer myp = new int(200); cout<<*myp<<endl; //重載*操做符 }; |
總結
操做符重載是C++的強大特性之中的一個
操做符重載的本質是經過函數擴展操做符的語義
operatorkeyword是操做符重載的關鍵
friendkeyword可以對函數或類開發訪問權限
操做符重載遵循函數重載的規則
操做符重載可以直接使用類的成員函數實現
=, [], ()和->操做符僅僅能經過成員函數進行重載
++操做符經過一個int參數進行前置與後置的重載
C++中不要重載&&和||操做符
面向對象程序設計有4個主要特色:抽象、封裝、繼承和多態性。咱們已經解說了類和對象,瞭解了面向對象程序設計的兩個重要特徵一數據抽象與封裝,已經能夠設計出基於對象的程序。這是面向對象程序設計的基礎。
要較好地進行面向對象程序設計。還必須瞭解面向對象程序設計另外兩個重要特 徵——繼承性和多態性。本章主要介紹有關繼承的知識。多態性將在興許章節中解說。
繼承性是面向對象程序設計最重要的特徵。可以說,假設沒有掌握繼承性,就等於沒有掌握類和對象的精華。就是沒有掌握面向對象程序設計的真諦。
has-A,uses-A 和 is-A
has-A 包括關係。用以描寫敘述一個類由多個「部件類」構成。
實現has-A關係用類成員表示,即一個類中的數據成員是還有一種已經定義的類。
uses-A 一個類部分地使用還有一個類。
經過類之間成員函數的相互聯繫,定義友員或對象參數傳遞實現。
is-A 機制稱爲「繼承」。關係具備傳遞性,不具備對稱性。
萬事萬物中皆有繼承,是重要的現象
兩個案例:1)植物繼承圖;2)程序猿繼承圖
注意:C++中的繼承方式(public、private、protected)會影響子類的對外訪問屬性。
一、子類擁有父類的所有成員變量和成員函數
二、子類可以擁有父類沒有的方法和屬性
三、子類就是一種特殊的父類
四、子類對象可以看成父類對象使用
派生類繼承了基類的全部成員變量和成員方法(除了構造和析構以外的成員方法),但是這些成員的訪問屬性,在派生過程當中是可以調整的。
一、類成員訪問級別(public、private、protected)
二、思考:類成員的訪問級別僅僅有public和private是否足夠?
1)C++中的繼承方式會影響子類的對外訪問屬性
public繼承:父類成員在子類中保持原有訪問級別
private繼承:父類成員在子類中變爲private成員
protected繼承:父類中public成員會變成protected
父類中protected成員仍然爲protected
父類中private成員仍然爲private
2)private成員在子類中依舊存在,但是卻沒法訪問到。不論種方式繼承基類,派生類都不能直接使用基類的私有成員。
3)C++中子類對外訪問屬性表
|
父類成員訪問級別 |
|||
繼 承 方 式 |
|
public |
proteced |
private |
public |
public |
proteced |
private |
|
proteced |
proteced |
proteced |
private |
|
private |
private |
private |
Private |
4)繼承中的訪問控制
C++中的繼承方式(public、private、protected)會影響子類的對外訪問屬性
推斷某一句話,是否能被訪問
1)看調用語句。這句話寫在子類的內部、外部
2)看子類怎樣從父類繼承(public、private、protected)
3)看父類中的訪問級別(public、private、protected)
思考:怎樣恰當的使用public,protected和private爲成員聲明訪問級別?
一、需要被外界訪問的成員直接設置爲public
二、僅僅能在當前類中訪問的成員設置爲private
三、僅僅能在當前類和子類中訪問的成員設置爲protected,protected成員的訪問權限介於public和private之間。
練習:
public繼承不會改變父類對外訪問屬性。
private繼承會改變父類對外訪問屬性爲private;
protected繼承會部分改變父類對外訪問屬性。
結論:普通狀況下class B : public A
//類的繼承方式對子類對外訪問屬性影響
#include <cstdlib> #include <iostream>
using namespace std;
class A { private: int a; protected: int b; public: int c;
A() { a = 0; b = 0; c = 0; }
void set(int a, int b, int c) { this->a = a; this->b = b; this->c = c; } };
class B : public A { public: void print() { //cout<<"a = "<<a; //err cout<<"b = "<<b; cout<<"c = "<<endl; } };
class C : protected A { public: void print() { //cout<<"a = "<<a; //err cout<<"b = "<<b; cout<<"c = "<<endl; } };
class D : private A { public: void print() { //cout<<"a = "<<a; //err cout<<"b = "<<b<<endl; cout<<"c = "<<c<<endl; } };
int main_01(int argc, char *argv[]) { A aa; B bb; C cc; D dd;
aa.c = 100; //ok bb.c = 100; //ok //cc.c = 100; //err 類的外部是什麼含義 //dd.c = 100; //err
aa.set(1, 2, 3); bb.set(10, 20, 30); //cc.set(40, 50, 60); //ee //dd.set(70, 80, 90); //ee
bb.print(); cc.print(); dd.print();
system("pause"); return 0; } |
類型兼容規則是指在需要基類對象的不論什麼地方。都可以使用公有派生類的對象來替代。經過公有繼承,派生類獲得了基類中除構造函數、析構函數以外的所有成員。這樣。公有派生類實際就具有了基類的所有功能。凡是基類能解決的問題。公有派生類都可以解決。類型兼容規則中所指的替代包含下面狀況:
子類對象可以看成父類對象使用
子類對象可以直接賦值給父類對象
子類對象可以直接初始化父類對象
父類指針可以直接指向子類對象
父類引用可以直接引用子類對象
在替代以後,派生類對象就可以做爲基類的對象使用。但是僅僅能使用從基類繼承的成員。
類型兼容規則是多態性的重要基礎之中的一個。
總結:子類就是特殊的父類 (base *p = &child;)
#include <cstdlib> #include <iostream>
using namespace std;
/* 子類對象可以看成父類對象使用 子類對象可以直接賦值給父類對象 子類對象可以直接初始化父類對象 父類指針可以直接指向子類對象 父類引用可以直接引用子類對象 */ //子類就是特殊的父類 class Parent03 { protected: const char* name; public: Parent03() { name = "Parent03"; }
void print() { cout<<"Name: "<<name<<endl; } };
class Child03 : public Parent03 { protected: int i; public: Child03(int i) { this->name = "Child2"; this->i = i; } };
int main() { Child03 child03(1000); //分別定義父類對象 父類指針 父類引用 child Parent03 parent = child03; Parent03* pp = &child03; Parent03& rp = child03;
parent.print(); pp->print(); rp.print(); system("pause"); return 0; } |
類在C++編譯器的內部可以理解爲結構體
子類是由父類成員疊加子類新成員獲得的
繼承中構造和析構
問題:怎樣初始化父類成員?父類與子類的構造函數有什麼關係
在子類對象構造時,需要調用父類構造函數對其繼承得來的成員進行初始化
在子類對象析構時。需要調用父類析構函數對其繼承得來的成員進行清理
#include <cstdlib> #include <iostream> using namespace std;
class Parent04 { public: Parent04(const char* s) { cout<<"Parent04()"<<" "<<s<<endl; }
~Parent04() { cout<<"~Parent04()"<<endl; } };
class Child04 : public Parent04 { public: Child04() : Parent04("Parameter from Child!") { cout<<"Child04()"<<endl; }
~Child04() { cout<<"~Child04()"<<endl; } };
void run04() { Child04 child; }
int main_04(int argc, char *argv[]) { run04();
system("pause"); return 0; } |
一、子類對象在建立時會首先調用父類的構造函數
二、父類構造函數運行結束後。運行子類的構造函數
三、當父類的構造函數有參數時,需要在子類的初始化列表中顯示調用
四、析構函數調用的前後順序與構造函數相反
原則: 先構造父類,再構形成員變量、最後構造本身
先析構本身,在析構成員變量、最後析構父類
//先構造的對象,後釋放
練習:demo05_extend_construct_destory.cpp
//子類對象怎樣初始化父類成員 //繼承中的構造和析構 //繼承和組合混搭狀況下,構造函數、析構函數調用順序研究
#include <iostream>
using namespace std;
class Object { public: Object(const char* s) { cout<<"Object()"<<" "<<s<<endl; } ~Object() { cout<<"~Object()"<<endl; } };
class Parent : public Object { public: Parent(const char* s) : Object(s) { cout<<"Parent()"<<" "<<s<<endl; } ~Parent() { cout<<"~Parent()"<<endl; } };
class Child : public Parent { protected: Object o1; Object o2; public: Child() : o2("o2"), o1("o1"), Parent("Parameter from Child!") { cout<<"Child()"<<endl; } ~Child() { cout<<"~Child()"<<endl; } };
void run05() { Child child; }
int main05(int argc, char *argv[]) { cout<<"demo05_extend_construct_destory.cpp"<<endl; run05();
system("pause"); return 0; } |
一、當子類成員變量與父類成員變量同名時
二、子類依舊從父類繼承同名成員
三、在子類中經過做用域分辨符::進行同名成員區分(在派生類中使用基類的同名成員。顯式地使用類名限定符)
四、同名成員存儲在內存中的不一樣位置
總結:同名成員變量和成員函數經過做用域分辨符進行區分
繼承和statickeyword在一塊兒會產生什麼現象哪?
理論知識
Ø 基類定義的靜態成員。將被所有派生類共享
Ø 依據靜態成員自身的訪問特性和派生類的繼承方式,在類層次體系中具備不一樣的訪問性質 (遵照派生類的訪問控制)
Ø 派生類中訪問靜態成員,用下面形式顯式說明:
類名 :: 成員
或經過對象訪問 對象名 . 成員
總結:
1> static函數也遵照3個訪問原則
2> static易犯錯誤(不但要初始化,更重要的顯示的告訴編譯器分配內存)
3> 構造函數默以爲private
多繼承概念
Ø 一個類有多個直接基類的繼承關係稱爲多繼承
Ø 多繼承聲明語法
class 派生類名 : 訪問控制 基類名1 , 訪問控制 基類名2 , … , 訪問控制 基類名n
{
數據成員和成員函數聲明
};
Ø 類 C 可以依據訪問控制同一時候繼承類 A 和類B 的成員,並加入
本身的成員
多繼承的派生類構造和訪問
Ø 多個基類的派生類構造函數可以用初始式調用基類構造函數初始化數據成員
Ø 運行順序與單繼承構造函數狀況相似。
多個直接基類構造函數運行順序取決於定義派生類時指定的各個繼承基類的順序。
Ø 一個派生類對象擁有多個直接或間接基類的成員。不一樣名成員訪問不會出現二義性。假設不一樣的基類有同名成員,派生類對象訪問時應該加以識別。
多繼承簡單應用
假設一個派生類從多個基類派生,而這些基類又有一個共同的基類。則在對該基類中聲明的名字進行訪問時。可能產生二義性
分析:
總結:
Ø 假設一個派生類從多個基類派生,而這些基類又有一個共同
的基類,則在對該基類中聲明的名字進行訪問時,可能產生
二義性
Ø 假設在多條繼承路徑上有一個公共的基類,那麼在繼承路徑的某處
匯合點,這個公共基類就會在派生類的對象中產生多個基類子對象
Ø 要使這個公共基類在派生類中僅僅產生一個子對象,必須對這個基類
聲明爲虛繼承。使這個基類成爲虛基類。
Ø 虛繼承聲明使用keyword virtual
實驗:注意添加virtualkeyword後,構造函數調用的次數。
Ø 繼承是面向對象程序設計實現軟件重用的重要方法。程序猿可以在已有基類的基礎上定義新的派生類。
Ø 單繼承的派生類僅僅有一個基類。多繼承的派生類有多個基類。
Ø 派生類對基類成員的訪問由繼承方式和成員性質決定。
Ø 建立派生類對象時,先調用基類構造函數初始化派生類中的基類成員。調用析構函數的次序和調用構造函數的次序相反。
Ø C++提供虛繼承機制,防止類繼承關係中成員訪問的二義性。
Ø 多繼承提供了軟件重用的強大功能,也添加了程序的複雜性。
問題引出(賦值兼容性原則趕上函數重寫)
面向對象新需求
C++提供的多態解決方式
多態案例
多態project意義
面向對象三大概念、三種境地(封裝、繼承、多態)
多態成立條件
總結條件、看代碼的時候要看出多態
假設子類定義了與父類中原型一樣的函數會發生什麼?
函數重寫 在子類中定義與父類中原型一樣的函數 函數重寫僅僅發生在父類與子類之間 |
class Parent { public: void print() { cout<<"Parent:print() do..."<<endl; } };
class Child : public Parent { public: void print() { cout<<"Child:print() do..."<<endl; } }; int main01() { run00();
/* Child child; Parent *p = NULL; p = &child; child.print(); child.Parent::print(); */
system("pause"); return 0; }
|
父類中被重寫的函數依舊會繼承給子類 默認狀況下子類中重寫的函數將隱藏父類中的函數 經過做用域分辨符::可以訪問到父類中被隱藏的函數 |
/* C/C++是靜態編譯型語言 在編譯時,編譯器本身主動依據指針的類型推斷指向的是一個什麼樣的對象 */ /* 一、在編譯此函數的時。編譯器不可能知道指針 p 到底指向了什麼。 二、編譯器沒有理由報錯。 三、因而,編譯器以爲最安全的作法是編譯到父類的print函數,因爲父類和子類確定都有一樣的print函數。 */
//面向對象新需求 //假設我傳一個父類對象。運行父類的print函數 //假設我傳一個子類對象,運行子類的printf函數
//現象產生的緣由 //賦值兼容性原則趕上函數重寫 出現的一個現象 //1 沒有理由報錯 //2 對被調用函數來說。在編譯器編譯期間,我就肯定了,這個函數的參數是p,是Parent類型的。。 。 //3靜態鏈編
//project開發中怎樣推斷是否是多態存在?
/* 在同一個類裏面能實現函數重載 繼承的狀況下。發生重寫 重載不必定; 重寫的定義 靜態聯編 重載是 動態聯編 */ #include <iostream>
using namespace std;
class Parent { public: void print() { cout<<"Parent:print() do..."<<endl; } };
class Child : public Parent { public: void print() { cout<<"Child:print() do..."<<endl; } };
/* 一、在編譯此函數的時,編譯器不可能知道指針 p 到底指向了什麼。 二、編譯器沒有理由報錯。 三、因而,編譯器以爲最安全的作法是編譯到父類的print函數,因爲父類和子類確定都有一樣的print函數。 */
void howToPrint(Parent* p) { p->print(); }
void run00() { Child child; Parent* pp = &child; Parent& rp = child;
//child.print();
//經過指針 //pp->print(); //經過引用 //rp.print();
howToPrint(&child); } int main01() { run00();
/* Child child; Parent *p = NULL; p = &child; child.print(); child.Parent::print(); */
system("pause"); return 0; }
|
編譯器的作法不是咱們指望的
依據實際的對象類型來推斷重寫函數的調用
假設父類指針指向的是父類對象則調用父類中定義的函數
假設父類指針指向的是子類對象則調用子類中定義的重寫函數
Ø C++中經過virtualkeyword對多態進行支持
Ø 使用virtual聲明的函數被重寫後就能夠展示多態特性
案例場景:
英雄戰機HeroFighter , AdvHeroFighter 分別和敵機EnemyFighter 戰鬥.
power() attack()
#include "iostream" using namespace std;
class HeroFighter { public:
public: virtual int ackPower() { return 10; } };
class AdvHeroFighter : public HeroFighter { public: virtual int ackPower() { return HeroFighter::ackPower()*2; } };
class enemyFighter { public: int destoryPower() { return 15; } };
//假設把這個結構放在動態庫裏面 //寫了一個框架,可以調用 //個人第3代戰機代碼出現的時間晚於框架出現的時間。。。 。 //框架 有使用後來人 寫的代碼的能力。。。 //面向對象3大概念 /* 封裝 突破了C語言函數的概念。 。
繼承 代碼複用 。。 。。 我複用原來寫好的代碼。。。
多態 多態可以使用將來。。。。 。 80年代寫了一個框架。。 。 。。。 90人寫的代碼 多態是咱們軟件行業追尋的一個目標。 。。 //// */ // void objPK(HeroFighter *hf, enemyFighter *enemyF) { if (hf->ackPower() >enemyF->destoryPower()) { printf("英雄戰勝敵人。 。 。勝利\n"); } else { printf("英雄。。。 犧牲\n"); } }
void main() { HeroFighter hf; enemyFighter ef;
objPK(&hf, &ef);
AdvHeroFighter advhf;
objPK(&advhf, &ef); system("pause"); } |
//面向對象3大概念
/*
封裝
突破了C語言函數的概念。
。
繼承
代碼複用 。
。
。。
我複用原來寫好的代碼。
。。
多態
多態可以使用將來。。。。
。80年代寫了一個框架。
。。
。
。。
90人寫的代碼
多態是咱們軟件行業追尋的一個目標。。。
//寫了一個框架。可以調用後來人。寫的代碼的能力
////
*/
//間接賦值成立的3個條件
//1 定義兩個變量。。。
//2 創建關聯 。。
。。
//3 *p
//多態成立的三個條件
//1 要有繼承
//2 要有函數重寫。。
。C 虛函數
//3 要有父類指針(父類引用)指向子類對象
//多態是設計模式的基礎。多態是框架的基礎
01靜態聯編和動態聯編
一、聯編是指一個程序模塊、代碼之間互相關聯的過程。
二、靜態聯編(static binding)。是程序的匹配、鏈接在編譯階段實現。也稱爲早期匹配。
重載函數使用靜態聯編。
三、動態聯編是指程序聯編推遲到執行時進行,因此又稱爲晚期聯編(遲綁定)。
switch 語句和 if 語句是動態聯編的樣例。
四、理論聯繫實際
一、C++與C一樣。是靜態編譯型語言 二、在編譯時,編譯器本身主動依據指針的類型推斷指向的是一個什麼樣的對象;因此編譯器以爲父類指針指向的是父類對象。 三、由於程序沒有執行。因此不可能知道父類指針指向的詳細是父類對象仍是子類對象 從程序安全的角度,編譯器若是父類指針僅僅指向父類對象。所以編譯的結果爲調用父類的成員函數。這樣的特性就是靜態聯編。 |
多態的實現效果 多態:相同的調用語句有多種不一樣的表現形態; 多態實現的三個條件 有繼承、有virtual重寫、有父類指針(引用)指向子類對象。 多態的C++實現 virtualkeyword,告訴編譯器這個函數要支持多態。不是依據指針類型推斷怎樣調用;而是要依據指針所指向的實際對象類型來推斷怎樣調用 動態聯編PK靜態聯編。依據實際的對象類型來推斷重寫函數的調用。 多態的重要意義 設計模式的基礎 是框架的基石。 實現多態的理論基礎 函數指針作函數參數 C函數指針是C++至高無上的榮耀。C函數指針通常有兩種使用方法(正、反)。 多態原理探究 與面試官展開討論 |
c++編譯器多態實現原理
函數重載
必須在同一個類中進行
子類沒法重載父類的函數,父類同名函數將被名稱覆蓋
重載是在編譯期間依據參數類型和個數決定函數調用
函數重寫
必須發生於父類與子類之間
並且父類與子類中的函數必須有全然一樣的原型
使用virtual聲明以後能夠產生多態(假設不使用virtual,那叫重定義)
多態是在執行期間依據詳細對象的類型決定函數調用
#include <cstdlib> #include <iostream>
using namespace std;
class Parent01 { public: Parent01() { cout<<"Parent01:printf()..do"<<endl; } public: virtual void func() { cout<<"Parent01:void func()"<<endl; }
virtual void func(int i) { cout<<"Parent:void func(int i)"<<endl; }
virtual void func(int i, int j) { cout<<"Parent:void func(int i, int j)"<<endl; } };
class Child01 : public Parent01 {
public:
//此處2個參數。和子類func函數是什麼關係 void func(int i, int j) { cout<<"Child:void func(int i, int j)"<<" "<<i + j<<endl; }
//此處3個參數的。和子類func函數是什麼關係 void func(int i, int j, int k) { cout<<"Child:void func(int i, int j, int k)"<<" "<<i + j + k<<endl; } };
void run01(Parent01* p) { p->func(1, 2); }
int main() { Parent01 p;
p.func(); p.func(1); p.func(1, 2);
Child01 c; //c.func(); //問題1 c.Parent01::func(); c.func(1, 2);
run01(&p); run01(&c);
system("pause"); return 0; }
//問題1:child對象繼承父類對象的func,請問這句話能執行嗎?why //c.func(); //因爲名稱覆蓋。C++編譯器不會去父類中尋找0個參數的func函數,僅僅會在子類中找func函數。
//1子類裏面的func沒法重載父類裏面的func //2當父類和子類有一樣的函數名、變量名出現,發生名稱覆蓋(子類的函數名,覆蓋了父類的函數名。 ) //3//c.Parent::func(); //問題2 子類的兩個func和父類裏的三個func函數是什麼關係? |
c++編譯器多態實現原理
在什麼狀況下應當聲明虛函數
Ø 構造函數不能是虛函數。創建一個派生類對象時,必須從類層次的根開始,沿着繼承路徑逐個調用基類的構造函數
Ø 析構函數可以是虛的。虛析構函數用於指引 delete 運算符正確析構動態對象
父類指針和子類指針的步長
1) 鐵律1:指針也僅僅一種數據類型。C++類對象的指針p++/--,仍然可用。
2) 指針運算是依照指針所指的類型進行的。
p++《=》p=p+1 //p = (unsigned int)basep + sizeof(*p) 步長。
3) 結論:父類p++與子類p++步長不一樣;不要混搭。不要用父類指針++方式操做數組。
理論知識:
Ø 當類中聲明虛函數時,編譯器會在類中生成一個虛函數表
Ø 虛函數表是一個存儲類成員函數指針的數據結構
Ø 虛函數表是由編譯器本身主動生成與維護的
Ø virtual成員函數會被編譯器放入虛函數表中
Ø 當存在虛函數時,每個對象中都有一個指向虛函數表的指針(C++編譯器給父類對象、子類對象提早佈局vptr指針。當進行howToPrint(Parent *base)函數是,C++編譯器不需要區分子類對象或者父類對象,僅僅需要再base指針中,找vptr指針就能夠。
)
Ø VPTR通常做爲類對象的第一個成員
C++中多態的實現原理 當類中聲明虛函數時,編譯器會在類中生成一個虛函數表 虛函數表是一個存儲類成員函數指針的數據結構 虛函數表是由編譯器本身主動生成與維護的 virtual成員函數會被編譯器放入虛函數表中 存在虛函數時,每個對象中都有一個指向虛函數表的指針(vptr指針) |
說明1: 經過虛函數表指針VPTR調用重寫函數是在程序執行時進行的,所以需要經過尋址操做才幹肯定真正應該調用的函數。而普通成員函數是在編譯時就肯定了調用的函數。在效率上。虛函數的效率要低很是多。 說明2: 出於效率考慮,沒有必要將所有成員函數都聲明爲虛函數 |
說明3 :C++編譯器。運行HowToPrint函數。不需要區分是子類對象仍是父類對象 |
#include <iostream> using namespace std;
class A { public: void printf() { cout<<"aaa"<<endl; } protected: private: int a; };
class B { public: virtual void printf() { cout<<"aaa"<<endl; } protected: private: int a; };
void main() { //加上virtualkeyword c++編譯器會添加一個指向虛函數表的指針 。。。 printf("sizeof(a):%d, sizeof(b):%d \n", sizeof(A), sizeof(B)); cout<<"hello..."<<endl; system("pause"); return ; } |
1)對象中的VPTR指針何時被初始化?
對象在建立的時,由編譯器對VPTR指針進行初始化 僅僅有當對象的構造全然結束後VPTR的指向才終於肯定 父類對象的VPTR指向父類虛函數表 子類對象的VPTR指向子類虛函數表
|
2)分析過程 繪圖分析 |
C++中沒有Java中的接口概念,抽象類可以模擬Java中的接口類。(接口和協議)
project上的多繼承
被實際開發經驗拋棄的多繼承
project開發中真正意義上的多繼承是差點兒不被使用的
多重繼承帶來的代碼複雜性遠多於其帶來的便利
多重繼承對代碼維護性上的影響是災難性的
在設計方法上,不論什麼多繼承都可以用單繼承取代
多繼承中的二義性和多繼承不能解決的問題
C++中是否有Java中的接口概念? |
絕大多數面嚮對象語言都不支持多繼承 絕大多數面嚮對象語言都支持接口的概念 C++中沒有接口的概念 C++中可以使用純虛函數實現接口 接口類中僅僅有函數原型定義,沒有不論什麼數據的定義。 class Interface { public: virtual void func1() = 0; virtual void func2(int i) = 0; virtual void func3(int i) = 0; };
|
實際project經驗證實 多重繼承接口不會帶來二義性和複雜性等問題 多重繼承可以經過精心設計用單繼承和接口來取代 接口類僅僅是一個功能說明。而不是功能實現。 子類需要依據功能說明定義功能實現。 |
#include "iostream" using namespace std;
/* C++中沒有接口的概念 C++中可以使用純虛函數實現接口 接口類中僅僅有函數原型定義,沒有不論什麼數據的定義。 */
class Interface1 { public: virtual void print() = 0; virtual int add(int a, int b) = 0; };
class Interface2 { public: virtual void print() = 0; virtual int add(int a, int b) = 0; virtual int minus(int a, int b) = 0; };
class parent { public: int a; }; class Child : public parent, public Interface1, public Interface2 { public: void print() { cout<<"Child::print"<<endl; }
int add(int a, int b) { return a + b; }
int minus(int a, int b) { return a - b; } };
int main() { Child c;
c.print();
cout<<c.add(3, 5)<<endl; cout<<c.minus(4, 6)<<endl;
Interface1* i1 = &c; Interface2* i2 = &c;
cout<<i1->add(7, 8)<<endl; cout<<i2->add(7, 8)<<endl; system("pause"); } |
/*
編寫一個C++程序, 計算程序猿( programmer )工資
1要求能計算出0基礎程序猿( junior_programmer ) 中級程序猿 ( mid_programmer )高級程序猿( adv_programmer)的工資
2要求利用抽象類統一界面,方便程序的擴展, 比方:新增, 計算架構師 (architect ) 的工資
*/
理論知識
Ø 虛函數和多態性使成員函數依據調用對象的類型產生不一樣的動做
Ø 多態性特別適合於實現分層結構的軟件系統,便於對問題抽象時 定義共性,實現時定義差異
Ø 面向抽象類編程(面向接口編程)是項目開發中重要技能之中的一個。
企業信息系統框架集成第三方產品
案例背景:通常的企業信息系統都有成熟的框架。軟件框架通常不發生變化,能自由的集成第三方廠商的產品。
案例需求:請你在企業信息系統框架中集成第三方廠商的Socket通訊產品和第三方廠商加密產品。
第三方廠商的Socket通訊產品:完畢兩點之間的通訊;
第三方廠商加密產品:完畢數據發送時加密。數據解密時解密。
案例要求: 1)能支持多個廠商的Socket通訊產品入圍
2)能支持多個第三方廠商加密產品的入圍
3)企業信息系統框架不輕易發生框架
需求實現
思考1:企業信息系統框架、第三方產品怎樣分層
思考2:企業信息系統框架。怎樣自由集成第三方產品
(軟件設計:模塊要求鬆、接口要求緊)
思考3:軟件分紅之後。開發企業信息系統框架的程序猿,應該作什麼?
第三方產品入圍應該作什麼?
編碼實現
分析有多少個類 CSocketProtocol CSckFactoryImp1 CSckFactoryImp2
CEncDesProtocol HwEncdes ciscoEncdes
一、 定義CSocketProtocol 抽象類
二、 編寫框架函數
三、 編寫框架測試函數
四、 廠商1(CSckFactoryImp1)實現CSocketProtocol、廠商2(CSckFactoryImp1)實現CSocketProtocol
五、 抽象加密接口(CEncDesProtocol)、加密廠商1(CHwImp)、加密廠商2(CCiscoImp)),集成實現業務模型
六、 框架(c語言函數方式。框架函數;c++類方式。框架類)
幾個重要的面向對象思想
繼承-組合(強弱)
注入
控制反轉 IOC
MVC
面向對象思想擴展aop思想
aop思想是對繼承編程思想的有力的補充
友情提示:今天課程內容,更加貼近實戰,並且語法和軟件思想都較難,請學員緊跟思路。課後增強複習!
結論: 僅僅要你動手,又很是easy!
函數三要素: 名稱、參數、返回值 C語言中的函數有本身特定的類型 |
C語言中經過typedef爲函數類型重命名 typedef type name(parameter list) typedef int f(int, int); typedef void p(int); |
函數指針 |
函數指針用於指向一個函數 函數名是函數體的入口地址 1)可經過函數類型定義函數指針: FuncType* pointer; 2)也可以直接定義:type (*pointer)(parameter list); pointer爲函數指針變量名 type爲指向函數的返回值類型 parameter list爲指向函數的參數類型列表 |
函數指針語法梳理 //函數類型 //函數指針類型 //函數指針變量
數組指針語法梳理 //數組類型語法 //數組指針類型 //數組指針變量
|
typedef int(FUNC)(int);
int test(int i) { return i * i; }
void f() { printf("Call f()...\n"); }
int main() { FUNC* pt = test;
void(*pf)() = &f;
pf(); (*pf)();
printf("Function pointer call: %d\n", pt(3)); } |
一、 指針作函數參數pk函數指針作函數參數 回顧指針作函數參數 一級指針作函數參數、二級。 。 。。、三級 |
二、 函數指針作函數參數 當函數指針 作爲函數的參數,傳遞給一個被調用函數。 被調用函數就可以經過這個指針調用外部的函數,這就造成了回調 |
三、練習 int add(int a, int b) int libfun( int (*pDis)(int a, int b) );
int main(void) { int (*pfun)(int a, int b); pfun = add; libfun(pfun);
}
int add(int a, int b) { return a + b;
}
int libfun( int (*pDis)(int a, int b) ) { int a, b; a = 1; b = 2; add(1,3) //直接調用add函數 printf("%d", pDis(a, b)); //經過函數指針作函數參數,間接調用add函數 //思考 這樣寫 pDis(a, b)有什麼優勢? } //剖析思路 //1函數的調用 和 函數的實現 有效的分離 //2 C++的多態,可擴展
現在這幾個函數是在同一個文件其中 假如 int libfun(int (*pDis)(int a, int b)) 是一個庫中的函數,就僅僅有使用回調了。經過函數指針參數將外部函數地址傳入 來實現調用
函數 add 的代碼做了修改,也沒必要修改庫的代碼,就可以正常實現調用 便於程序的維護和升級 |
回調函數思想:
結論:回調函數的本質:提早作了一個協議的約定(把函數的參數、函數返回值提早約定)
請思考:C編譯器經過那個詳細的語法。實現解耦合的?
C++編譯器經過多態的機制(提早佈局vptr指針和虛函數表,找虛函數入口地址來實現)
一、 函數指針作函數參數,調用方式 被調用函數和主調函數在同一文件裏(用來教學,沒有不論什麼意義) |
二、函數指針作函數參數 被調用函數和主調函數不在同一個文件裏、模塊中。 難點:理解被調用函數是什麼機制被調用起來的。 框架 框架提早設置了被調用函數的入口(框架提供了第三方模塊入口地址的集成功能) 框架具有調用第三方模塊入口函數 |
三、 練習
typedef int (*EncDataFunc)(unsigned char *inData,int inDataLen,unsigned char *outData,int *outDataLen,void *Ref, int RefLen);
int MyEncDataFunc(unsigned char *inData,int inDataLen,unsigned char *outData,int *outDataLen,void *Ref, int RefLen) { int rv = 0; char *p = "222222222222";
strcpy(outData, p); *outDataLen = strlen(p); return rv; }
int Send_Data(EncDataFunc encDataFunc, unsigned char *inData, int inDataLen, unsigned char *outData, int *outDatalen) { int rv = 0; if (encDataFunc != NULL) { rv = encDataFunc(inData, inDataLen, outData, outDatalen, NULL, 0); if (rv != 0) { printf("func encDataFunc() err.\n"); return rv; } } return rv; }
int main() { int rv = 0;
EncDataFunc encDataFunc = NULL; encDataFunc = MyEncDataFunc;
// 第一個調用 { unsigned char inData[2048]; int inDataLen; unsigned char outData[2048]; int outDatalen; strcpy(inData, "1111"); inDataLen = strlen(inData); rv = encDataFunc(inData,inDataLen, outData, &outDatalen, NULL, 0); if (rv != 0) { printf("edf err .....\n"); } else { printf("edf ok \n"); printf("%s \n", outData); } }
{ unsigned char inData[2048]; int inDataLen; unsigned char outData[2048]; int outDatalen; strcpy(inData, "3333"); inDataLen = strlen(inData); rv = Send_Data(MyEncDataFunc, inData, inDataLen, outData, &outDatalen); if (rv != 0) { printf("func Send_Data err:%d", rv); return rv; } printf("%s \n", outData); }
getchar(); } |
回調函數效果展現。
C語言版本號Socket動態庫升級成框架集成第三方產品
簡稱:C動態庫升級成框架案例
名字解釋
動態庫:抽象類一個套接口,單獨封裝成模塊,供別人調用;沒法擴展。
框架:能自由的擴展
案例背景:通常的企業信息系統都有成熟的框架,可以有C語言寫,也可以由C++語言。軟件框架通常不發生變化。能自由的集成第三方廠商的產品。
案例需求:在socket通訊庫中,完畢數據加密功能,有n個廠商的加密產品供你選擇。怎樣實現動態庫和第三個廠商產品的解耦合。
提醒:C++經過抽象類,也就是面向抽象類編程實現的(至關於C++編譯器經過多態機制,已經很是好用了。提早佈局vptr指針、虛函數表;調用是遲綁定完畢。),
C語言中怎樣實現哪?
案例要求: 1)能支持多個第三方廠商加密產品的入圍
2)企業信息系統框架不輕易發生框架
需求實現思路分析
思考2:企業信息系統框架,怎樣自由集成第三方產品
(軟件設計:模塊要求鬆、接口要求緊)
思考3:軟件分層肯定後,動態庫應該作什麼?產品入圍廠商應該作什麼?
之後。開發企業信息系統框架的程序猿,應該作什麼?
第三方產品入圍應該作什麼?
編碼實現
一、 動態庫中定義協議。並完畢任務的調用
typedef int (*EncData)(unsigned char *inData,int inDataLen,unsigned char*outData,int *outDataLen,void *Ref, int RefLen);
typedef int (*DecData)(unsigned char *inData,int inDataLen,unsigned char*outData,int *outDataLen,void *Ref, int RefLen);
二、 加密廠商完畢協議函數的編寫
三、 對接調試。
四、 動態庫中可以緩存第三方函數的入口地址。也可以不緩存。兩種實現方式。
案例總結
回調函數:利用函數指針作函數參數,實現的一種調用機制。詳細任務的實現者,可以不知道何時被調用。
回調機制原理:
當詳細事件發生時,調用者經過函數指針調用詳細函數
回調機制的將調用者和被調函數分開。二者互不依賴
任務的實現 和 任務的調用 可以耦合 (提早進行接口的封裝和設計)
劉備利用周瑜、曹仁廝殺之際。乘虛襲取了南郡、荊州、襄陽,之後又征服了長沙等四郡。
周瑜想一想十分氣恨,正無處報復以奪還荊州。不久。劉備突然喪偶。周瑜計上心來,對孫權說:「您的妹妹,漂亮、剛強,咱們以聯姻抗曹名義向劉備招親,把他騙來南徐幽禁,逼他們拿荊州來換。」孫權大喜,郎派人到荊州說親。
劉備以爲這是騙局,想要拒絕。諸葛亮笑道:「送個好妻子上門何不答應?您僅僅管去東吳,我叫趙雲陪您去,自有安排,包您得了夫人又不失荊州。」
接着。諸葛亮暗暗關照趙雲道:「我這裏有三個錦囊。內藏三條妙計。到南徐時打開第一個,到年末時打開第二個。危險無路時打開第三個。
」
第一個錦囊
一到東吳就拜會喬國老
第二個錦囊
劉備被孫權設計留下就對他謊稱曹操大軍壓境
第三個錦囊
被東吳軍隊追趕就求孫夫人解圍