c++之類和對象快速入門

c++ 類和對象

訪問修飾符

訪問修飾符 類內部 子類 普通外部類
public
protected ×
private × ×

class和struct比較

  • 默認訪問權限

    • struct默認爲public
  • class默認爲private

構造函數和析構函數

構造函數

語法

類名(){}

  • 構造函數可以重載
  • 構造函數系統自動調用
class Person{
    //無參構造函數
   	Student();
    //有參構造函數
	Student(string name, int age);
	//有參構造函數
	Student(int age);
    //拷貝構造函數
	Student(const Student& stu);
    
    string toString();
	//析構函數
    ~Student();
}

分類

  • 有參數
  • 無參數
  • 拷貝構造函數
//拷貝構造函數舉例

Person(const Person& p){
    this->age = stu.age;
	this->name = stu.name;
}

//拷貝構造函數調用情景

//1.使用已存在對象初始化新對象
Person p1("xiaohong",14);
Person p2(p1);


//2.函數值傳遞複製參數的時候會調用拷貝構造函數
void func(Person p){
    
}
int main(){
    Person p1("xiaohong",14);
    func(p1);
}

//3. 值方式返回局部對象
Person func(){
    Person p("Lisi",20);
    // 返回的Person對象是P的拷貝
    return p;
    
}
int main(){
    Person p1("xiaohong",14);
    Person p2=func();
}
  • 編譯器默認有 無參數構造函數、析構函數和拷貝複製函數
  • 寫了有參數構造函數之後,不再提供無參數構造函數,但是提供拷貝構造函數
  • 寫了拷貝構造函數之後,不再提供其他構造函數(包括默認無參數構造函數)

初始化列表

<類名():參數1(初始值)>[,參數2(初始值),...]

代碼示例

//
Person():age(12),name("Lisi"){
    
}

Person(){
    this->age = 12;
	this->name = "Lisi";
}


//

Person(string myName , int myAge):name(myName),age(myAge){
    
}

Person((string myName , int myAge){
    this->age = myAge;
	this->name = myName;
}

3種調用方法

  • 括號法
Person p1; //無參數構造函數,不要加括號
Person p1(); //編譯器會認爲這是一個函數p1()的聲明
Person p2("xiaohng",10); //有參數構造函數
Person p3(p2); //拷貝構造函數
  • 顯式法
Person p1;//無參數構造函數
Person p1 = Person("xiaohng",10);//有參數構造函數
Person p3 = Person(p2);//拷貝構造函數

//相關知識點  匿名對象
 Person("xiaohng",10); 

//不要使用拷貝函數 初始化匿名對象
//示例

Person(p3); //報錯,Person p3重定義
//原因: Person(p3)語句 相當於 Person p3; 
  • 隱式法
Person p1; //無參數構造函數
Person p2 = 10; //有參數構造函數== Person p2(10)
Person p3 =p2; //拷貝構造函數==Person(p2)

析構函數

  • 語法 ~類名(){}
  • 析構函數沒有參數
  • 對象釋放時自動調用

成員變量和成員函數

  • 成員變量和成員函數分開存儲
  • 只有非靜態變量才屬於對象

代碼說明

//測試類1
class Person {

};

Person p;
// sizeof(p)=1;
//類內部爲空的對象大小爲1字節

 //測試類2   
class Person {
	int age;
};  
Person p;
//sizeof(p)=4;


//測試類3
class Person {
	int age;
    static int count;
}; 

Person p;
//sizeof(p)=4;


//測試類4
class Person {
	int age;
    static int count;
    void func();
};  
Person p;
//sizeof(p)=4;

//測試類5  
class Person {
	int age;
    static int count;
    void func();
    static void func2()
}; 
Person p;
// sizeof(p)=4;

靜態成員變量

靜態成員變量聲明之後,需要在類的外部分配空間

代碼示例

class Person{
public:
    static int count;
    
}

int Person::count=0;

this指針

  • this指向對象本身
  • *this解引用爲當前對象
  • this本質,指針常量 Person * const this

使用this實現鏈式編程示例

class Timer

#include <string>
using namespace std;
class Timer
{
private:
	int sec;
	int min;
	int hour;
public:
	Timer():sec(0),min(0),hour(0) {
		
	}
    
	Timer(int sec, int min, int hour) {
		this->sec = sec;
		this->min = min;
		this->hour = hour;
	}
    
    
 	//下面三個函數返回當前對象的引用
    //一定要返回當前對象的引用,否則會返回當前對象的拷貝,無法對當前對象實現鏈式操作
	Timer& addSec(int sec=0) {
		this->sec += sec;
		this->min += this->sec / 60;
		this->sec = this->sec % 60;
		return *this;
	}

	Timer& addMin(int min=0) {
		this->min += min;
		this->hour += this->min / 60;
		this->min = this->min % 60;
		return *this;
	}

	Timer& addHour(int hour=0) {
		this->hour += hour;
		return *this;
	}

	string toString() {
		return "[" + to_string(hour) + ":" + to_string(min) + ":" + to_string(sec) + "]";
	}

};

main.cpp

int main() {

	Timer timer(0, 0, 0);
	timer.addSec(10).addMin(20).addHour(100);  //鏈式
	cout << timer.toString() << endl;
	timer.addSec(20).addMin(30).addHour(1);    //鏈式
	cout << timer.toString() << endl;
	timer.addSec(50).addMin(10).addHour(0);   //鏈式
	cout << timer.toString() << endl;
    
    return 0;
}

空指針訪問成員函數

在c++中,空指針可以訪問沒有使用非靜態成員變量的函數、

示例代碼

class Person{
private:
    int age;
public:
    void sayHello(){
        cout<<"hello world!"<<endl;
    }
    
    void sayAge()}{
    	cout<<"my age is "<<age<<"."<<endl;
	}
}


int main(){
    Person * p =NULL;
    p.sayHello();   //可以執行
    p.sayAge();    //不可以執行,程序拋出異常 
}

爲了程序的健壯性,可以將上面的Person類改爲下面的代碼

class Person{
private:
    int age;
public:
    void sayHello(){
        cout<<"hello world!"<<endl;
    }
    
    void sayAge()}{
    	//這裏判斷this是否是NULL
    	if(this == NULL){
            return;
        }
    	cout<<"my age is "<<age<<"."<<endl;
	}
}

const修飾成員函數(常函數)

this的本質

指針常量 Person * const this,在使用this指針時,this指針的指向不可以改變,例如下面的代碼是錯誤的

class Person{
public:
    void change(){
        this == NULL; //錯誤
    }
}

但是指針指向的值時可以改變的,例如

class Person{
    int age;
public:
    void change(){
        this->age = 10; //正確
    }
}

使用const修飾成員函數,相當於將this修改爲const Person * const this,即this指針的指向和指向對象的值都不可以改變,因此在常函數體內部無法修改當前對象的屬性。

但是使用mutable修飾的屬性值在常函數內部可以改變

常函數聲明格式

class Classname{
    //常函數聲明
    func() const{
        
    }
}

示例代碼

class Person{
private:
    int age;
    mutable string name;
public:
    void testChange() const{
        this->age = 10; //錯誤
        this->name = "root"; //正確
    }
}

const修飾對象(常對象)

  • const修飾對象後,該對象的屬性值不可以修改(mytable修飾的除外)
  • 常對象只能調用常函數(非常函數可以修改屬性值,常對象不可以修改屬性值,爲了避免衝突,常對象不能調用常函數)

代碼示例

class Person{
public:
    int age;
    mutable string name;
public:
    
    //常函數聲明
    void testChange() const{
        this->age = 10; //錯誤
        this->name = "root"; //正確
    }
    
    //普通成員函數
    void changeAge(){
        age=12;
    }
}


int main(){
    
    //常對象聲明
    const Person p;
    p.age = 12; //錯誤
    p.name = "Tom"; //正確
    p.testChange();  //正確
    p.changeAge(); //錯誤
    
}

友元 friend

一般情況下,類中private屬性在類的外部不能被訪問,但是一個類的友元可以不受這個限制。

三種友元的方式

  1. 全局函數
  2. emp

全局函數做友元

使用全局函數作爲友元時,只需要在類的聲明中加入friend void func();即可將一個全局函數聲明爲一個類的友元

代碼示例

class Person

#pragma once
#include<iostream>
#include<string>

using namespace std;
class Person
{
	//友元全局函數聲明
	friend void askSecret(Person &p);
public:
	string name;
	int age;
private:
	string secret; //祕密,只能告訴好朋友,一般人不能訪問

public:

	Person( string name,int age) {
		this->name = name;
		this->age = age;
	}

	void setSecret(string str) {
		this->secret = str;
	}
};

MainApp.cpp

#include"Person.h"


void askSecret(Person& p) {
    //類的外部訪問私有屬性 p.secret
	cout << p.name << "'s secret is " << p.secret << endl;  //tom's secret is I like Jess
}

int main() {
	Person p("tom", 12);
	p.setSecret("I like Jess");
	askSecret(p);
	system("pause");
	return 0;
}

類做友元

使用類作爲友元時,只需要在類的聲明中加入friend class <classname>;即可將一個類聲明爲另一個類的友元

代碼示例

class Person

#include<iostream>
#include<string>

using namespace std;
class Person
{
    //聲明友元類
	friend class Doctor;
public:
	string name;
	int age;
private:
	string secret; //祕密,只能告訴好朋友,一般人不能訪問

public:

	Person( string name,int age) {
		this->name = name;
		this->age = age;
	}

	void setSecret(string str) {
		this->secret = str;
	}
};

class Doctor

#include"Person.h"
class Doctor
{
	string name;

public:
	Doctor() {
		this->name = "doctor li";	
	}


	void askSecret(Person& patient) {
        //友元訪問Person類的私有屬性
		cout << patient.name << "'s secret is \"" << patient.secret << "\"" << endl;
	}
};

MainApp.cpp

#include"Person.h"
#include "Doctor.h"

int main() {
	Person p("tom", 12);
	p.setSecret("I like Jess");
	Doctor doctor;
	doctor.askSecret(p);   //tom's secret is "I like Jess"
	system("pause");
	return 0;
}

成員函數做友元

使用成員函數作爲友元時,只需要在類的聲明中加入friend void <classname>::func();即可將一個成員函數聲明爲另一個類的友元

注意:儘量不要使用這種做法,會造成 A&B兩個類互相包含的錯誤。例如,A.func()是B的友元,在聲明時,需要在B中包含A,但是在A中訪問B的時候,又必須包含B。

運算符重載

  • 重載有兩種方式,使用成員函數實現和使用全局函數實現
  • 運算法重載函數也可以發生函數重載
  • 基本數據類型的運算符不可以重載

聲明方式

//成員函數實現
Timer operator+(Timer t) {
    Timer tmp(t);
    tmp.addSec(this->sec).addMin(this->min).addHour(this->hour);
    return tmp;
}




//全局函數實現
Timer tmp(t1);
tmp.addSec(t2.getSec()).addMin(t2.getMin()).addHour(t2.getHour());
return tmp;


//調用方式
Timer t1(10,20,30);
Timer t2(20,40,50);

Timer t4 = t1 + t2;  // 本質爲 Timer t3=t1.operator+(t2);

+重載

代碼示例

class Timer

#include <string>
using namespace std;
class Timer
{
private:
	int sec;
	int min;
	int hour;
public:
	Timer():sec(0),min(0),hour(0) {
		
	}

    //運算符重載聲明
	Timer operator+(Timer t) {
		Timer tmp(t);
		tmp.addSec(this->sec).addMin(this->min).addHour(this->hour);
		return tmp;
		
	}

	Timer(int sec, int min, int hour) {
		this->sec = sec;
		this->min = min;
		this->hour = hour;
	}

	Timer& addSec(int sec=0) {
		this->sec += sec;
		this->min += this->sec / 60;
		this->sec = this->sec % 60;
		return *this;
	}

	Timer& addMin(int min=0) {
		this->min += min;
		this->hour += this->min / 60;
		this->min = this->min % 60;
		return *this;
	}

	Timer& addHour(int hour=0) {
		this->hour += hour;
		return *this;
	}	
	string toString() {
		return "[" + to_string(hour) + ":" + to_string(min) + ":" + to_string(sec) + "]";
	}

};

MainApp.cpp

int main() {
	Timer timer(0, 0, 0);
	timer.addSec(10).addMin(20).addHour(100);
	Timer timer2(10, 50, 40);
	//Timer timer3 = timer.operator+(timer2);
	Timer timer3 = timer + timer2;

	cout << timer.toString() << endl;
	cout << timer2.toString() << endl;
	cout << timer3.toString() << endl;
	system("pause");
	return 0;
}

++運算符

區分++intint++的方法

//++i
operator++(){
    
}

// i++
operator++(int){
    
}

=賦值運算符重載

  • c++中,使用賦值給對象賦值時,會拷貝對象的屬性值,這一點與java引用賦值不一樣
  • 如果對象屬性中,有在區的數據,即使用new關鍵字開闢的內存空間,使用賦值語句賦值時會產生淺拷貝的問題,這個時候,對=·運算符重載非常有必要

==&!=重載

()函數調用運算符重載(仿函數)

代碼示例

class Printer

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

class Printer
{
public:
    //這裏對“()”進行重載
	void operator()(string text) {
		cout << text << endl;
	}
};

MainApp.cpp


int main() {
    
	Printer print;
    //使用`對象()`的方式可以調用仿函數
	print("Hello world!!");

    //匿名對象: 類名()
	Printer()("這裏是匿名對象函數打印結果");
	return 0;
}

繼承

繼承的方式

  • 語法class 類名: <繼承方式> <父類>

  • 繼承方式

    • public
    • protected
    • private
父類屬性權限 public protected private
public繼承 public protected 不可訪問
protected繼承 protected protected 不可訪問
private繼承 private private 不可訪問

繼承中的對象模型

  • 子類的對象大小包含子類的屬性和所有父類的非靜態屬性(包括私有屬性)
  • 編譯器將父類的私有屬性隱藏,因此,子類對象訪問不到

代碼示例

class Base{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
}

class Son: public Base{
public:
    int m_D;
}

int main(){
    cout<<sizeof(Son)<<endl; //16,表明父類所有屬性都存儲在子類對象中
}

構造函數和析構函數的執行順序

創建一個子類對象時:

  • 先執行父類構造函數
  • 再執行子類構造函數

釋放子類對象時:

  • 先調用子類析構函數
  • 再調用父類析構函數

同名成員函數的處理

  • 當子類和父類有同名函數時,直接調用函數會調用子類函數
  • 如果需要調用父類同名函數,需要使用如下語法
class Animal(){
public:
    string getName(){
        
    }
}


class Dog:public Animal(){
public:
    string getName(){

    } 
}


int main(){
    Dog d;
    d.getName(); //調用子類函數
    d.Animal::getName(); //調用父類函數
}
  • 當子類和父類有同名函數時,編譯器會隱藏掉父類中所有同名函數,因此,需要加作用域訪問

虛繼承

菱形繼承

base
son1
son2
grandson
  • 發生菱形繼承時,子類grandson會有兩份base中的同名屬性,互不干擾
  • 浪費內存
  • 解決方法,虛繼承

虛繼承語法

class name: virtual public base{}

代碼示例

沒有使用虛繼承

class A {
public:int age;
};

class B :public A {

};

class C : public A {

};

class D :public B, public C {

};

int main() {

	D d;
	d.C::age = 18;
	d.B::age = 20;
    // d.age = 20; 編譯錯誤
 
	cout << "d.C::age=" << d.C::age<<endl;  //d.C::age=18
	cout << "d.B::age=" << d.B::age<<endl;  //d.B::age=20
    

}
class A {
public:int age;
};

//虛繼承
class B :virtual public A {

};
//虛繼承
class C : virtual public A {

};

class D :public B, public C {

};

int main() {

	D d;
	d.C::age = 18;
	d.B::age = 20;
    d.age = 30; //可以使用,沒有編譯錯誤
 
	cout << "d.C::age=" << d.C::age<<endl;  //d.C::age=30
	cout << "d.B::age=" << d.B::age<<endl;  //d.B::age=30
    

}
  • 使用虛繼承之後,保存的是屬性的指針

多態

  • 必須有繼承關係
  • 子類必須重寫父類的虛函數
  • 虛函數virtual void func()
  • 實現地址晚綁定,程序運行時才知道對象地址

代碼示例

classAnimal

class Animal
{
public:
    //虛函數
	virtual void speak() {
		cout << "animal is speaking..." << endl;
	}

};

class Dog

class Dog : public Animal
{
	void speak() {
		cout << "dog is speaking" << endl;
	}

	
};

MainApp.cpp

void test(Animal & a) {
	a.speak();
}

int main() {

	Dog d;
	test(d);  //dog is speaking
}

純虛函數和抽象類

  • 父類中的虛函數實現一般沒有意義,使用純虛函數
  • virtual returnType func(param)=0
  • 當類中有純虛函數時,該類變爲抽象類
  • 抽象類無法實例化對象
  • 繼承抽象類的子類如果沒有重寫父類純虛函數,則這個子類也是抽象類

虛析構和純虛析構

  • 使用多態時,利用父類類型接受子類對象

    Animal animal = new Dog;
    
  • 當釋放這個對象時,會調用父類的析構函數,子類的析構函數不會被調用,導致子類中有釋放堆區數據的代碼不能被執行,內存釋放不乾淨

  • 解決方法:將父類的析構函數寫成虛析構函數

    virtual ~Animal(){   
    }
    
  • 這樣在釋放子類對象時,會先調用子類的析構函數,再調用父類的析構函數

  • 純虛析構

    • 純虛析構需要在類內部聲明,在外部實現
    virtual ~Animal()=0;
    //類的外部
    Animal::~Animal(){
        
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章