1. 程式人生 > 其它 >C++基礎-類與物件(1)

C++基礎-類與物件(1)

C++類與物件(1)

類的設計:可以把屬性和行為放在不同的許可權下

struct和class區別在於某人的訪問許可權不同

  • struct:預設共有
  • class:預設私有

物件的初始化和清理

如果我們不寫,系統會自己給我沒寫

  • 建構函式的語法 類名(){}

沒有返回值,也不寫void

函式名和型別相同

可以有參,也可以無參

在呼叫物件會自動呼叫函式,無需手動呼叫,只調用一次

  • 解構函式語法 ~類名(){}

同上,不過是無參(不可以過載),銷燬時自動呼叫函式,只調用一次

建構函式的分類與呼叫

分類

  • 有參和無參(預設)
  • 普通和拷貝

拷貝函式

函式名(const 函式名 &p(物件)){
    age = p.age;
}
#include<iostream>           //建構函式和解構函式
using namespace std;
class person{
public:
	int age;
public:
	//建構函式
	//普通
	person(){
		cout << "Person無參建構函式的呼叫" << endl;
	}
	person(int a){
		age = a;
		cout << "Person有參建構函式的呼叫" << endl;
	}
	//拷貝
	person(const person &p){
		age = p.age;
		cout << "Person拷貝建構函式的呼叫" << endl;
	}
	//解構函式
	~person(){
		cout << "Person解構函式的呼叫" << endl;
	}
};

void test01(){
	//呼叫
	//1.括號法
	//person p1;
	//person p2(10);
	//person p3(p2);
	//注:呼叫預設建構函式時,不要加()(系統會以為是一個函式的宣告)
	
	//cout << "p2的年齡:" << p2.age << endl;
	//cout << "p3的年齡:" << p3.age << endl;
	
	//2.顯示法
	person p4;
	person p5 = person(10);//右值:匿名物件,當前行結束,系統收回匿名物件
	person p6 = person(p5);
	//注:不要利用拷貝函式初始化匿名物件
	
	
	//3.隱式轉換法
	person p7 = 10;//相當於person p5 = person(10);
	person p8 = p7;
}

int main(){
	test01();
 return 0;
}*/

拷貝函建構函式的呼叫時機

c++中呼叫拷貝函式一般三種請況

  • 使用一個已經建立完畢的物件來初始化一個物件
  • 值傳遞的方法給函式引數傳值
  • 以值方式返回區域性物件
#include<iostream>          //拷貝時機
using namespace std;

class Person{
private:
	int m_Age;	
public:	
	Person(){
		cout << "person的預設無參建構函式呼叫" << endl;
	}
	
	Person(int age){
		cout << "Person有參建構函式呼叫" << endl;
		m_Age = age;	
	}
	
	Person(const Person &p){
		cout << "Person的拷貝呼叫" << endl;
		m_Age =p.m_Age;	
	}
	
	~Person(){
		cout << "person的解構函式呼叫" << endl;
	}	
};

void test01(){
	Person p1 = Person(20);
	Person p2(p1);	
}

void dowork(Person p){	}
void test02(){
	Person p;
	dowork(p);	
}

Person dowork2(){
	Person p1;
	cout << (int*)&p1 << endl;  //輸出地址
	return p1;
}
void test03(){
	Person p = dowork2();
	cout << (int*)&p << endl;	
}

int main(){
	test01();
    cout << endl;
	test02();
    cout << endl;
	test03();
 return 0;
}

建構函式的呼叫規則

預設情況下,C++會至少給一個類新增3個函式

  • 預設建構函式無參,函式體為空
  • 預設解構函式無參,函式體為空
  • 預設拷貝建構函式,對屬性進行拷貝(所有的屬性都進行賦值操作)

規則如下:

  • 如果使用者定義了有參建構函式,C++不在提供預設無參構造,但會提供預設的拷貝
  • 如果使用者定義了拷貝函式,C++不h會提供其他建構函式

注意:若只定義了拷貝(只有參同理),則

Person p1;
//和
Person p1(20);
//均是錯誤的(因為系統不會提供)

深拷貝和淺拷貝

淺拷貝:簡單的賦值拷貝操作(如果對其進行釋放,則堆區的記憶體會重複釋放,出現錯誤)

深拷貝:在堆區重新申請空間,進行拷貝操作

m_height = new int(*P.m_height); //new返回的值是地址

private:
int * m_height;  //地址(指標)型別

初始化列表

作用:用來初始化屬性

語法:

建構函式():屬性1(值1),屬性2(值2)...{}

來個淺例吧

#include <iostream>
using namespace std;

class Person{	
public:
	//傳統初始化
//	Person(int a,int b,int c){
//		m_A = a;
//		m_B = b;
//		m_C = c;
//	}

    //初始化列表
	Person(int a,int b,int c):m_A(a),m_B(b),m_C(c){} //引數a,b,c可以自由的改變所賦的值
	
    int m_A;
    int m_B;
    int m_C;
};

void test01(){
	Person p(30,20,10);
	//Person p;
	cout << "m_A:" << p.m_A << endl;
	cout << "m_B:" << p.m_B << endl;
	cout << "m_C:" << p.m_C << endl;
}

int main(int argc, char** argv) {
	test01();	
 return 0;
}

結果是30 20 10

類物件作為類的成員

C++類中的成員可以是另一個類的物件,我們成該成員為物件成員

例如:

class A{};
class B{
    A a; 
}
//敲黑板(好老的梗...)
//先構造A的物件(即先構造其他類的物件),再構造B的物件
//析構的順序是相反的,先析構本類,再析構其他類

程式碼的簡單例子

#include <iostream>
using namespace std;
#include<cstring>  //要用字串呢

class Phone{ //類一
public:
	//品牌名字
	string m_Pname;
	
	Phone(string name){
		m_Pname = name;
	}
};

class Person{ //類二
public:
	//姓名
	string m_Name;
	//手機
	Phone m_Phone;  //類一作為類二的成員
	
	Person(string Name,string Pname):m_Name(Name),m_Phone(Pname){}
		                                      //相當於Phone m_Phone = Pname = Phone(Pname)(隱式轉化)
};

void test01(){
	Person p("張三","華為");
	cout << p.m_Phone.m_Pname;
}

int main(int argc, char** argv) {
	test01();	
 return 0;
}

結果:華為

靜態成員函式

靜態成員變數就是加上const

靜態成員分成:

  1. 靜態成員變數
    • 所有物件共享一份資料
    • 再編譯階段分配記憶體
    • 類內宣告,類外初始化
  2. 靜態成員函式
    • 所有物件共享一個函式
    • 靜態成員函式只能訪問靜態成員變數(函式體內無法區分普通變數是那個物件的成員)
    • 也是有訪問許可權的,private下在類外就訪問不到
#include <iostream>
using namespace std;
class Person{
public:
	//靜態成員函式
	void static func(){
		m_a = 100;//靜態成員函式訪問靜態成員變數
		cout << "func的呼叫" << m_a << endl;
	}
	static int m_a;//類內宣告類外初始化
};
int Person::m_a = 0;

//兩種訪問方式
void test01(){
    
	//通過物件訪問
	Person p;
	p.func();
	
	//通過類名訪問
	Person::func();
}

int main(int argc, char** argv) {
	test01();	
	return 0;
} 

結果

物件模型和this指標

成員變數和成員函式分開儲存

  • 只有非靜態成員變數才屬於類的物件上面

  • 空物件佔用一個位元組

C++編譯器會給每個空物件分配一個位元組的空間(獨一無二的記憶體地址),防止區分空物件佔記憶體的位置

#include <iostream>
using namespace std;
class Person1{};
class Person2{
    int m_a;//非靜態成員變數,屬於類的物件上
	static int m_b;//靜態成員變數,不屬於類的物件上
	void test01(){}//非靜態成員函式,不屬於類的物件上
	static void test02(){}//靜態成員函式,不屬於類的物件上  
};
int Person2::m_b = 0;

void test01(){//空物件所佔用的記憶體
	Person1 p1;
	cout << "p1 sizeof of p is " << sizeof(p1) << endl;	
}

void test02(){//非空物件佔用的記憶體
	Person2 p2;
	cout << "p2 sizeof of p is " << sizeof(p2) << endl;	
}
	
int main(int argc, char** argv) {
	test01();
	test02();
	return 0;
} 

this指標

引子:在上面我們知道,非靜態的成員函式只會生成一份函式例項,也是是說多個同類的物件會公用一塊程式碼(一個函式),那麼:這一塊程式碼是如何區分是那個物件呼叫自己呢?

通過this指標來解決上面的問題,this指標指向被呼叫的成員函式所屬的物件(eg:p1呼叫就指向p1...)

  • this指標是隱含在每一個非靜態成員函式內的一種指標,不用定義,直接使用

用途

  1. 當形參和成員變數重名時,可用this來區分
  2. 在類的非靜態成員函式返回物件本身(return *this;)
#include <iostream>
using namespace std;
class Person1{//名稱衝突
public:
	Person1(int age,int age1){
		this->age = age;
		age1 = age1;
	}
    
	int age;
	int age1;
	
	Person1 & Add(Person1 &p){
		this->age += p.age;
		//this是一個指向p3的指標,*this就是物件p3的本體
		return *this;	
	}
};

void test01(){
	Person1 p1(18,18);
	cout << "p1的年齡是" << p1.age << endl;
	cout << "p1的年齡是" << p1.age1 << endl;	
}
void test02(){//把p2的年齡加到p3上
	Person1 p2(10,10);
	Person1 p3(10,10);
	p3.Add(p2).Add(p2);//鏈式程式設計思想
	cout << "p3的年齡是" << p3.age << endl;
}

int main(int argc, char** argv) {
	test01();
	test02();
	return 0;
} 

結果

空指標訪問成員函式

C++中允許空指標呼叫成員函式的,但是也要注意有沒有用到this地址

如果用到this指標,則需要加以判斷確保程式碼的健壯性

if(this == NULL) return;

看個小例子吧

#include <iostream>
using namespace std;
class Person{
public:
	void show(){
		cout << "show的呼叫" << endl;
	}
	
	int m_age;
	void get(){
		if(this == NULL) return;
		cout << "age=" << m_age << endl;
		              //預設this->m_age
	}
};


void test01(){
	Person * p = NULL;
	//空指標可以訪問成員
    p->show();
	p->get();	
}
	
int main(int argc, char** argv) {
	test01();
	return 0;
} 

const修飾成員函式

常函式

  • 不可以修改成員屬性
  • 成員屬性宣告時+mutable關鍵字,在常函式中就可以修改了
class Person{
public:
	//this本質 指標常量 Person * const this 指向不可以改變
	//在成員函式後面+const <=>const Person * const this,讓指標指向的值不可以改變
	void show() const{ 
		m_a =100;//所以會報錯哦
		//其實是this->m_a = 100;
	}
	int m_a;
	
};

常物件

  • 常物件只能呼叫常函式
const Person p;
p.show();

友元

在程式裡,有些私有的屬性想讓類外的特殊的一些函式或者類進行呼叫,就需要友元技術

作用(目的):讓一個函式或是類訪問另一個類中的私有成員

友元關鍵字:friend(友元:不是類的成員,不受訪問限制)

友元的三種實現

  • 全域性函式友元
  • 類做友元
  • 成員函式做友元

全域性函式做友元

#include <iostream>
using namespace std;
#include<cstring>
class Building{
	//goodFriend是Building類的好朋友,可以訪問啦
	friend void goodFriend(Building &building);
    
public:
	Building(){
		SittingRoom = "客廳";
		BedRoom = "臥室";
	}
public:
	string SittingRoom;//客廳	
private:
	string BedRoom;//臥室	
};

//全域性函式
void goodFriend(Building &building){
	cout << "友元全域性函式 正在訪問:" << building.SittingRoom << endl;
	cout << "友元全域性函式 正在訪問:" << building.BedRoom << endl;
}

void test01(){
	Building building;
	goodFriend(building);
}
	
int main(int argc, char** argv) {
	test01();
	return 0;
} 


結果

類做友元

大致流程:

  • 先建立GoodFriend類的物件GF
  • 呼叫本類下的建構函式:建立一個Building(同時呼叫Building的建構函式)
  • 訪問visit()函式,就可以訪問building下的成員啦
#include <iostream>
using namespace std;
#include<cstring>

class Building{
	//GoodFriend類是Building類的好朋友
	friend class GoodFriend;
    
...//和上面一樣
};

class GoodFriend{
public:
	GoodFriend(){
		//建立物件
		building = new Building;
	}
    
	void visit(){//參觀函式 訪問Building中的屬性
		cout << "友元正在訪問:" << building->SittingRoom << endl;
		cout << "友元正在訪問:" << building->BedRoom << endl;
	}
    
	Building * building;
};

void test01(){
	GoodFriend GF;
	GF.visit();
}
	
int main(int argc, char** argv) {
	test01();
	return 0;
} 


結果

成員函式做友元

流程與上面的幾乎一樣

#include <iostream>
using namespace std;
#include<cstring>

class Building;//防止在未建立BUilding類是報錯
class GoodFriend{
public:
    Building * building;
	GoodFriend();
	void visit();//參觀函式 訪問Building中的私有成員
};

class Building{
    //visit()做為BUilding類的好朋友
	friend void GoodFriend::visit();
    
public:
	string SittingRoom;//客廳
private:
	string BedRoom;//臥室
    
public:
	Building();
};

//類外宣告
Building::Building(){
	SittingRoom = "客廳";
	BedRoom = "臥室";
}

GoodFriend::GoodFriend(){
	building = new Building;
}
void GoodFriend::visit(){//參觀函式 訪問Building中的私有成員
	cout << "友元正在訪問:" << building->SittingRoom << endl;
	cout << "友元正在訪問:" << building->BedRoom << endl;
}

void test01(){ //測試函式
	GoodFriend GF;
	GF.visit();	
}
	
int main(int argc, char** argv) {
	test01();
	return 0;
} 


結果:

運算子的過載

概念:對已有運算子重新進行定義,賦予其另一種功能,以適應不同的資料型別

對於內建的資料型別,系統知道如何進行運算

加號運算子過載(其他同理)

  1. 成員函式過載+號

本質:Person p3 = p1.operator+(p2);

#include <iostream>
using namespace std;
class Person{
public:
	int m_A;
	int m_B;
/*======================================================*/
	Person operator+(Person &p){
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}
/*======================================================*/
};

void test01(){
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;
	Person p2;
	p2.m_A = 10;
	p2.m_B = 10;
	
	Person p3 = p1 + p2;
	
	cout << p3.m_A <<endl;
	cout << p3.m_B <<endl;	
}

int main(int argc, char** argv) {
	test01();	
 return 0;
}

結果是兩個20(相加成功)

  1. 全域性函式過載+號

本質:Person p3 = operator+(p1,p2);

#include <iostream>
using namespace std;
class Person{
public:
	int m_A;
	int m_B;	
};
/*======================================================*/
Person operator+(Person &p1,Person &p2){
	Person temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
/*======================================================*/
int main(int argc, char** argv) { //函式和上面的一樣
	test01();	
 return 0;
}

結果也是兩個20

注意:- 運算子的過載也可以發生函式過載(名字相同,引數不同)

       - 不可以改變內建運算子

左移運算子的過載(<<)

只能利用全域性函式過載左移運算子

作用:輸出自定義的資料型別

本質:operator<<(cout,p) => cout << p

#include <iostream>
using namespace std;
class Person{
public:
	int m_A;
	int m_B;
};
/*======================================================*/
ostream & operator<<(ostream &out,Person &p){
	out << "m_A:" << p.m_A << '\t' << "m_B:" << p.m_B << endl;
	return out;
}
/*======================================================*/
void test01(){
	Person p;
	p.m_A = 10;
	p.m_B = 10;
	cout << p << endl;
}

int main(int argc, char** argv) {
	test01();	
 return 0;
}

結果:m_A:10 m_B:10

類的成員變成私有:用友元

class Person{
     friend ostream & operator<<(ostream &out,Person &p);
public:
	int m_A;
	int m_B;
};

遞增運算子過載(++)

前置返回引用,後置返回值

  • 前置遞增
 #include <iostream>
 using namespace std;
 class MyInteger{//自定義的整型
   friend ostream & operator<<(ostream &out,MyInteger &p);
 public:
   MyInteger(){
     m_Num = 0;
   }
   /*======================================================*/
   MyInteger & operator++(){//返回引用是為了一直對一個數據操作
     m_Num++;
     return *this;
   }
   /*======================================================*/
 private:
   int m_Num;
 };

 ostream & operator<<(ostream &out,MyInteger &p){
   out << p.m_Num;
   return out;
 }

 void test01(){
   MyInteger myint;
   cout << "myint:" << ++(++myint) << endl;
   cout << "myint:" << myint << endl;
 }

 int main(int argc, char** argv) {
   test01();  
 return 0;
 } 

結果1:myint:2(換行)myint:2

  • 後置遞增
#include <iostream>
using namespace std;
class MyInteger{//自定義的整型
	friend ostream & operator<<(ostream &out,const MyInteger &p);
public:
	MyInteger(){
		m_Num = 0;
	}  
	/*======================================================*/
	MyInteger  operator++(int){//int 代表佔位引數,可以用於區分前置和後置遞增
		//先記錄
		MyInteger temp = *this;
		//後遞增
		m_Num++;
		//再返回
		return temp;
	}
	/*======================================================*/
private:
	int m_Num;
};
ostream & operator<<(ostream &out, const MyInteger &p){//這裡加了const,否則在test02()的輸出會有問題
	out << p.m_Num;
	return out;
}

void test02(){
	MyInteger myint;
	cout << "myint:" << myint++ << endl;
	cout << "myint:" << myint << endl;
}

int main(int argc, char** argv) {
	test02();
	return 0;
} 

結果2:myint:0(換行)myint:1

賦值運算子過載

補充建構函式呼叫規則,一個類至少4個函式

  • 第四個:賦值運算子operator=,對屬性進行拷貝

p2 = p1的問題:堆區重複釋放,和淺拷貝的問題是一樣的

#include <iostream>
using namespace std;
class Person{
public:
	int *m_age;//開闢到堆區
	
	Person(int age){
		m_age = new int(age);
	}
    
	~Person(){
		if(m_age != NULL){
			delete m_age;
			m_age = NULL;
		}	
	}
    /*======================================================*/
    Person & operator=(Person &p){//和深拷貝幾乎是一樣的,返回值是引用是要滿足連等
		//先判斷左值是否有屬性在堆區,如果有,先釋放乾淨,再深拷貝
		if(m_age != NULL){
			delete m_age;
			m_age = NULL;		
		} 
		m_age = new int(*p.m_age);
		//返回物件本身
		return *this;
	}
};
/*======================================================*/
void test01(){
	Person p1(10);
	Person p2(20);
	Person p3(30);
	p3 = p2 = p1;//賦值操作
	cout << "p1的年齡是:" << *p1.m_age << endl;
	cout << "p2的年齡是:" << *p2.m_age << endl;
	cout << "p3的年齡是:" << *p3.m_age << endl;
}

int main(int argc, char** argv) {
	test01();
	return 0;
} 


結果:p1,p2,p3都是10

關係運算符的過載(>/<...)

  • ==的過載(!=同理)
#include <iostream>
using namespace std;
class Person{
public:
	string m_name;
	int m_age;
	
	Person(string name,int age){
		m_name = name;
		m_age = age;
	}
	~Person(){}
	/*======================================================*/
	bool operator==(Person &p){
		if(this->m_name == p.m_name&&this->m_age == p.m_age){
			return true;	
		}else{
			return false;
		}
	}
    /*======================================================*/
};
void test01(){
	Person p1("Tom",18);
	Person p2("Tom",18);
	if(p1 == p2) cout << "p1和p2相等" << endl;
	else cout << "p1和p2不相等" << endl;
}
	
int main(int argc, char** argv) {
	test01();
	return 0;
} 

結果:p1和p2相等

函式呼叫運算子過載

  • 函式呼叫運算子()也可以過載
  • 由於過載後使用的方式非常像函式的呼叫,因此也稱謂仿函式
  • 仿函式沒有固定的寫法,很靈活
#include <iostream>//寫了兩個...
using namespace std;
#include<cstring>
class Myprint{//列印類
public:
    /*======================================================*/
	void operator()(string test){
		cout << test <<endl;
	}
    /*======================================================*/
};

class MyAdd{//加法類
	public:
    /*======================================================*/
	int operator()(int a,int b){
		return a+b;	
	}
    /*======================================================*/
};

void test01(){
	Myprint myPrint;
	myPrint("hello word");
    
	MyAdd myAdd;
	int c = myAdd(1,2);
	cout << c << endl;
    
	//匿名物件
	cout << MyAdd()(1,1) << endl;
}
	
int main(int argc, char** argv) {
	test01();
	
	return 0;
} 

結果:hello word(換行)3(換行)2