前言
前面簡(jiǎn)單介紹了C++中的基本知識(shí)和引用,本篇博客將介紹C++中的類(lèi)與函數(shù),分別是成員函數(shù)、無(wú)參構(gòu)造函數(shù)、有參構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)等。
成員函數(shù)
類(lèi)的成員函數(shù)有兩種定義:一種是在聲明類(lèi)時(shí)就給出成員函數(shù)的定義;另一種是在聲明類(lèi)時(shí),只聲明成員函數(shù)的原型,然后在類(lèi)的外部定義成員函數(shù),其定義方法是:
返回類(lèi)型 類(lèi)名::成員函數(shù)名(參數(shù)表)
其中,::是域限定符,用于說(shuō)明成員函數(shù)名是指定類(lèi)名中的一個(gè)函數(shù)。這里我將就第二種舉例子如下:
首先我們創(chuàng)建一個(gè)頭文件MyTeacher.h
//防止被重復(fù)引用
#pragma once;
class MyTeacher {
private:
char* name;
int age;
public:
void setName(char* name);
char* getName();
void setAge(int age);
int getAge();
};
頭文件中定義了類(lèi)MyTeacher,聲明了四個(gè)函數(shù),#pragma once防止被重復(fù)引用。
再創(chuàng)建一個(gè)C++文件,用于上面四個(gè)函數(shù)的定義
#include "MyTeacher.h"
void MyTeacher::setName(char* name) {
this->name = name;
}
char* MyTeacher::getName() {
return this->name;
}
void MyTeacher::setAge(int age) {
this->age = age;
}
int MyTeacher::getAge() {
return this->age;
}
最后我們?cè)诹硪粋€(gè)C++文件中去使用
#include<iostream>
#include<stdarg.h>
#include "MyTeacher.h"
using namespace std;
void main() {
MyTeacher t;
t.setName("john");
cout << t.getName() << endl;
getchar();
}
運(yùn)行打印結(jié)果:
john
類(lèi)成員函數(shù)的第二種定義方式在C++中是比較普遍的,需要了解一下。
無(wú)參構(gòu)造函數(shù)
#include<iostream>
#include<stdarg.h>
using namespace std;
class Teacher {
private:
char* name;
int age;
public:
//無(wú)參構(gòu)造函數(shù)(有默認(rèn)的無(wú)參構(gòu)造函數(shù),如果添加了,就會(huì)覆蓋默認(rèn)的無(wú)參構(gòu)造函數(shù))
Teacher() {
cout << "無(wú)參構(gòu)造函數(shù)" << endl;
}
};
void main() {
Teacher t1;
getchar();
}
運(yùn)行打印結(jié)果:
無(wú)參構(gòu)造函數(shù)
有參構(gòu)造函數(shù)
添加有參構(gòu)造函數(shù),將無(wú)參構(gòu)造函數(shù)注釋
//有參構(gòu)造函數(shù)會(huì)覆蓋默認(rèn)的無(wú)參構(gòu)造函數(shù)
Teacher(char* name, int age) {
this->name = name;
this->age = age;
cout << "有參構(gòu)造函數(shù)" << endl;
}
main函數(shù)中調(diào)用,將無(wú)參函數(shù)的調(diào)用注釋
Teacher t2("john", 23);
//有參構(gòu)造函數(shù)的另一種寫(xiě)法
//Teacher t2 = Teacher("john", 23);
運(yùn)行打印輸出結(jié)果為:
有參構(gòu)造函數(shù)
析構(gòu)函數(shù)
添加析構(gòu)函數(shù),將有參構(gòu)造函數(shù)注釋
~Teacher(){
cout << "析構(gòu)函數(shù)" << endl;
}
編寫(xiě)一個(gè)函數(shù),創(chuàng)建Teacher的引用
void func() {
Teacher t3;
}
main函數(shù)中調(diào)用func函數(shù),將有參構(gòu)造函數(shù)注釋
func();
運(yùn)行打印結(jié)果為:
析構(gòu)函數(shù)
當(dāng)對(duì)象要被釋放時(shí),析構(gòu)函數(shù)會(huì)被調(diào)用,此例中的Teacher t3要被釋放時(shí),析構(gòu)函數(shù)被調(diào)用。析構(gòu)函數(shù)的作用就是做一些善后處理工作。接下來(lái)我們來(lái)模擬析構(gòu)函數(shù)的善后處理工作。
將無(wú)參構(gòu)造函數(shù)打開(kāi),并修改如下:
Teacher() {
this->name = (char*)malloc(100);
strcpy(this->name, "john");
cout << "無(wú)參構(gòu)造函數(shù)" << endl;
}
析構(gòu)函數(shù)將this->name內(nèi)存通過(guò)free釋放
~Teacher(){
free(this->name);
cout << "析構(gòu)函數(shù)" << endl;
}
運(yùn)行錯(cuò)誤,在頭文件上方添加
#define _CRT_SECURE_NO_WARNINGS
運(yùn)行打印結(jié)果為:
無(wú)參構(gòu)造函數(shù)
析構(gòu)函數(shù)
拷貝構(gòu)造函數(shù)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Teacher {
private:
char* name;
int age;
public:
Teacher(char* name, int age) {
this->name = name;
this->age = age;
cout << "有參構(gòu)造函數(shù)" << endl;
}
//拷貝構(gòu)造函數(shù)(值拷貝)
//默認(rèn)拷貝構(gòu)造函數(shù)就是值拷貝
Teacher(const Teacher &obj) {
this->name = obj.name;
this->age = obj.age;
cout << "拷貝構(gòu)造函數(shù)" << endl;
}
void myPrintf() {
cout << this->name << "," << this->age << endl;
}
};
void main() {
Teacher t1 = Teacher("john", 23);
Teacher t2 = t1;
t2.myPrintf();
getchar();
}
拷貝構(gòu)造函數(shù)的參數(shù)其實(shí)就是常引用,這下是不是對(duì)常引用的應(yīng)用又了解了一波呢?默認(rèn)拷貝構(gòu)造函數(shù)就是值拷貝,也就是說(shuō)如果不寫(xiě)任何拷貝函數(shù),默認(rèn)就是值拷貝,值拷貝又名淺拷貝。
拷貝構(gòu)造函數(shù)被調(diào)用的場(chǎng)景:
1.聲明時(shí)賦值
Teacher t2 = t1;
2.作為參數(shù)傳入,實(shí)參給形參賦值
3.作為函數(shù)返回值返回,給變量初始化賦值
淺拷貝問(wèn)題
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Teacher {
private:
char* name;
int age;
public:
Teacher(char* name, int age) {
this->name = (char*)malloc(100);
strcpy(this->name, name);
this->age = age;
cout << "有參構(gòu)造函數(shù)" << endl;
}
~Teacher() {
//釋放內(nèi)存
free(this->name);
cout << "析構(gòu)函數(shù)" << endl;
}
};
void func() {
Teacher t1("jonh", 23);
Teacher t2 = t1;
}
void main() {
func();
getchar();
}
這里有參函數(shù)中通過(guò)動(dòng)態(tài)內(nèi)存的方式給this->name給予內(nèi)存空間,在析構(gòu)函數(shù)中釋放內(nèi)存。運(yùn)行出現(xiàn)錯(cuò)誤,錯(cuò)誤信息大意就是程序試圖刪除一個(gè)空指針,結(jié)果為:
有參構(gòu)造函數(shù)
析構(gòu)函數(shù)
我們來(lái)分析一下執(zhí)行的流程,首先執(zhí)行t1的有參構(gòu)造函數(shù),給t1的this->name賦予內(nèi)存空間,然后執(zhí)行默認(rèn)的淺拷貝,上面有說(shuō)過(guò)淺拷貝其實(shí)就是值拷貝,而指針的值就是地址,所以會(huì)將t2的this->name賦予剛才一樣的內(nèi)存空間。這時(shí)候func函數(shù)執(zhí)行完,將釋放內(nèi)部的局部變量,首先釋放的是t2,而不是t1,對(duì)于此處不相信的同學(xué)可以在func函數(shù)中分別對(duì)t1、t2的age賦值為1,2,然后在析構(gòu)函數(shù)中打印age的值,就可以知曉釋放的先后順序。言歸正傳,首先釋放t2,將那塊相同的內(nèi)存空間釋放,接下來(lái)再去調(diào)用析構(gòu)函數(shù),釋放t1,這次就出現(xiàn)問(wèn)題了,因?yàn)槟菈K內(nèi)存已經(jīng)被釋放了,不能再次釋放。
默認(rèn)的淺拷貝其實(shí)就是下面的邏輯原理:
Teacher(const Teacher &obj) {
this->name = obj.name;
this->age = obj.age;
cout << "拷貝構(gòu)造函數(shù)" << endl;
}
深拷貝
解決淺拷貝問(wèn)題:
Teacher(const Teacher &obj) {
this->name = (char*)malloc(100);
strcpy(this->name, obj.name);
this->age = age;
}
當(dāng)Teacher t2 = t1時(shí),調(diào)用拷貝函數(shù),又在內(nèi)存中開(kāi)辟出一段內(nèi)存空間,然后指針指向這段內(nèi)存空間。
運(yùn)行打印結(jié)果為:
有參構(gòu)造函數(shù)
析構(gòu)函數(shù)
析構(gòu)函數(shù)
我們來(lái)簡(jiǎn)單分析一下運(yùn)行結(jié)果:Teacher t1("jonh", 23)會(huì)執(zhí)行有參構(gòu)造函數(shù),Teacher t2 = t1會(huì)調(diào)用拷貝構(gòu)造函數(shù),此時(shí)又在內(nèi)存中開(kāi)辟出一段內(nèi)存空間,然后t2的name指針指向這段新開(kāi)辟出來(lái)的內(nèi)存空間,最后當(dāng)func函數(shù)執(zhí)行完之后,會(huì)首先釋放t2,調(diào)用析構(gòu)函數(shù),接下來(lái)釋放t1,由于之前t1和t2指向的內(nèi)存空間不同,所以這時(shí)候釋放t1是沒(méi)有任何影響的,故而再次調(diào)用析構(gòu)函數(shù)。
總結(jié)
淺拷貝(值拷貝),拷貝的是指針的地址
深拷貝,拷貝的是指針指向的數(shù)據(jù)內(nèi)容