類:class(數據+函數)
類是一種複雜的數據類型,它是將不同類型的數據和與這些數據相關的操作封裝在一起的集合體。這有點像C語言中的結構,唯一不同的就是結構沒有定義所說的“數據相關的操作”,“數據相關的操作”就是我們平常經常看到的“方法”,因此,類具有更高的抽象性,類中的數據具有隱藏性,類還具有封裝性。
1. public成員可從類外部直接訪問,private(只能在類的裏面進行訪問)/protected成員不能從類外部直接訪問。
2. 每個限定符在類體中可使用多次,它的作用域是從該限定符出現開始到下一個限定符之前或類體結束前。
3. 類體中如果沒有定義限定符,則默認爲私有的。
4. 類的訪問限定符體現了面向對象的封裝性。
類的一般定義格式如下:
class <類名>
{
public: //三種訪問限定符
private:
protected:
};
<各個成員函數的實現>
類的作用域:
1. 每個類都定義了自己的作用域,類的成員(成員函數/成員變量)都在類的這個作用域內,成員函數內可任意訪問成員變量和其它成員 函數。
2. 對象可以通過 . 直接訪問公有成員,指向對象的指針通過 -> 也可以直接訪問對象的公有成員。
3. 在類體外定義成員,需要使用 :: 作用域解析符指明成員屬於哪個類域。
類內聲明定義和類聲明類外定義:(二者取一即可)
class Student
{
public:
char* _name;
char* _sex;
int _tell;
//void Display(); //類內聲明
void Display() //類內聲明定義
{
cout<<_name<<" "<<_sex<<" "<<_tell<<" "<<endl;
}
private:
/*char* _name; //私有域內的對象在類外是直接訪問不到的,這體現了c++的封裝性(可通過其他方式訪問)
char* _sex;
int _tell;*/
protected:
};
/*void Student:: Display() //類外定義
{
cout<<_name<<" "<<_sex<<" "<<_tell<<" "<<endl;
}*/
int main()
{
Student a1;
a1._name = "zhang";
a1._sex = "男";
a1._tell = 188;
a1.Display();
system("pause");
return 0;
}
class Student
{
public:
char _name;
char _sex;
int _tell;
void Display()
{
cout<<_name<<" "<<_sex<<" "<<_tell<<" "<<endl;
}
};
class Student
{
public:
char* _name;
char* _sex;
int _tell;
void Display()
{
cout<<_name<<" "<<_sex<<" "<<_tell<<" "<<endl;
}
};
通過類裏面對象的變化和運算結果可以看出:
1:類的大小隻和對象有關,與成員函數無關。
2:求類的大小是存在字節對齊現象(就像結構體的字節對齊)。
爲什麼要字節對齊?
簡單的說就是用空間換時間。犧牲一部分內存讓程序運行的更快。因爲cpu每次訪問以某個數的整數倍進行讀取數據。內存對齊是方便cpu工作。
當類裏面什麼也沒有定義的時候計算出來的大小是多少?
通常情況下是1。爲甚麼不是0呢?我們可以這樣思考,如果是0就代表這個類不存在,然而事實是存在的,只是沒有定義對象,那麼就讓1代表這個類是存在的。
隱含this指針:
1. 每個成員函數都有一個指針形參,它的名字是固定的,稱爲this指針,this指針是隱式的。(構造函數比較特殊,沒有這個隱含this形 參)
2. 編譯器會對成員函數進行處理,在對象調用成員函數時,對象地址作實參傳遞給成員函數的第一個形參this指針。
3. this指針是成員函數隱含指針形參,是編譯器自己處理的,我們不能在成員函數的形參中添加this指針的參數定義,也不能在調用時 顯示傳遞對象的地址給this指針。
class Student { public: char* _name; char* _sex; int _tell; void Display() //void Display(student* this) (this指針指向調用該函數的對象) { cout<<_name<<" "<<_sex<<" "<<_tell<<" "<<endl; //cout<<this->_name<<" "<<this->_sex<<" "<<this->_tell<<endl; } };
函數中的6個默認成員函數:
1構造函數:
2拷貝構造函數:
3析構函數:
4賦值運算符的重載:
5取地址操作符的重載:
6const修飾取地址操作符的重載:
構造函數:
1. 函數名與類名相同。
2. 無返回值。
3. 對象構造(對象實例化)時系統自動調用對應的構造函數。
4. 構造函數可以重載。
5. 構造函數可以在類中定義,也可以在類外定義
6. 如果類定義中沒有給出構造函數,則C++編譯器自動產生一個缺省的構造函數,但只要我們定義了一個構造函數,系統就不會自動 生成缺省的構造函數。
7. 無參的構造函數和全缺省值的構造函數都認爲是缺省構造函數,並且缺省的構造函數只能有一個。
class Date
{
public:
//構造函數,給對象分配空間,初始化
//無參構造函數
Date()
{}
//帶參構造函數
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//全缺省構造函數
Date(int year = 2008, int month = 8, int day = 8)//如果不傳參則默認缺省參數,如果傳參則用傳遞參數
{
_year = year ;
_month = month ;
_day = day ;
}
//半缺省構造函數
Date(int year, int month = 8, int day = 8) //需要將未缺省參數(year)傳入,其他參數不傳則默認,傳則利用傳入參數。
{
_year = year;
_month = month;
_day = day;
}
void Display() //類內聲明定義
{
cout<<_year<<" "<<_month<<" "<<_day<<" "<<endl;
}
private :
int _year ;
int _month ;
int _day ;
};
拷貝構造:(利用同類對象進行初始化的特殊拷貝構造)
1. 拷貝構造函數其實是一個構造函數的重載。
2. 拷貝構造函數的參數必須使用引用傳參,使用傳值方式會引發無窮遞歸調用。(思考爲什麼?)
3. 若未顯示定義,系統會默認缺省的拷貝構造函數。缺省的拷貝構造函數會,依次拷貝類成員進行初始化。
class Date1
{
public:
Date1(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//拷貝構造
Date1(const Date1& d) //參數中包含一個隱含的this指針,指向目的對象
{
_year = d._year; //this->_year = d._year;
_month = d._month; //this->_month = d._month;
_day = d._day; //this->_day = d._day;
}
void Display()
{
cout<<_year<<" "<<_month<<" "<<_day<<" "<<endl;
}
private:
int _year ;
int _month ;
int _day ;
};
void test1()
{
Date1 d1(2017,7,1); //構造
d1.Display();
Date1 d2(d1); //拷貝構造
d1.Display();
Date1 d3 = d2; //拷貝構造 (d2(d1)和 d3 = d2都是拷貝構造,形式不同,意義一樣)
d1.Display();
}
int main()
{
test1();
system("pause");
return 0;
}
析構函數:(當一個對象生命週期結束時就調用析構函數來銷燬這個函數,構造函數(包括拷貝構造)運行多少次,析構函數就要運行 多少次)
1. 析構函數在類名加上字符~。
2. 析構函數無參數無返回值。
3. 一個類有且只有一個析構函數。若未顯示定義,系統會自動生成缺省的析構函數。
4. 對象生命週期結束時,C++編譯系統系統自動調用析構函數。
5. 注意析構函數體內並不是刪除對象,而是做一些清理工作。
class Date2 {
public :
// 默認的析構函數 如果程序中存在動態開闢的空間需要自己重新寫析構函數
~Date2()
{}
private :
int _year ;
int _month ;
int _day ;
};
運算符重載:(拷貝構造和賦值運算符重載很相像但還是有區別)
1. operator+ 合法的運算符 構成函數名(重載<運算符的函數名:operator< )。
2. 重載運算符以後,不能改變運算符的優先級/結合性/操作數個數。
//拷貝構造
Date2(const Date2& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//賦值運算符重載
Date2& operator=(const Date2& d)
{
if(this != &d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this;
}
二者區別:
拷貝構造函數是創建的對象,使用一個已有對象來初始化這個準備創建的對象。(用一個存在的對象去初始化一個不存在的對象)
賦值運算符的重載是對一個已存在的對象進行拷貝賦值。(兩個對象都存在)
Date2 d1;
Date2 d2 = d1; //拷貝構造
Date2 d3;
d3 = d1; //賦值運算符重載