C++类和对象学习总结
生活随笔
收集整理的這篇文章主要介紹了
C++类和对象学习总结
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
C++中的類與對象
http://blog.csdn.net/qq_32583189/article/details/52412369C++中的類與對象
C中的類與對象
定義類
創建頭文件
類和結構體
訪問控制
作用域解析運算符
實現類成員
默認的內聯方法
創建對象
開發環境:Visual Studio 2010
定義類
通常,C++程序將接口(類定義)放在頭文件中,并將實現(類方法的代碼放在源代碼文件中),本文章以一個學生的例子來完成代碼。
學生定義:記錄三門課程(語文,英語,數學)的成績,能夠查看學生的平均成績
創建頭文件
現在創建名為student.h的頭文件
//student.h -- Student class interface //version 00 #ifndef STUDENT_H_ #define STUDENT_H_#include <string>class Student{ //class declaration private:std::string name;int ch;int en;int math;float average;void count(){average = (ch + en + math+0.0F)/3;} public:Student();//構造函數~Student();//析構函數void setName(std::string name);void setChScore(int score);void setEnScore(int score);void setMathScore(int score);void show(); };#endif
注意:類定義和結構體定義一樣,要在最后加上;(分號)
類和結構體
關鍵字class指明這些代碼定義了一個類設計,在這里class和typename不是同義詞,不能使用typename代替class
類描述看上去很像是包含成員函數以及public和private可見性標簽的結構聲明。實際上,C++對結構進行了擴展,使之具有相同的特性。它們之間唯一的區別就是,結構體的默認訪問類型是public,而類為private。C++程序猿通常使用類來實現類的描述,而把結構體作為純粹的數據對象表示。
訪問控制
在C++程序中通常使用訪問控制關鍵字來保證自身屬性的可見性。常見的三個關鍵字為private,public,protected
關鍵字 含義
private C++中的默認訪問權限,也就是說上述student.h文件中的private關鍵字可以省略。其含義表示為只有該對象自身可以訪問這些屬性或者方法,被private修飾的屬性和方法對于外部來說不可見
public 公有權限,表示被public修飾的屬性和方法可以被外部程序直接調用
protected 受保護的權限,表示這些屬性和方法僅僅能被該類和派生類的內部訪問,也就是說,對于外部世界來說,保護成員的行為與private相同,對于派生類來說,保護成員的行為和public相同
作用域解析運算符
在具體實現一個類之前,我們需要先了解一個運算符:作用域解析運算符(::)。該符號的作用是用來標識一個函數所屬的類,例如當我們輸入Student::average()時。表示我們訪問的average()方法屬于Student類而不是其他的類。?
但是在一個Student類的內部我們并不需要使用使用作用域解析運算符就能訪問average()方法,這是因為在類的內部自身所定義的所有方法對自身來說都是可見的。?
對于一個方法名稱來說,Student::average()是方法的函數限定名(全稱),而average()方法屬于非限定名(簡稱),當程序對于一個函數來源無異議時(沒有重名方法),可以使用簡稱。
實現類成員
現在我們創建一個C++文件student.cpp
//student.cpp -- implementing the Student class
//version 00
#include "stdafx.h"
#include "student.h"
Student::Student(){
}
Student::~Student(){
}
void Student::setChScore(int score){
? ? Student::ch = score;
}
void Student::setName(std::string n){
? ? Student::name = n;
}
void Student::setEnScore(int score){
? ? en = score;
}
void Student::setMathScore(int score){
? ? math = score;
}
void Student::show(){
? ? Student::count();
? ? ios_base::fmtflags orig = cout.setf(ios_base::fixed,ios_base::floatfield);
? ? std::streamsize prec = cout.precision(1);
? ? cout<<name<<" 同學的語文成績為"<<ch<<"分,數學成績為"<<math<<"分,英語成績為"<<en<<"分,平均成績"<<average<<"分"<<endl;
? ? cout.setf(orig,ios_base::floatfield);
}
注意這個實現的Student::setChScore(int score)中我們在使用ch參數時使用了作用于解析運算符,其實在類的內部我們可以直接使用簡稱,所以在后續的方法中我們使用了非限定名。
默認的內聯方法
我們在類的定義中聲明并定義了一個count()方法,由于其位于聲明中,編譯器將其編譯為內聯方法。一般內容簡單的方法都可以作為內聯方法。如果我們并不想直接在定義中聲明并定義一個內聯方法,我們也可以在實現時給方法添加inline關鍵字。比如
? ? inline void count(){
? ? ? ? average = (ch + en + math+0.0F)/3;
? ? }
但是我們在聲明外定義內聯函數時必須注意C++中的一個規則:要求內聯函數在每一個使用它的源文件中均由定義。也就是說我們需要在每一個使用該方法的文件中定義該方法,很明顯我們并不想這樣做,所以最好的實現就是在聲明中實現定義方法。
創建對象
當我們在使用一個類的實例對象時需要用到一個關鍵字new,這個關鍵字和C語言中的malloc關鍵字類似,都是用來在堆內存中為數據分配一段空間。?
我們在main方法中舉個例子
//visual studio 2010 -- main program
#include "stdafx.h"
#include "student.h"
int _tmain(int argc, _TCHAR* argv[]){
? ? Student *jack = new Student();
? ? jack->setName("jack");
? ? jack->setChScore(98);
? ? jack->setMathScore(100);
? ? jack->setEnScore(92);
? ? jack->show();
? ? delete jack;
? ? return 0;
}
輸出結果:
jack 同學的語文成績為98分,數學成績為100分,英語成績為92分,平均成績96.7分
請按任意鍵繼續. . .
溫馨提醒:記得養成良好習慣,在對象不用后要使用delete關鍵字銷毀對象。
========
C++類的定義和對象的創建
http://c.biancheng.net/cpp/biancheng/view/185.html類和對象是 C++ 的重要特性,它們使得 C++ 成為面向對象的編程語言,可以用來開發中大型項目,本節重點講解類和對象的語法,如果你對它們的概念還不了解,請先閱讀《C++類和對象的概念》。
類是創建對象的模板,一個類可以創建多個對象,每個對象都是類類型的一個變量;創建對象的過程也叫類的實例化。每個對象都是類的一個具體實例(Instance),擁有類的成員變量和成員函數。
有些教程將類的成員變量稱為類的屬性(Property),將類的成員函數稱為類的方法(Method)。在面向對象的編程語言中,經常把函數(Function)稱為方法(Method)。
與結構體一樣,類只是一種復雜數據類型的聲明,不占用內存空間。而對象是類這種數據類型的一個變量,或者說是通過類這種數據類型創建出來的一份實實在在的數據,所以占用內存空間。
類的定義
類是用戶自定義的類型,如果程序中要用到類,必須提前說明,或者使用已存在的類(別人寫好的類、標準庫中的類等),C++語法本身并不提供現成的類的名稱、結構和內容。
一個簡單的類的定義:
純文本復制
class Student{
public:
? ? //成員變量
? ? char *name;
? ? int age;
? ? float score;
? ? //成員函數
? ? void say(){
? ? ? ? cout<<name<<"的年齡是"<<age<<",成績是"<<score<<endl;
? ? }
};
class是 C++ 中新增的關鍵字,專門用來定義類。Student是類的名稱;類名的首字母一般大寫,以和其他的標識符區分開。{ }內部是類所包含的成員變量和成員函數,它們統稱為類的成員(Member);由{ }包圍起來的部分有時也稱為類體,和函數體的概念類似。public也是 C++ 的新增關鍵字,它只能用在類的定義中,表示類的成員變量或成員函數具有“公開”的訪問權限,初學者請先忽略該關鍵字,我們將在《C++類成員的訪問權限》中講解。
注意在類定義的最后有一個分號;,它是類定義的一部分,表示類定義結束了,不能省略。
整體上講,上面的代碼創建了一個 Student 類,它包含了 3 個成員變量和 1 個成員函數。
類只是一個模板(Template),編譯后不占用內存空間,所以在定義類時不能對成員變量進行初始化,因為沒有地方存儲數據。只有在創建對象以后才會給成員變量分配內存,這個時候就可以賦值了。
類可以理解為一種新的數據類型,該數據類型的名稱是 Student。與 char、int、float 等基本數據類型不同的是,Student 是一種復雜數據類型,可以包含基本類型,而且還有很多基本類型中沒有的特性,以后大家會見到。
創建對象
有了 Student 類后,就可以通過它來創建對象了,例如:
Student liLei; ?//創建對象
Student是類名,liLei是對象名。這和使用基本類型定義變量的形式類似:
int a; ?//定義整型變量
從這個角度考慮,我們可以把 Student 看做一種新的數據類型,把 liLei 看做一個變量。
在創建對象時,class 關鍵字可要可不要,但是出于習慣我們通常會省略掉 class 關鍵字,例如:
class Student LiLei; ?//正確
Student LiLei; ?//同樣正確
除了創建單個對象,還可以創建對象數組:
Student allStu[100];
該語句創建了一個 allStu 數組,它擁有100個元素,每個元素都是 Student 類型的對象。
訪問類的成員
創建對象以后,可以使用點號.來訪問成員變量和成員函數,這和通過結構體變量來訪問它的成員類似,如下所示:
#include <iostream>
using namespace std;
//類通常定義在函數外面
class Student{
public:
? ? //類包含的變量
? ? char *name;
? ? int age;
? ? float score;
? ? //類包含的函數
? ? void say(){
? ? ? ? cout<<name<<"的年齡是"<<age<<",成績是"<<score<<endl;
? ? }
};
int main(){
? ? //創建對象
? ? Student stu;
? ? stu.name = "小明";
? ? stu.age = 15;
? ? stu.score = 92.5f;
? ? stu.say();
? ? return 0;
}
運行結果:
小明的年齡是15,成績是92.5
stu 是一個對象,占用內存空間,可以對它的成員變量賦值,也可以讀取它的成員變量。
類通常定義在函數外面,當然也可以定義在函數內部,不過很少這樣使用。
使用對象指針
C語言中經典的指針在 C++ 中仍然廣泛使用,尤其是指向對象的指針,沒有它就不能實現某些功能。
上面代碼中創建的對象 stu 在棧上分配內存,需要使用&獲取它的地址,例如:
Student stu;
Student *pStu = &stu;
pStu 是一個指針,它指向 Student 類型的數據,也就是通過 Student 創建出來的對象。
當然,你也可以在堆上創建對象,這個時候就需要使用前面講到的new關鍵字,例如:
Student *pStu = new Student;
在棧上創建出來的對象都有一個名字,比如 stu,使用指針指向它不是必須的。但是通過 new 創建出來的對象就不一樣了,它在堆上分配內存,沒有名字,只能得到一個指向它的指針,所以必須使用一個指針變量來接收這個指針,否則以后再也無法找到這個對象了,更沒有辦法使用它。也就是說,使用 new 在堆上創建出來的對象是匿名的,沒法直接使用,必須要用一個指針指向它,再借助指針來訪問它的成員變量或成員函數。
棧內存是程序自動管理的,不能使用 delete 刪除在棧上創建的對象;堆內存由程序員管理,對象使用完畢后可以通過 delete 刪除。在實際開發中,new 和 delete 往往成對出現,以保證及時刪除不再使用的對象,防止無用內存堆積。
棧(Stack)和堆(Heap)是 C/C++ 程序員必須要了解的兩個概念,我們已在《C語言和內存》專題中進行了深入講解,相信你必將有所頓悟。
有了對象指針后,可以通過箭頭->來訪問對象的成員變量和成員函數,這和通過結構體指針來訪問它的成員類似,請看下面的示例:
pStu -> name = "小明";
pStu -> age = 15;
pStu -> score = 92.5f;
pStu -> say();
下面是一個完整的例子:
#include <iostream>
using namespace std;
class Student{
public:
? ? char *name;
? ? int age;
? ? float score;
? ? void say(){
? ? ? ? cout<<name<<"的年齡是"<<age<<",成績是"<<score<<endl;
? ? }
};
int main(){
? ? Student *pStu = new Student;
? ? pStu -> name = "小明";
? ? pStu -> age = 15;
? ? pStu -> score = 92.5f;
? ? pStu -> say();
? ? delete pStu; ?//刪除對象
? ? return 0;
}
運行結果:
小明的年齡是15,成績是92.5
雖然在一般的程序中無視垃圾內存影響不大,但記得 delete 掉不再使用的對象依然是一種良好的編程習慣。
總結
本節重點講解了兩種創建對象的方式:一種是在棧上創建,形式和定義普通變量類似;另外一種是在堆上創建,必須要用一個指針指向它,讀者要記得 delete 掉不再使用的對象。
通過對象名字訪問成員使用點號.,通過對象指針訪問成員使用箭頭->,這和結構體非常類似。
========
C++類(Class)總結
http://www.cnblogs.com/xiongxuanwen/p/4290086.html?
一、C++類的定義
? ? C++中使用關鍵字 class 來定義類, 其基本形式如下:
class 類名
{
public:
//行為或屬性?
protected:
//行為或屬性
private:
//行為或屬性
};
?
示例:
? ? ?定義一個點(Point)類, 具有以下屬性和方法:
? ? ? 屬性: x坐標, y坐標
? ? ? 方法: 1.設置x,y的坐標值; 2.輸出坐標的信息。
實現代碼:
class Point
{
public:
? ? ?void setPoint(int x, int y);
? ? ?void printPoint();
?
private:
? ? ?int xPos;
? ? ?int yPos;
}; ?
代碼說明:
? ? ?上段代碼中定義了一個名為 Point 的類, 具有兩個私密屬性, int型的xPos和yPos, 分別用來表示x點和y點。
? ? ?在方法上, setPoint 用來設置屬性, 也就是 xPos 和 yPos 的值; printPoint 用來輸出點的信息。 ? ?
1 數據抽象和封裝
? ? ?抽象是通過特定的實例抽取共同特征以后形成概念的過程。一個對象是現實世界中一個實體的抽象,一個類是一組對象的抽象。
? ? ?封裝是將相關的概念組成一個單元,然后通過一個名稱來引用它。面向對象封裝是將數據和基于數據的操作封裝成一個整體對象,對數據的訪問或修改只能通過對象對外提供的接口進行。
?
2 類定義
? ? ?幾個重要名詞:
(1) 類名
? ? ?遵循一般的命名規則; 字母,數字和下劃線組合,不要以數字開頭。
(2) 類成員
? ? ?類可以沒有成員,也可以定義多個成員。成員可以是數據、函數或類型別名。所有的成員都必須在類的內部聲明。
? ? ?沒有成員的類是空類,空類也占用空間。
class People
{
};
sizeof(People) = 1; ? ?
(3) 構造函數
? ? ?構造函數是一個特殊的、與類同名的成員函數,用于給每個數據成員設置適當的初始值。
(4) 成員函數
? ? ?成員函數必須在類內部聲明,可以在類內部定義,也可以在類外部定義。如果在類內部定義,就默認是內聯函數。
?
3 類定義補充
3.1 可使用類型別名來簡化類
? ? ?除了定義數據和函數成員之外,類還可以定義自己的局部類型名字。
? ? ?使用類型別名有很多好處,它讓復雜的類型名字變得簡單明了、易于理解和使用,還有助于程序員清楚地知道使用該類型的真實目的。
class People
{?
public:?
? ? ?typedef std::string phonenum; //電話號碼類型
?
? ? ?phonenum phonePub; //公開號碼
private: ? ? ?
? ? ?phonenum phonePri;//私人號碼
};?
?
3.2 成員函數可被重載
? ? ?可以有多個重載成員函數,個數不限。
3.3 內聯函數
? ? ?有三種:
(1)直接在類內部定義。
(2)在類內部聲明,加上inline關鍵字,在類外部定義。
(3)在類內部聲明,在類外部定義,同時加上inline關鍵字。注意:此種情況下,內聯函數的定義通常應該放在類定義的同一頭文件中,而不是在源文件中。這是為了保證內聯函數的定義在調用該函數的每個源文件中是可見的。
3.4 訪問限制
? ? ?public,private,protected 為屬性/方法限制的關鍵字。
3.5 類的數據成員中不能使用 auto、extern和register等進行修飾, 也不能在定義時進行初始化
? ? ?如 int xPos = 0; //錯;
例外:
? ? ? ? ? 靜態常量整型(包括char,bool)數據成員可以直接在類的定義體中進行初始化,例如:
? ? ? ? ? static const int ia= 30;?
?
4 類聲明與類定義
4.1 類聲明(declare)
class Screen;
? ? ? 在聲明之后,定義之前,只知道Screen是一個類名,但不知道包含哪些成員。只能以有限方式使用它,不能定義該類型的對象,只能用于定義指向該類型的指針或引用,聲明(不是定義)使用該類型作為形參類型或返回類型的函數。
void Test1(Screen& a){};
void Test1(Screen* a){};
4.2 類定義(define)
? ? ?在創建類的對象之前,必須完整的定義該類,而不只是聲明類。所以,類不能具有自身類型的數據成員,但可以包含指向本類的指針或引用。
class LinkScreen
{
public:
? ? ? ? ? Screen window;
? ? ? ? ? LinkScreen* next;
? ? ? ? ? LinkScreen* prev;
}; //注意,分號不能丟
? ? ?因為在類定義之后可以接一個對象定義列表,可類比內置類型,定義必須以分號結束:
class LinkScreen{ /* ... */ };
class LinkScreen{ /* ... */ } scr1,scr2;?
? ? ?
5 類對象
? ? ?定義類對象時,將為其分配存儲空間。
? ? ?Sales_item item; //編譯器分配了足以容納一個 Sales_item 對象的存儲空間。item 指的就是那個存儲空間。
?
6 隱含的 this 指針?
? ? ?成員函數具有一個附加的隱含形參,即 this指針,它由編譯器隱含地定義。成員函數的函數體可以顯式使用 this 指針。
6.1 何時使用 this 指針
? ? ?當我們需要將一個對象作為整體引用而不是引用對象的一個成員時。最常見的情況是在這樣的函數中使用 this:該函數返回對調用該函數的對象的引用。
class Screen?
{
...
public:
? ? ? Screen& set(char);
};
Screen& Screen::set(char c)?
{
? ? ? contents[cursor] = c;
? ? ? return *this;
}
7 類作用域
? ? ?每個類都定義了自己的作用域和唯一的類型。
? ? ?類的作用域包括:類的內部(花括號之內), 定義在類外部的成員函數的參數表(小括號之內)和函數體(花括號之內)。
class Screen?
{?
//類的內部
...
};?
//類的外部
char Screen::get(index r, index c) const
{
? ? ?index row = r * width; ? ? ?// compute the row location
? ? ?return contents[row + c]; ? // offset by c to fetch specified character
}?
? ? ?注意:成員函數的返回類型不一定在類作用域中。可通過 類名::來判斷是否是類的作用域,::之前不屬于類的作用域,::之后屬于類的作用域。例如
Screen:: 之前的返回類型就不在類的作用域,Screen:: 之后的函數名開始到函數體都是類的作用域。
class Screen?
{?
public:?
? ? ?typedef std::string::size_type index;?
? ? ?index get_cursor() const;?
};?
Screen::index Screen::get_cursor() const ? //注意:index前面的Screen不能少
{?
? ? ?return cursor;?
}?
? ? ?該函數的返回類型是 index,這是在 Screen 類內部定義的一個類型名。在類作用域之外使用,必須用完全限定的類型名 Screen::index 來指定所需要的 index 是在類 Screen 中定義的名字。
?
?
二 構造函數
? ? ?構造函數是特殊的成員函數,用來保證每個對象的數據成員具有合適的初始值。
? ? ?構造函數名字與類名相同,不能指定返回類型(也不能定義返回類型為void),可以有0-n個形參。
? ? ?在創建類的對象時,編譯器就運行一個構造函數。
?
1 構造函數可以重載
? ? ?可以為一個類聲明的構造函數的數量沒有限制,只要每個構造函數的形參表是唯一的。
class Sales_item;
{
public:?
? ? ?Sales_item(const std::string&);?
? ? ?Sales_item(std::istream&);?
? ? ?Sales_item(); //默認構造函數
};?
?
2 構造函數自動執行?
? ? ?只要創建該類型的一個對象,編譯器就運行一個構造函數:
Sales_item item1("0-201-54848-8");
Sales_item *p = new Sales_item();?
? ? ?第一種情況下,運行接受一個 string 實參的構造函數,來初始化變量item1。
? ? ?第二種情況下,動態分配一個新的 Sales_item 對象,通過運行默認構造函數初始化該對象。
?
3 構造函數初始化式
? ? ?與其他函數一樣,構造函數具有名字、形參表和函數體。
? ? ?與其他函數不同的是,構造函數可以包含一個構造函數初始化列表: ?
Sales_item::Sales_item(const string &book): isbn(book), units_sold(0), revenue(0.0)
{ }?
? ? ?構造函數初始化列表以一個冒號開始,接著是一個以逗號分隔的數據成員列表,每個數據成員后面跟一個放在圓括號中的初始化式。
? ? ?構造函數可以定義在類的內部或外部。構造函數初始化只在構造函數的定義中指定。
? ? ?構造函數分兩個階段執行:(1)初始化階段;(2)普通的計算階段。初始化列表屬于初始化階段(1),構造函數函數體中的所有語句屬于計算階段(2)。
? ? ?初始化列表比構造函數體先執行。不管成員是否在構造函數初始化列表中顯式初始化,類類型的數據成員總是在初始化階段初始化。
3.1 哪種類需要初始化式
? ? ?const 對象或引用類型的對象,可以初始化,但不能對它們賦值,而且在開始執行構造函數的函數體之前要完成初始化。
? ? ?初始化 const 或引用類型數據成員的唯一機會是構造函數初始化列表中,在構造函數函數體中對它們賦值不起作用。
? ? ?沒有默認構造函數的類類型的成員,以及 const 或引用類型的成員,必須在初始化列表中完成初始化。
class ConstRef?
{?
public:?
? ? ?ConstRef(int ii);?
private:?
? ? ?int i;?
? ? ?const int ci;?
? ? ?int &ri;?
};?
ConstRef::ConstRef(int ii)?
{
? ? ?i = ii; ? // ok?
? ? ?ci = ii; ?// error
? ? ?ri = i; ? //?
}
? ? ?應該這么初始化:
ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }?
3.2 成員初始化的次序
? ? ?每個成員在構造函數初始化列表中只能指定一次。重復初始化,編譯器一般會有提示。
? ? ?成員被初始化的次序就是定義成員的次序,跟初始化列表中的順序無關。
3.3 初始化式表達式
? ? ?初始化式可以是任意表達式
Sales_item(const std::string &book, int cnt, double price): isbn(book), units_sold(cnt), revenue(cnt * price) { }
3.4 類類型的數據成員的初始化式
? ? ?初始化類類型的成員時,要指定實參并傳遞給成員類型的一個構造函數,可以使用該類型的任意構造函數。
Sales_item(): isbn(10, '9'), units_sold(0), revenue(0.0) {}
?3.5 類對象的數據成員的初始化 ? ? ?
? ? ?在類A的構造函數初始化列表中沒有顯式提及的每個成員,使用與初始化變量相同的規則來進行初始化。
? ? ?類類型的數據成員,運行該類型的默認構造函數來初始化。
? ? ?內置或復合類型的成員的初始值依賴于該類對象的作用域:在局部作用域中不被初始化,在全局作用域中被初始化為0。假設有一個類A,
class A
{
? ? public:
? ? ? ? int ia;
? ? ? ? B b;
};
? ? A類對象A a;不管a在局部作用域還是全局作用域,b使用B類的默認構造函數來初始化,ia的初始化取決于a的作用域,a在局部作用域,ia不被初始化,a在全局作用域,ia初始化0。
4 默認構造函數?
? ? ?不含形參的構造函數就是默認構造函數。 ? ??
? ? ?只要定義一個對象時沒有提供初始化式,就使用默認構造函數。如: A a;
? ? ?為所有形參提供默認實參的構造函數也定義了默認構造函數。例如:
class A
{
public:?
? ? ?A(int a=1,char c =''){}
private: ?
? ? ?int ia;
? ? ?char c1;
};
4.1 合成的默認構造函數
? ? ?只有當一個類沒有定義構造函數時,編譯器才會自動生成一個默認構造函數。
? ? ?一個類只要定義了一個構造函數,編譯器也不會再生成默認構造函數。
建議:
? ? ?如果定義了其他構造函數,也提供一個默認構造函數。
? ? ?如果類包含內置或復合類型(如 int& 或 string*)的成員,它應該定義自己的構造函數來初始化這些成員。每個構造函數應該為每個內置或復合類型的成員提供初始化。
?
5 隱式類類型轉換
5.1 只含單個形參的構造函數能夠實現從形參類型到該類類型的一個隱式轉換
class A
{
public:
? ? ?A(int a)
? ? ?{
? ? ? ? ? ia =a;
? ? ?}
?
? ? ?bool EqualTo(const A& a)
? ? ?{
? ? ? ? ? return ia == a.ia;
? ? ?}
?
private:
? ? ?int ia;
};
?
A a(1);
bool bEq = false;
bEq = a.EqualTo(1);//參數為1,實現從int型到A的隱式轉換
?
5.2抑制由構造函數定義的隱式轉換
? ? ?通過將構造函數聲明為 explicit,來防止在需要隱式轉換的上下文中使用構造函數:?
class A
{
public:
? ? ?explicit A(int a )
? ? ?{
? ? ? ? ? ia =a;
? ? ?}
?
? ? ?bool EqualTo(const A& a)
? ? ?{
? ? ? ? ? return ia == a.ia;
? ? ?}
?
private:
? ? ?int ia;
};
? ? ?通常,除非有明顯的理由想要定義隱式轉換,否則,單形參構造函數應該為 explicit。將構造函數設置為 explicit 可以避免錯誤。
?
?
三 復制控制
1 復制構造函數
1.1 幾個要點
(1) 復制構造函數
? ? ?復制構造函數是一種特殊構造函數,只有1個形參,該形參(常用 const &修飾)是對該類類型的引用。
class Peopel
{
public:
? ? ?Peopel();//默認構造函數
? ? ?Peopel(const Peopel&);//復制構造函數
? ? ?~Peopel();//析構函數
};
? ? ?當定義一個新對象并用一個同類型的對象對它進行初始化時,將顯式使用復制構造函數。
Peopel a1; Peopel a2 = a1;
? ? ?當將該類型的對象傳遞給函數或函數返回該類型的對象時,將隱式使用復制構造函數。
Peopel Func(Peopel b){...}
(2)析構函數
? ? ?析構函數是構造函數的互補:當對象超出作用域或動態分配的對象被刪除時,將自動應用析構函數。
? ? ?析構函數可用于釋放構造對象時或在對象的生命期中所獲取的資源。
? ? ?不管類是否定義了自己的析構函數,編譯器都自動執行類中非 static 數據成員的析構函數。
(3) 復制控制
? ? ?復制構造函數、賦值操作符和析構函數總稱為復制控制。編譯器自動實現這些操作,但類也可以定義自己的版本。
(4) 兩種初始化形式
? ? ?C++ 支持兩種初始化形式:直接初始化和復制初始化。直接初始化將初始化式放在圓括號中,復制初始化使用 = 符號。
? ? ?對于內置類型,例如int, double等,直接初始化和復制初始化沒有區別。
? ? ?對于類類型:直接初始化直接調用與實參匹配的構造函數;復制初始化先使用指定構造函數創建一個臨時對象,然后用復制構造函數將那個臨時對象復制到正在創建的對象。直接初始化比復制初始化更快。
(5)形參和返回值
? ? ?當形參或返回值為類類型時,由該類的復制構造函數進行復制。?
(6)初始化容器元素
? ? ?復制構造函數可用于初始化順序容器中的元素。例如:
vector<string> svec(5);
? ? ?編譯器首先使用 string 默認構造函數創建一個臨時值,然后使用復制構造函數將臨時值復制到 svec 的每個元素。?
(7)構造函數與數組元素
? ? ?如果沒有為類類型數組提供元素初始化式,則將用默認構造函數初始化每個元素。
? ? ?如果使用常規的花括號括住的數組初始化列表來提供顯式元素初始化式,則使用復制初始化來初始化每個元素。根據指定值創建適當類型的元素,然后用復制構造函數將該值復制到相應元素:
Sales_item primer_eds[] = { string("0-201-16487-6"),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?string("0-201-54848-8"),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?string("0-201-82470-1"),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Sales_item()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?};
1.2 合成的復制構造函數
(1)合成的復制構造函數
? ? ?如果沒有定義復制構造函數,編譯器就會為我們合成一個。
? ? ?合成復制構造函數的行為是,執行逐個成員初始化,將新對象初始化為原對象的副本。
逐個成員初始化:合成復制構造函數直接復制內置類型成員的值,類類型成員使用該類的復制構造函數進行復制。
例外:如果一個類具有數組成員,則合成復制構造函數將復制數組。復制數組時合成復制構造函數將復制數組的每一個元素。
1.3 定義自己的復制構造函數
(1) 只包含類類型成員或內置類型(但不是指針類型)成員的類,無須顯式地定義復制構造函數,也可以復制。?
class Peopel
{
public:
? ? ?std::string name;
? ? ?unsigned int id;
? ? ?unsigned int age;
? ? ?std::string address;
};
(2) 有些類必須對復制對象時發生的事情加以控制。
? ? ?例如,類有一個數據成員是指針,或者有成員表示在構造函數中分配的其他資源。而另一些類在創建新對象時必須做一些特定工作。這兩種情況下,都必須定義自己的復制構造函數。
? ? ?最好顯式或隱式定義默認構造函數和復制構造函數。如果定義了復制構造函數,必須定義默認構造函數。
?
1.4 禁止復制
? ? ?有些類需要完全禁止復制。例如,iostream 類就不允許復制。延伸:容器內元素不能為iostream?
? ? ?為了防止復制,類必須顯式聲明其復制構造函數為 private。
2 賦值操作符
? ? ?與復制構造函數一樣,如果類沒有定義自己的賦值操作符,則編譯器會合成一個。
(1)重載賦值操作符
Sales_item& operator=(const Sales_item &);
(2)合成賦值操作符
? ? ?合成賦值操作符會逐個成員賦值:右操作數對象的每個成員賦值給左操作數對象的對應成員。除數組之外,每個成員用所屬類型的常規方式進行賦值。對于數組,給每個數組元素賦值。
(3)復制和賦值常一起使用?
? ? ?一般而言,如果類需要復制構造函數,它也會需要賦值操作符。?
3 析構函數
? ? ?構造函數的用途之一是自動獲取資源;與之相對的是,析構函數的用途之一是回收資源。除此之外,析構函數可以執行任意類設計者希望在該類對象的使用完畢之后執行的操作。
(1) 何時調用析構函數
撤銷(銷毀)類對象時會自動調用析構函數。
變量(類對象)在超出作用域時應該自動撤銷(銷毀)。
動態分配的對象(new A)只有在指向該對象的指針被刪除時才撤銷(銷毀)。
撤銷(銷毀)一個容器(不管是標準庫容器還是內置數組)時,也會運行容器中的類類型元素的析構函數(容器中的元素總是從后往前撤銷)。
(2)何時編寫顯式析構函數
? ? ?如果類需要定義析構函數,則它也需要定義賦值操作符和復制構造函數,這個規則常稱為三法則:如果類需要析構函數,則需要所有這三個復制控制成員。
(3)合成析構函數
? ? ?合成析構函數按對象創建時的逆序撤銷每個非 static 成員,因此,它按成員在類中聲明次序的逆序撤銷成員。
? ? ?對于每個類類型的成員,合成析構函數調用該成員的析構函數來撤銷對象。
? ? ?合成析構函數并不刪除指針成員所指向的對象。 所以,如果有指針成員,一定要定義自己的析構函數來刪除指針。
? ? ?析構函數與復制構造函數或賦值操作符之間的一個重要區別:即使我們編寫了自己的析構函數,合成析構函數仍然運行。
?
?
四 友元
? ? ?友元機制允許一個類將對其非公有成員的訪問權授予指定的函數或類。
? ? ?友元可以出現在類定義的內部的任何地方。
? ? ?友元不是授予友元關系的那個類的成員,所以它們不受聲明出現部分的訪問控制影響。
? ? ?建議:將友元聲明成組地放在類定義的開始或結尾。
?
1 友元類
class Husband
{
public:
? ? ?friend class Wife;
private:
? ? ?double money;//錢是老公私有的,別人不能動,但老婆除外
};
?
class Wife
{
public:
? ? ?void Consume(Husband& h)
? ? ?{
? ? ? ? ? h.money -= 10000;//老婆可以花老公的錢
? ? ?}
};
?
Husband h;
Wife w;
w.Consume(h);
?
2 使其他類的成員函數成為友元
class Husband; //1.聲明Husband?
?
class Wife //2.定義Wife類?
{
public:
? ? ?void Consume(Husband& h);
};
?
class Husband //3.定義Husband類
{
public:
? ? ?friend void Wife::Consume(Husband& h);//聲明Consume函數。
private:
? ? ?double money;//錢是老公私有的,別人不能動,但老婆除外
};
?
void Wife::Consume(Husband& h) //4.定義Consume函數。
{
? ? ?h.money -= 10000;//老婆可以花老公的錢
}
注意類和函數的聲明和定義的順序:
(1)聲明類Husband?
(2)定義類Wife,聲明Consume函數
(3)定義類Husband
(4)定義Consume函數。
?
?
五 static 類成員
?
static 成員,有全局對象的作用,但又不破壞封裝。
1 static 成員變量
static 數據成員是與類關聯的對象,并不與該類的對象相關聯。
static 成員遵循正常的公有/私有訪問規則。 ?
2 使用 static 成員而不是全局對象有三個優點。
(1) ?static 成員的名字是在類的作用域中,因此可以避免與其他類的成員或全局對象名字沖突。
(2) ?可以實施封裝。static 成員可以是私有成員,而全局對象不可以。
(3) ?通過閱讀程序容易看出 static 成員是與特定類關聯的,這種可見性可清晰地顯示程序員的意圖。?
3 static 成員函數
? ? ?在類的內部聲明函數時需要添加static關鍵字,但是在類外部定義函數時就不需要了。
? ? ?因為static 成員是類的組成部分但不是任何對象的組成部分,所以有以下幾個特點:
1) static 函數沒有 this 指針
2) static 成員函數不能被聲明為 const (將成員函數聲明為 const 就是承諾不會修改該函數所屬的對象)
3) static 成員函數也不能被聲明為虛函數
4 static 數據成員?
? ? ?static 數據成員可以聲明為任意類型,可以是常量、引用、數組、類類型,等等。
? ? ?static 數據成員必須在類定義體的外部定義(正好一次),并且應該在定義時進行初始化。
建議:定義在類的源文件中名,即與類的非內聯函數的定義同一個文件中。注意,定義時也要帶上類類型+"::"
double Account::interestRate = 0.035;?
5 特殊的靜態常量整型成員?
? ? ?靜態常量整型數據成員可以直接在類的定義體中進行初始化,例如:
static const int period = 30;?
? ? ?當然char 可以轉換成整形,也是可以的, ? static const char bkground = '#';
?
6 其他
(1)static 數據成員的類型可以是該成員所屬的類類型。非 static 成員只能是自身類對象的指針或引用?
class Screen?
{
public:
? ? ? ? ?// ...
private:
? ? ? ? ?static Screen src1; // ok
? ? ? ? ?Screen *src2; ? ? ? // ok
? ? ? ? ?Screen src3; ? ? ? ?// error
};?
(2)非 static 數據成員不能用作默認實參,static 數據成員可用作默認實參
class Screen?
{
public:
? ? ? ? ? Screen& clear(char = bkground);
private:
? ? ? ? ?static const char bkground = '#';//static const整形變量可以在類內部初始化。
};
========
C++創建對象的三種方式
http://blog.csdn.net/azhexg/article/details/14225545C++中有三種創建對象的方法
#include <iostream> ?
using namespace std; ?
class A ?
{ ?
private: ?
? ? int n; ?
public: ?
? ? A(int m):n(m) ?
? ? { } ?
? ? ~A(){} ?
}; ?
int main() ?
{ ?
? ? A a(1); ?//棧中分配 ?
? ? A b = A(1); ?//棧中分配 ?
? ? A* c = new A(1); ?//堆中分配 ?
delete c; ?
? ? return 0; ?
} ?
第一種和第二種沒什么區別,一個隱式調用,一個顯式調用,兩者都是在進程虛擬地址空間中的棧中分配內存,而第三種使用了new,在堆中分配了內存,而棧中內存的分配和釋放是由系統管理,而堆中內存的分配和釋放必須由程序員手動釋放。采用第三種方式時,必須注意一下幾點問題:
new創建類對象需要指針接收,一處初始化,多處使用
new創建類對象使用完需delete銷毀
new創建對象直接使用堆空間,而局部不用new定義類對象則使用棧空間
new對象指針用途廣泛,比如作為函數返回值、函數參數等
頻繁調用場合并不適合new,就像new申請和釋放內存一樣
棧的大小遠小于堆的大
棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率 比較高。堆則是C/C++函數庫提供的,它的機制是很復雜的,例如為了分配一塊內存,庫函數會按照一定的算法(具體的算法可以參考數據結構/操作系統)在 堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會 分 到足夠大小的內存,然后進行返回。顯然,堆的效率比棧要低得多。
========
C++用new來創建對象和非new來創建對象的區別
http://www.cnblogs.com/GODYCA/archive/2013/01/10/2854777.html我們都知道C++中有三種創建對象的方法,如下:
#include <iostream>
using namespace std;
class A
{
private:
? ? int n;
public:
? ? A(int m):n(m)
? ? {
? ? }
? ? ~A(){}
};
int main()
{
? ? A a(1); ?//棧中分配
? ? A b = A(1); ?//棧中分配
? ? A* c = new A(1); ?//堆中分配
delete c;
? ? return 0;
}
第一種和第二種沒什么區別,一個隱式調用,一個顯式調用,兩者都是在進程虛擬地址空間中的棧中分配內存,而第三種使用了new,在堆中分配了內存,而棧中內存的分配和釋放是由系統管理,而堆中內存的分配和釋放必須由程序員手動釋放,所以這就產生一個問題是把對象放在棧中還是放在堆中的問題,這個問題又和堆和棧本身的區別有關:
這里面有幾個問題:
1.堆和棧最大可分配的內存的大小
2.堆和棧的內存管理方式
3.堆和棧的分配效率
首先針對第一個問題,一般來說對于一個進程棧的大小遠遠小于堆的大小,在linux中,你可以使用ulimit -s (單位kb)來查看一個進程棧的最大可分配大小,一般來說不超過8M,有的甚至不超過2M,不過這個可以設置,而對于堆你會發現,針對一個進程堆的最大可分配的大小在G的數量級上,不同系統可能不一樣,比如32位系統最大不超過2G,而64為系統最大不超過4G,所以當你需要一個分配的大小的內存時,請用new,即用堆。
其次針對第二個問題,棧是系統數據結構,對于進程/線程是唯一的,它的分配與釋放由操作系統來維護,不需要開發者來管理。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時,這些存儲單元會被自動釋放。棧內存分配運算內置于處理器的指令集中,效率很高,不同的操作系統對棧都有一定的限制。 堆上的內存分配,亦稱動態內存分配。程序在運行的期間用malloc申請的內存,這部分內存由程序員自己負責管理,其生存期由開發者決定:在何時分配,分配多少,并在何時用free來釋放該內存。這是唯一可以由開發者參與管理的內存。使用的好壞直接決定系統的性能和穩定。
由上可知,但我們需要的內存很少,你又能確定你到底需要多少內存時,請用棧。而當你需要在運行時才知道你到底需要多少內存時,請用堆。
最后針對第三個問題,棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率 比較高。堆則是C/C++函數庫提供的,它的機制是很復雜的,例如為了分配一塊內存,庫函數會按照一定的算法(具體的算法可以參考數據結構/操作系統)在 堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會 分 到足夠大小的內存,然后進行返回。顯然,堆的效率比棧要低得多。
由上可知,能用棧則用棧。
復制代碼
#include <stdio.h>
#include <stdlib.h> ?
void main()
{
?int n,*p,i,j,m;
?printf("本程序可對任意個整數排序;\n");
?printf("請輸入整數的總個數: ");
?scanf("%d",&n);
?p=(int *)calloc(n,sizeof(int)); ? ?//運行時決定內存分配大小
?if(p==0) ? {
? printf("分配失敗!\n"); ?
? exit(1); ?
?}
========
總結
以上是生活随笔為你收集整理的C++类和对象学习总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL CTE学习总结
- 下一篇: C++内联函数学习总结