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
屬性在類的外部不能被訪問,但是一個類的友元
可以不受這個限制。
三種友元的方式
- 全局函數
- 類
- 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;
}
++
運算符
區分++int
和int++
的方法
//++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(); //調用父類函數
}
- 當子類和父類有同名函數時,編譯器會隱藏掉父類中所有同名函數,因此,需要加作用域訪問
虛繼承
菱形繼承
- 發生菱形繼承時,子類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(){ }