0.前言
最近在做算法移植的時候,發(fā)現(xiàn)自己對c++的知識忘得很多,于是就計劃重新?lián)炱饋怼?br>
紙上得來終覺淺,絕知此事要躬行
一直都是我學(xué)習(xí)和工作的原則,我比較喜歡動手。
于是我把c++的基礎(chǔ)知識一行行代碼敲了一遍,無論多簡單!
1.內(nèi)存分區(qū)模型
c++內(nèi)存分為4個區(qū)域:
代碼區(qū):存放函數(shù)體的二進(jìn)制代碼,由操作系統(tǒng)進(jìn)行管理。
全局區(qū):存放全局變量、靜態(tài)變量、常量
棧區(qū):由編譯器自動分配釋放,存放函數(shù)的參數(shù)值、局部變量等。
堆區(qū):由程序員分配和釋放,若程序員不釋放,程序結(jié)束時由操作系統(tǒng)回收
內(nèi)存分區(qū)的意義:
不同區(qū)域存放的數(shù)據(jù),賦予不同的生命周期,給我們更大的靈活編程。
1.1 程序運行前
在程序編譯后,生產(chǎn)了exe可執(zhí)行程序,未執(zhí)行該程序前分為
代碼區(qū):
存放cpu執(zhí)行的機器指令
1.共享:目的是對于頻繁被執(zhí)行的程序,只需內(nèi)存中由一份代碼即可。
2.只讀:防止程序被隨意修改指令
全局區(qū):
全局變量包含常量區(qū):字符串常量和其他常量
程序結(jié)束后,系統(tǒng)自己釋放全局區(qū)的數(shù)據(jù)
const修飾的局部變量(局部常量),不放在全局區(qū),而是放在棧區(qū)
int fun() {
const int a =10;//局部常量
}
總結(jié):
- c++在exe程序運行前分為全局區(qū)和代碼區(qū)
- 代碼區(qū)的特點是只讀和共享
- 全局區(qū)中存放全局變量 靜態(tài)變量 常量
- 常量區(qū)中存放const修飾的全局變量(即全局常量) 和 字符串常量
1.2 程序運行后
棧區(qū)
由編譯器自動分配和釋放,函數(shù)的參數(shù),局部變量等
注意事項:不與返回局部變量的地址,棧區(qū)開辟的數(shù)據(jù)在函數(shù)結(jié)束后,編譯器會自動釋放
堆區(qū)
由程序員分別和釋放,若程序員不釋放,程序結(jié)束后,系統(tǒng)會自己釋放,如果程序不結(jié)束,系統(tǒng)就永遠(yuǎn)不釋放。
c++中主要用new在堆區(qū)申請內(nèi)存
1.3 new的用法
new申請,務(wù)必使用delete釋放
int *a = new int(10);
delete a;
int *b = new int [8];
delete [] b;//數(shù)組釋放要加[]
2.引用
2.1 引用的基本使用
作用:給變量起別名
語法:數(shù)據(jù)類型 &別名 = 原名
int a =10;
int &b = a;
(本質(zhì)上:int *const b = &a)
2.2 引用的注意事項
- 1.引用必須要初始化
int &b;//錯誤 - 2.引用在初始化后,不可以改變
int a =10;
int &b = a;
int c =20;
b = c;//賦值操作,而不是更改引用
2.3 引用做函數(shù)參數(shù)
作用:函數(shù)傳參時,可以利用引用的技術(shù),讓形參修飾實參
優(yōu)點:可以簡化指針修改實參
void swap(int &a,int &b) {
int temp = a;
a = b;
b = temp;
}
2.4 引用做函數(shù)返回值
作用:引用可以作為函數(shù)的返回值
注意:不要返回局部變量的引用
用法:函數(shù)調(diào)用作為左值
int& fun()
{
static int a =10;
return a;
}
int &ref = fun();
fun()= 100;//函數(shù)調(diào)用可以作為左值 被賦值
2.5 引用的本質(zhì)
本質(zhì):引用的本質(zhì)在c++內(nèi)部實現(xiàn)是一個指針常量(指向不變,里面的值可變)
//編譯器發(fā)現(xiàn)這里是引用,轉(zhuǎn)換位int * const ref = &a;
void func(int & ref) {
ref = 100;//ref是引用,轉(zhuǎn)換位*ref = 100
}
int main(){
int a=10;
int& ref = a;
//自動轉(zhuǎn)換為 int* const ref = &a;
//指針常量是指向不可更改,值可改,也說明為什么引用不可更改
ref = 20;//發(fā)現(xiàn)是引用,自動轉(zhuǎn)換成 *ref =20
}
結(jié)論:c++途徑用引用計數(shù),因為語法方便,引用的本質(zhì)是指針常量,但是所有指針的操作,編譯器都幫我們做了
常量引用
作用:常量引用主要用來修飾形參,防止誤操作
const int & a;
int &ref =10;//這是錯誤的,引用必須引用一塊合法的內(nèi)存,10在常量區(qū),訪問時必須加const
int &ref =10;//這是錯誤的,引用必須引用一塊合法的內(nèi)存
const int &ref =10;//合法
//加上const之后,編譯器把代碼修改成
/*
int temp =10;
const int &ref = temp;
*/
//常量引用使用場景:通常用來修飾形參,防止函數(shù)改值
void printValue(const int&a){
//a=20;報錯
print("a=%d",a);
}
3.函數(shù)提高
3.1 函數(shù)的默認(rèn)參數(shù)
在c++中,函數(shù)的形參是可以有默認(rèn)值的。
語法: 返回值 函數(shù)名 (參數(shù) = 默認(rèn)值) { }
int func(int a,int b=1, int c =2) {
}
如果形參中某個位置有了默認(rèn)參數(shù),從這個位置往后,從左往右都必須有默認(rèn)參數(shù)
如果函數(shù)聲明有默認(rèn)參數(shù),函數(shù)實現(xiàn)就不能有默認(rèn)參數(shù),否則會報錯:重定義默認(rèn)參數(shù)
int fun2(int a =10);//聲明
int func2(int a =10){//實現(xiàn)
}
這樣會報錯
3.2 函數(shù)占位參數(shù)
c++中的函數(shù)的形參是可以有占位參數(shù)的,用來做占位,調(diào)用函數(shù)時必須填補該位置。
語法: 返回值 函數(shù)名 (數(shù)據(jù)類型){ }
//占位參數(shù)-第二個參數(shù)
void func(int a,int) {
}
//占位參數(shù) 還可以有默認(rèn)參數(shù)
void func2(int = 10){
}
int main(){
func(10,10);
}
3.3 函數(shù)重載
3.3.1 函數(shù)重載概述
作用:函數(shù)名可以相同,提高復(fù)用性
函數(shù)重載滿足條件
- 同一個作用域下
- 函數(shù)名相同
- 函數(shù)參數(shù) 類型不同 或者 個數(shù)不同 或者 順序不同
注意:函數(shù)返回值 不可以作為函數(shù)重載的條件
void fun();
void fun (int a,float b)
void fun (float b,int a)
//int fun (float b,int a) 這個會報錯,二義性
3.3.2 函數(shù)重載注意事項
- 引用作為重載條件
//1.引用作為重載條件
void func(int &a){ //int& a =10 ;不合法
}
void func(const int &a){const int &a = 10 合法
}
這兩個是函數(shù)重載 int 和 const int算不同類型
int main(){
int a =10;
func(a);//此時調(diào)用第1個void func(int &a),
//如果void func(int &a)沒定義,就調(diào)用第2個
func(10);//此時調(diào)用第2個void func(const int &a)
}
- 函數(shù)重載碰到默認(rèn)參數(shù)
void func(int a){ //int& a =10 ;不合法
}
void func(int a,int b =10){
}
fun(10);//這里編譯器不知道調(diào)用哪個了
這里函數(shù)會出現(xiàn)二義性,會報錯,
一般寫函數(shù)重載時,就別寫默認(rèn)參數(shù)了
4.類和對象
萬事萬物皆為對象,包含屬性和行為
c++面向?qū)ο蟮娜筇匦裕悍庋b、繼承、多態(tài)
4.1 封裝
4.4.1 封裝的意義
封裝是c++面向?qū)ο蟮娜筇匦灾?br> 意義:
- 將屬性和行為作為一個整體,表現(xiàn)生活中的事物
- 將屬性和行為加以權(quán)限控制f
封裝的意義1:
在設(shè)計類的時候,屬性和行為寫一起,表現(xiàn)事物
語法:class 類名{訪問權(quán)限: 屬性/行為};
設(shè)計一個圓類
//圓周率
const double PI = 3.14;
class Circle
{
//訪問權(quán)限
public:
//屬性
int r;//半徑
//行為
double calZC(){
return 2*PI*r;
}
Circle c ;//對象
c.r = 10;
c.calZC();
}
封裝的意義2:
將屬性和行為加以權(quán)限控制
訪問權(quán)限有3種
1.public:公共權(quán)限
成員 類內(nèi)可以訪問,類外可以
2.protected:保護(hù)權(quán)限
成員 類內(nèi)可以訪問,類外不可以
3.private:私有權(quán)限
成員 類內(nèi)可以訪問,類外不可以
4.1.2 struct 和 class區(qū)別
在c++中,struct和class的唯一區(qū)別:
默認(rèn)訪問權(quán)限不同
- struct 默認(rèn)權(quán)限 公共
- class 默認(rèn)權(quán)限 私有
class C1
{
int a;//私有
};
struct C2 {
int a ;//公共
}
4.1.3 成員屬性設(shè)置為 私有
- 優(yōu)點1::將所有的屬性設(shè)置成私有,可以自己控制讀寫權(quán)限
- 優(yōu)點2:對于寫權(quán)限,可以檢測數(shù)據(jù)的有效性
4.2 對象的初始化和釋放
4.2.1 構(gòu)造函數(shù)和析構(gòu)函數(shù)
對象的初始化和釋放是2個非常重要的安全問題。
對象沒用初始狀態(tài),其后果是未知的
使用完對象沒釋放,也會造成安全難問題
- 構(gòu)造函數(shù):創(chuàng)建對象時,初始化屬性
- 析構(gòu)函數(shù):銷毀對象時,釋放資源
2個函數(shù)都是系統(tǒng)自動調(diào)用!
構(gòu)造函數(shù)語法:類名(){ }
1.構(gòu)造函數(shù)沒有返回值,也不寫void
2.函數(shù)名和類名相同
3.構(gòu)造函數(shù)有參數(shù),可以函數(shù)重載
4.創(chuàng)建對象時,自動調(diào)用一次構(gòu)造函數(shù)
析構(gòu)函數(shù)語法:
1.析構(gòu)函數(shù)沒有返回值,也不寫void
2.析構(gòu)函數(shù)與類名相同,在名稱前加上~
3.析構(gòu)函數(shù)沒用參數(shù),無法重載
4.對象銷毀前會自動調(diào)用一次析構(gòu)函數(shù)
Class p{
//構(gòu)造函數(shù)
p(){ }
//析構(gòu)函數(shù)
~p(){
}
};
4.2.3構(gòu)造函數(shù)的分類和調(diào)用
兩種分類方式:
按照參數(shù):有參構(gòu)造和無參構(gòu)造
按照類型:普通構(gòu)造和拷貝構(gòu)造
三種調(diào)用方式:
括號法
顯示法
隱式轉(zhuǎn)換法
class Person {
public:
Person(){//無參構(gòu)造
}
Person(int a){//有參構(gòu)造
age = a;
}
//以上都是普通構(gòu)造
Person(const Person &p){//拷貝構(gòu)造
age = p.age;
}
private:
int age;
}
1.括號法
Person p1;
Person p2(10);
Person p3(p2);
2.顯示法
Person p1;
Person p2 = Person(10);
Person p3 = Person(p2);
Person(6);//單獨拿出來是 匿名對象
特點:當(dāng)前行執(zhí)行結(jié)束后,系統(tǒng)會立即回收匿名對象
注意事項:
不要利用拷貝構(gòu)造函數(shù),初始化匿名對象
Person(p3);
編譯器會認(rèn)為Person(p3) == Person p3;
3.隱式轉(zhuǎn)換法
Person p4 = 10;//相當(dāng)于 Person p4 = Person(10);
Person p5 = p4;//相當(dāng)于 Person p5 = Person(p4);
4.2.3 拷貝構(gòu)造函數(shù)的調(diào)用時機
class Person{
public:
Person(){
無參構(gòu)造
}
Person(int a ){
有參構(gòu)造
age =a;
}
Person(const Person & p){
拷貝構(gòu)造
age = p.age;
}
~Person(){
析構(gòu)函數(shù)
}
private:
int age;
}
三種情況:
- 使用一個已經(jīng)創(chuàng)建完的對象來初始化一個新對象
void test(){
Person p1(20);
Person p2(p1);
}
- 值傳遞的方式給函數(shù)參數(shù)傳值
void doWork(Person p){//這里相當(dāng)于Person p = my_p
}
void test2(){
Person my_p;
doWork(my_p);
}
* 以值的方式返回局部對象
```c
Person doWork3(){
Person p;
return p;//這里執(zhí)行完后會釋放p,系統(tǒng)會拷貝一個臨時p返回
}
void test(){
Person p = doWrok3();
}
4.2.4 構(gòu)造函數(shù)調(diào)用規(guī)則
默認(rèn)情況下,c++編輯器會給一個類添加3個函數(shù)
1.默認(rèn)構(gòu)造函數(shù)(無參 函數(shù)體為空)
2.默認(rèn)析構(gòu)函數(shù)(無參 函數(shù)體為空)
3.默認(rèn)拷貝構(gòu)造函數(shù)(對所有屬性進(jìn)行值拷貝)
構(gòu)造函數(shù)規(guī)則:
- 如果用戶定義了有參構(gòu)造,c++編輯器不在提共默認(rèn)無參構(gòu)造,但是會提供默認(rèn)拷貝構(gòu)造
- 如果用戶定義拷貝構(gòu)造函數(shù),c++不會提供其他構(gòu)造函數(shù)
Person(const Person & p){
拷貝構(gòu)造
age = p.age;
}
4.2.5 深拷貝和淺拷貝
- 淺拷貝:值拷貝
- 深拷貝: 在堆區(qū)重新申請一塊內(nèi)存空間,進(jìn)行拷貝操作
#include <iostream>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
class Person
{
public:
Person()
{
cout<<"Person 默認(rèn)構(gòu)造函數(shù)"<<endl;
}
Person(int age,int height)
{
m_age = age;
m_height = new int(height);
cout<<"Person 有參構(gòu)造函數(shù)"<<endl;
}
//淺拷貝:帶來的問題就是堆區(qū)重復(fù)釋放
Person(const Person &p)
{
cout<<"Person 淺拷貝構(gòu)造函數(shù)調(diào)用"<<endl;
m_age = p.m_age;
m_height = p.m_height;
}
//深拷貝
Person(const Person &p)
{
m_age = p.m_age;
m_height = new int(*p.m_height);
}
~Person()
{
cout<<"Person 析構(gòu)函數(shù)調(diào)用,m_height地址"<<m_height<<endl;
if(m_height != nullptr)
{
cout<<"釋放 person=%p"<<this<<" m_height ="<<*m_height<<endl;
delete m_height;
m_height = nullptr;
}
}
int m_age;
int *m_height;
};
void test()
{
Person p1(18,180);
cout<<"p1 ="<< (int*)&p1 << " "<<p1.m_age<<" "<<*p1.m_height<<endl;
Person p2(p1);
cout<<"p2 ="<<(int*)&p2<< " "<<p2.m_age<<" "<<*p2.m_height<<endl;
}
int main(int argc, char** argv) {
test();
return 0;
}
總結(jié):如果成員變量有在堆區(qū)開辟內(nèi)存的,一點要自己提供拷貝構(gòu)造函數(shù),防止淺拷貝代理的堆內(nèi)存重復(fù)釋放問題
4.2.6 初始化列表
作用
c++提供初始化列表的語法,用來初始化屬性
語法:構(gòu)造函數(shù)():屬性1(值1),屬性2(值2)···{}
#include <iostream>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
class Person
{
public:
Person():m_a(10),m_b(20)
{
}
Person(int a,int b):m_a(a),m_b(b)
{
}
int m_a;
int m_b;
};
int main(int argc, char** argv) {
Person p;
cout<<p.m_a<<" "<<p.m_b<<endl;
Person p2(30,40);
cout<<p2.m_a<<" "<<p2.m_b<<endl;
return 0;
}
4.2.7 類對象作為類的成員
構(gòu)造時:當(dāng)其他類對象作為本類成員時,構(gòu)造時先構(gòu)造出其他類的對象,在構(gòu)造自身
析構(gòu)時:先析構(gòu)自己,再析構(gòu)其他類。(因為加載函數(shù)是棧的形式 后進(jìn)先出)
class Phone
{
public:
Phone(string name):name(name)
{
cout<<"Phone 有參構(gòu)造函數(shù)調(diào)用"<<endl;
//this.name = name;
}
~Phone()
{
cout<<"Phone 析構(gòu)函數(shù)調(diào)用"<<endl;
}
string name;
} ;
class Person
{
public:
Person(string name,string p):name(name),p(p)
{
cout<<"Person 有參構(gòu)造函數(shù)調(diào)用"<<endl;
}
~Person()
{
cout<<"Person 析構(gòu)函數(shù)調(diào)用"<<endl;
}
string name;
Phone p;
};
4.2.8 靜態(tài)成員
在成員函數(shù)和成員變量前面加上static關(guān)鍵字,成為靜態(tài)成員。
靜態(tài)成員分為:
- 靜態(tài)成員變量
- 所有對象共享一份數(shù)據(jù)
- 在編譯階段分配內(nèi)存
- 類內(nèi)聲明,類外初始化
class A
{
public:
static int m_a;//類內(nèi)聲明
}
int A::m_a = 100;//類外初始化 要去掉static
- 靜態(tài)成員函數(shù)
- 所有對象共享同一個函數(shù)
- 靜態(tài)成員函數(shù)只能訪問靜態(tài)成員變量
- 類外無法訪問私有的成員函數(shù)或者變量
class Person
{
public:
static void fun()
{
cout<<"靜態(tài)成員函數(shù)"<<endl;
// m_a = 1;
m_b = 2;
}
int m_a;
static int m_b;
priviate:
static void fun2()//
{
}
} ;
void test()
{
//1.通過對象調(diào)用
Person p;
p.fun();
//2.通過類名調(diào)用
Person::fun();
//私有無法訪問
//Person::fun2();
}
4.3 C++的對象模型和this指針
4.3.1 成員變量和成員函數(shù)分開存儲
在c++中,類內(nèi)的成員變量和成員函數(shù)是分開存儲的,
只有非靜態(tài)成員變量才屬于類的對象上
- 空對象占用內(nèi)存為1個字節(jié)
c++編譯器會給每個空對象分配一個字節(jié)空間,是為了區(qū)分空對象占用內(nèi)存的位置。
class Person
{
} ;
void test()
{
Person p;//空對象
cout<<sizeof(p)<<endl;
}
- 只有非靜態(tài)成員變量才屬于類的對象上
class Person
{
int a;//非靜態(tài)成員變量 屬于類的對象 此時p的內(nèi)存為4 個字節(jié)
static int b;//靜態(tài)成員變量 不屬于類對象上
void fun();//非靜態(tài)成員函數(shù) 不屬于類對象上
static void fun2();//靜態(tài)成員函數(shù) 不屬于類對象上
} ;
void test()
{
Person p;
cout<<sizeof(p)<<endl;
}
4.3.2 this指針的概念
this指針指向被調(diào)用的成員函數(shù)所屬的對象
誰調(diào)用this,this就指向哪個對象
this是隱含在非靜態(tài)成員函數(shù)內(nèi)的一種指針
this指針的用途:
- 當(dāng)形參和成員變量同名時,可以用this指針來區(qū)分
- 在類的非靜態(tài)成員函數(shù)中返回對象本身,可以用return *this
class Person
{
public:
Person(int age)
{
this->age = age;
}
//這里要返回Person&,如果返回Person,系統(tǒng)會創(chuàng)建一個臨時變量返回
Person& addAge(Person &p)
{
this->age += p.age;
//this指向的是p2的指針,而*this指向的是p2對象本身
return *this;
}
int age;
} ;
this指向的是p2的指針,而*this指向的是p2對象本身
4.3.3 空指針訪問成員函數(shù)
c++中空指針也是可以調(diào)用成員函數(shù)的,
如果要用到this指針,要加以判斷,保證代碼的健壯性
class Person
{
public:
void showName()
{
cout<<"this is Person class"<<endl;
}
void showAge()
{
if(this == NULL)//增加健壯性
return;
cout<<"age = "<<this->age<<endl;//p == null 這里直接訪問會崩潰
}
int age;
};
int main(int argc, char** argv) {
Person *p =NULL;
p->showName();
p->showAge();
return 0;
}
4.3.4 const修飾成員函數(shù)
常函數(shù)
- 成員函數(shù)后加const關(guān)鍵字,稱為常函數(shù)
- 常函數(shù)內(nèi)不可以修改成員屬性
- 成員函數(shù)加上關(guān)鍵字mutable后,在常函數(shù)中就可以修改
常對象:
- 聲明對象前加const稱為常對象
- 常對象只能調(diào)用常函數(shù)(因為普通函數(shù)可以改成員變量,常對象不允許修改成員變量)
class Person
{
public:
//常函數(shù)
//在成員函數(shù)后面加const,修飾的是this指向,讓指針指向的值也不可修改,這里 this = const Person * const this
void showPerson() const
{
//this = (Person * const this) 的本質(zhì)是 指針常量,,指針的指向是不可以修改,指向的內(nèi)容可變
//this->m_a = 100;不允許改變
this->m_b =100;
}
void fun()
{
//這里面可以修改成員變量的值,如 m_a =10;
}
int m_a;
mutable int m_b;//mutable變量,即使在常函數(shù)中,也能修改值
};
//常對象
void test()
{
const Person p ;//對象面前加const,成為常對象
//p.m_a = 100; 常對象不允許改任何值
p.m_b = 100;
//常對象只能調(diào)用常函數(shù)
p.showPerson();
//p.fun();//常對象不可以調(diào)用普通成員函數(shù),因為普通成員函數(shù)可以修改成員變量,常對象又不允許修改
}
4.4 友元
作用:
讓一個函數(shù)或者類訪問另一個類中的私有成員
友元的關(guān)鍵字friend
友元的三種實現(xiàn):
- 全局函數(shù)做友元
class MyHome
{
//全局函數(shù)做友元
friend void goodGay(MyHome &m);
public:
MyHome():m_livingRoom("客廳"),m_bedRoom("臥室")
{
}
string m_livingRoom;//客廳
private:
string m_bedRoom;//臥室
} ;
//全局函數(shù)
void goodGay(MyHome &m)//這里相當(dāng)于 MyHome *const m = &mh;
{
cout<<"好基友正在訪問:"<<m.m_livingRoom<<endl;
cout<<"好基友正在訪問:"<<m.m_bedRoom;
}
int main(int argc, char** argv) {
MyHome mh;
goodGay(mh);
return 0;
}
- 類做友元
class Home
{
public:
friend class GoodGay;
Home():m_livingRoom("客廳"), m_bedRoom("臥室")
{
}
public:
string m_livingRoom;
private:
string m_bedRoom;
};
class GoodGay
{
public:
GoodGay()
{
h = new Home;
}
void visit()
{
cout<<"好基友正在訪問:"<< h->m_livingRoom<<endl;
cout<<"好基友正在訪問:"<< h->m_bedRoom<<endl;
}
private:
Home *h;
};
- 成員函數(shù)做友元
class GoodGay;
class Home
{
//成員函數(shù)做友元
friend void GoodGay::visit();
public:
Home():m_livingRoom("客廳"), m_bedRoom("臥室")
{
}
public:
string m_livingRoom;
private:
string m_bedRoom;
};
class GoodGay
{
public:
GoodGay()
{
h = new Home;
}
void visit()
{
cout<<"好基友正在訪問:"<< h->m_livingRoom<<endl;
cout<<"好基友正在訪問:"<< h->m_bedRoom<<endl;
}
private:
Home *h;
};
void test()
{
GoodGay g;
g.visit();
}
4.5 重載
ps:運算符重載跳過
4.6 繼承
繼承的好處
減少重復(fù)的代碼
4.6.1 繼承的基本語法
語法:
class 子類:繼承方式 父類
子類也稱為派生類
父類也成為基類
class BaspePage
{
public:
void header()
{
cout<<"首頁 公開課 登錄 注冊"<<endl;
}
void footer()
{
cout<<"幫助中心、加入我們"<<endl;
}
} ;
class Jave : public BaspePage
{
public:
void content()
{
cout<<"這是 JAVA 教程"<<endl;
}
};
int main(int argc, char** argv) {
Jave j;
j.header();
j.content();
j.footer();
return 0;
}
4.6.2 繼承的方式
繼承的方式一共有三種:
- 公共繼承
- 保護(hù)繼承
- 私有繼承

class Father
{
public:
Father():a(1),b(1),c(1)
{
}
public:
int a;
protected:
int b;
private:
int c;
};
class Son1:public Father
{
public:
void fun()
{
a= 10;
b= 20;
//c= 30;
}
};
void test01()
{
Son1 s;
s.a = 100;
//s.b = 50; 保護(hù)權(quán)限類外無法訪問
}
4.6.3 繼承中的對象模型
問題:從父類繼承過來的成員,哪些屬于子類對象中?
私有成員也會被子類繼承,只是被編譯器隱藏了,無法訪問
class Base
{
public:
int a;
protected:
int b;
private:
int c;//私有成員也會被子類繼承,只是被編譯器隱藏了,無法訪問
};
class Son:private Base
{
public:
int d;
};
int main(int argc, char** argv) {
Son s;
cout<<sizeof(s)<<endl;//size = 16
return 0;
}
4.6.4 繼承中構(gòu)造和析構(gòu)的順序
子類繼承父類后,當(dāng)創(chuàng)建子類對象時,也會調(diào)用父類的構(gòu)造。
先構(gòu)造父類,在構(gòu)造子類,析構(gòu)相反:先析構(gòu)子類,再析構(gòu)父類
class Base
{
public:
Base()
{
std::cout<<"Base 的默認(rèn)構(gòu)造函數(shù)"<<endl;
}
~Base()
{
std::cout<<"Base 的析構(gòu)函數(shù)"<<endl;
}
};
class Son: public Base
{
public:
Son()
{
std::cout<<"Son 的默認(rèn)構(gòu)造函數(shù)"<<endl;
}
~Son()
{
std::cout<<"Son 的析構(gòu)函數(shù)"<<endl;
}
};
void test()
{
Son s;
}
4.6.5 繼承中同名成員的處理方式
問題:當(dāng)子類與父類出現(xiàn)同名成員,如何通過子類對象,訪問子類或者父類的同名數(shù)據(jù)呢?
- 訪問子類 同名成員,直接訪問即可
- 訪問父類 同名成員,需要加作用域
總結(jié):
- 1.子類對象可以直接訪問到子類中的同名成員
- 2.子類對象加作用域可以訪問到父類同名成員
- 3.當(dāng)子類和父類擁有同名的成員函數(shù)時,子類會隱藏父類中所有的同名函數(shù),需要加作用域才可以訪問父類的同名函數(shù)
class Base
{
public:
Base()
{
m_a = 100;
}
void fun()
{
cout<<"Base - fun()"<<endl;
}
void fun(int a)
{
cout<<"Base - fun(int a)"<<endl;
}
int m_a;
};
class Son : public Base
{
public:
Son()
{
m_a = 200;
}
void fun()
{
cout<<"Son - fun()"<<endl;
}
int m_a;
};
void test()
{
Son s;
cout<<"Son 下的 m_a = "<<s.m_a<<endl;
//通過子類對象訪問父類中同名成員,需要加作用域Base
cout<<"Base 下的 m_a = "<<s.Base::m_a<<endl;
}
void test2()
{
Son s;
s.fun(10);
//通過子類對象訪問父類中同名成員,需要加作用域Base
s.Base::fun();
//當(dāng)子類出現(xiàn)和父類同名的成員函數(shù)時,子類會隱藏掉父類所有的同名成員函數(shù)
//通過作用域才能訪問到被隱藏的同名成員函數(shù)
s.Base::fun(10);
}
4.6.6 繼承同名 靜態(tài)成員 的處理方式
問題:繼承中同名的靜態(tài)成員在子類對象上如何進(jìn)行訪問?
靜態(tài)成員和非靜態(tài)成員出現(xiàn)同名,處理方式一致
- 訪問子類同名成員,直接訪問即可
- 訪問父類同名成員,需要加作用域
//通過對象訪問
Son s;
cout<<s.m_a<<endl;
cout<<s.Base::m_a<<endl;
//通過類名訪問
cout<<Son::m_a<<endl;
//第一個::代表通過類名方式訪問,第二個::代表訪問父類作用域下
cout<<Son::Base::m_a<<endl;
總結(jié):同名靜態(tài)成員的處理方式和非靜態(tài)的完全一致,只不過有2種訪問方式(通過對象和通過類名)
4.6.7 多繼承語法
c++中運行一個類繼承多個類
語法:class 子類:繼承方式 父類 1,繼承方式 父類 2 ···
多繼承可能會引發(fā)父類中有同名成員出現(xiàn),需要加作用域區(qū)分
c++開發(fā)中不建議用多繼承
class A
{
public:
A():m_a(100)
{
}
int m_a;
};
class B
{
public:
B():m_a(200)
{
}
int m_a;
};
class C : public A, public B
{
public:
C()
{
m_c = 300;
}
int m_c;
};
int main(int argc, char** argv) {
C c;
//當(dāng)父類出現(xiàn)同名成員,需要加作用域區(qū)分
cout<<c.A::m_a<<" "<<c.B::m_a<<" "<<c.m_c<<endl;
return 0;
}
4.6.8 菱形繼承
概念:
2個子類繼承同一個父類
又有某個類統(tǒng)計繼承2個子類
這種繼承被稱為菱形繼承,或者鉆石繼承
class Animal
{
public:
int m_Age;
};
//繼承前面加virtual關(guān)鍵字,變?yōu)樘摾^承
//此時公共父類Animal稱為虛基類
class Sheep : virtual public Animal{};//羊
class Camel : virtual public Animal{};//駱駝
class Alpaca : public Sheep , public Camel{};//羊駝
int main() {
Alpaca a;
a.Sheep::m_Age = 100;
a.Camel::m_Age = 200;
cout<<"a.Sheep::m_Age ="<<a.Sheep::m_Age<<endl;
cout<<"a.Camel::m_Age ="<<a.Camel::m_Age<<endl;
cout<<"am_Age ="<<a.m_Age<<endl;//三個打印都是200,數(shù)據(jù)只有一份
return 0;
}
總結(jié):
- 菱形繼承帶來主要問題是 子類繼承多份相同的數(shù)據(jù),導(dǎo)致資源浪費。
- 利用 虛繼承 解決菱形繼承問題
- 虛繼承的本質(zhì)是繼承指針,通過指針去訪問同一份數(shù)據(jù)(m_Age)
4.7 多態(tài)
多態(tài)是C++面向?qū)ο蟮娜筇匦灾?/strong>
4.7.1 多態(tài)的基本概念
多態(tài)分為2類:
- 靜態(tài)多態(tài):函數(shù)重載和運算符重載 屬于靜態(tài)多態(tài),復(fù)用函數(shù)名
- 動態(tài)多態(tài):派生類和虛函數(shù)實現(xiàn)運行時多態(tài)
靜態(tài)多態(tài)和動態(tài)多態(tài)的區(qū)別:
- 靜態(tài)多態(tài)的函數(shù)地址早綁定 - 編譯階段確定函數(shù)地址
- 動態(tài)多態(tài)的函數(shù)地址晚綁定 - 運行階段確定函數(shù)地址
靜態(tài)多態(tài)
class Animal
{
public:
void speak()
{
cout<<"動物在說話"<<endl;
}
} ;
class Cat : public Animal
{
public:
//重寫: 函數(shù)返回值 參數(shù)列表完全相同
void speak()
{
cout<<"貓在說話"<<endl;
}
};
//地址早綁定 在編譯階段確定函數(shù)地址
//無論傳什么,都只會調(diào)用父類的函數(shù)
void doSpeak(Animal &a)
{
a.speak();
}
void test()
{
Cat c;
doSpeak(c);
}
int main(int argc, char** argv) {
test();//輸出結(jié)果:動物在說話
return 0;
}
動態(tài)多態(tài)
class Animal
{
public:
//加上virtual,變成虛函數(shù),就可以晚綁定 ,子類的函數(shù)可加virtual 或者不加
virtual void speak()
{
cout<<"動物在說話"<<endl;
}
} ;
重寫: 函數(shù)返回值 參數(shù)列表完全相同
重載:函數(shù)參數(shù)列表不同,返回值相同
總結(jié):
動態(tài)多態(tài)的滿足條件
- 1.有繼承關(guān)系
- 2.子類重寫父類的虛函數(shù)
動態(tài)多態(tài)的使用
父類的指針或者引用,執(zhí)行子類對象
class A
{
public:
int fun(){}
}
這里 sizeof(A)= 1
class A
{
public:
virtual int fun(){}
}
這里 sizeof(A)= 4,因為加上 virtual ,fun就變成了一個指針(指針都是4個字節(jié))vfptr:虛函數(shù)指針
4.7.2 多態(tài)案例-計算器
普通代碼實現(xiàn)計算器
//普通代碼實現(xiàn)計算器
class NomalCal
{
public:
int getResutl(int oper)
{
switch(oper)
{
case '+' :
return m_a + m_b;
break;
case '-' :
return m_a - m_b;
break;
case '*' :
return m_a * m_b;
break;
//如果想擴展功能:除法,需要修改源碼
//開發(fā)中提倡開閉原則:對擴展進(jìn)行開放,對修改進(jìn)行關(guān)閉
default:
return 0;
}
}
int m_a;
int m_b;
} ;
void test1()
{
NomalCal nc;
nc.m_a = 10;
nc.m_b = 10;
cout<<nc.m_a<< " + " <<nc.m_b<<" = "<<nc.getResutl('+')<<endl;
}
多態(tài)實現(xiàn)計算器
//多態(tài)實現(xiàn)
class BaseCal
{
public:
virtual int getResult(){return 0;}
int m_a;
int m_b;
} ;
class AddCal : public BaseCal
{
int getResult()
{
return m_a+m_b;
}
};
有其他功能繼續(xù)添加:比如減法
void test2()
{
//多態(tài)使用條件:父類指針或者引用指向之類對象
BaseCal *bc = new AddCal;
bc->m_a = 20;
bc->m_b = 10;
cout<<bc->m_a<< " + " <<bc->m_b<<" = "<<bc->getResult()<<endl;
delete bc;//記得釋放
}
多態(tài)的優(yōu)點:
- 代碼結(jié)構(gòu)清晰
- 可讀性強
- 利于前期和后期的擴展以及維護(hù),符合開閉原則
4.7.3 純虛函數(shù)和抽象類
在多態(tài)中,通常父類中的虛函數(shù)是毫無意義的,主要調(diào)用子類的重寫的函數(shù)。
因此可以把虛函數(shù)改為純虛函數(shù)
語法:virtual 返回值類型 函數(shù)名(參數(shù)列表) = 0
當(dāng)類中只要有1個純虛函數(shù),也把類稱為抽象類
抽象類的特點:
- 無法實例化對象
- 子類必須重寫抽象類中的純虛函數(shù),否則也屬于抽象類
class Base//抽象類:類中只要有一個純虛函數(shù)
{
public:
//純虛函數(shù)
virtual void fun() = 0;
} ;
class Son : public Base
{
public:
virtual void fun(){}
};
4.7.4 多態(tài)案例2-制作飲品
1.茶的抽象類
class AbsDrinKing
{
public:
//1.煮水
virtual void boil() = 0;
//2.沖泡
virtual void brew() = 0;
//3.倒入杯中
virtual void pourInCup() = 0;
//4.加入輔料
virtual void putSomething() = 0;
void makeDrink()
{
boil();
brew();
pourInCup();
putSomething();
}
};
2.做咖啡
class CofferDrink : public AbsDrinKing
{
//1.煮水
virtual void boil()
{
cout<<"煮咖啡"<<endl;
};
//2.沖泡
virtual void brew()
{
cout<<"泡咖啡"<<endl;
}
//3.倒入杯中
virtual void pourInCup()
{
cout<<"把咖啡倒入杯中"<<endl;
}
//4.加入輔料
virtual void putSomething()
{
cout<<"加入糖"<<endl;
}
};
3.做奶茶同理
省略···
4.制作函數(shù)
void doWork(AbsDringKing *abs)
{
abs->makeDrink();
}
4.7.5 虛析構(gòu)和純虛析構(gòu)
多態(tài)使用時,如果子類中有屬性開辟到堆區(qū),那么父類指針在釋放時無法調(diào)用到子類的析構(gòu)代碼。
解決方式:將父類的析構(gòu)函數(shù)改為虛析構(gòu)或者純虛析構(gòu)
虛析構(gòu)和純虛析構(gòu)共性:
- 可以解決父類指針釋放子類對象的問題
- 都需要有具體的函數(shù)實現(xiàn)
虛析構(gòu)和純虛析構(gòu)的區(qū)別:
- 如果是純虛析構(gòu),則該類屬于抽象類,無法實例化對象,而虛析構(gòu)可以!
虛析構(gòu)語法:
virtual 類名(){ }
純虛析構(gòu)語法:
virtual 類名() = 0;
類外還要寫實現(xiàn)
類名::~類名(){ }
可能出現(xiàn)內(nèi)存泄漏

解決辦法:
1.父類析構(gòu)函數(shù)加上virtual,變成虛析構(gòu)
class Animal
{
public:
Animal(){cout<<"Animal 構(gòu)造函數(shù)調(diào)用"<<endl;}
virtual ~Animal(){cout<<"Animal 虛析構(gòu)函數(shù)調(diào)用"<<endl;}//虛析構(gòu)
virtual void speak() = 0;
};
2.純虛析構(gòu)
class Animal
{
public:
Animal(){cout<<"Animal 構(gòu)造函數(shù)調(diào)用"<<endl;}
virtual ~Animal() = 0;//純虛析構(gòu)
virtual void speak() = 0;
};
Animal::~ Animal()
{
//如果父類也申請的堆區(qū)內(nèi)存,可以在這里釋放
cout<<"Animal 純虛析構(gòu)函數(shù)調(diào)用"<<endl;
}
總結(jié):
- 1.虛析構(gòu)和純虛析構(gòu)是用來解決 通過父類指針 釋放子類對象的情況
- 2.如果子類中沒有堆區(qū)數(shù)據(jù),可以不寫虛析構(gòu)或者純虛析構(gòu)
- 3.擁有純虛函數(shù)的類也屬于抽象類
4.7.6 多態(tài)案例3-電腦組裝
//抽象零件
//1.抽象CPU
class CPU
{
public:
virtual void cal() = 0;
};
//2.抽象顯卡
class VideoCard
{
public:
virtual void display() = 0;
};
//3.抽象內(nèi)存
class Memory
{
public:
virtual void storage() = 0;
} ;
//電腦類
class Computer
{
public:
Computer(CPU *cpu,VideoCard *vc,Memory * mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
cout<<"Computer 構(gòu)造函數(shù)"<<endl;
}
~ Computer()
{
if(m_cpu!=NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if(m_vc!=NULL)
{
delete m_vc;
m_vc = NULL;
}
if(m_mem!=NULL)
{
delete m_mem;
m_mem = NULL;
}
cout<<"Computer 析構(gòu)函數(shù) 釋放資源"<<endl;
}
void doWork()
{
//父類指針調(diào)用子類對象
m_cpu->cal();
m_vc->display();
m_mem->storage();
}
private:
CPU *m_cpu;
VideoCard *m_vc;
Memory *m_mem;
};
//intel 電腦
class IntelCPU : public CPU
{
public:
void cal(){cout<<"Intel cpu 工作了"<<endl;}
};
class IntelVideoCard : public VideoCard
{
public:
void display(){cout<<"Intel 顯卡 工作了"<<endl;;}
};
class IntelMemory : public Memory
{
public:
void storage(){cout<<"Intel 內(nèi)存 工作了"<<endl;}
};
//蘋果電腦省略***
void test()
{
//第一臺電腦
IntelCPU * ic = new IntelCPU;
IntelVideoCard *ivc = new IntelVideoCard;
IntelMemory *imem = new IntelMemory;
Computer *com1 = new Computer(ic,ivc,imem);
com1->doWork();
delete com1;
}
int main(int argc, char** argv) {
test();
return 0;
}
5.文件操作
文件類型分為2類:
1.文本文件:文本以ASCII碼形式存儲在計算機中
2.二進(jìn)制文件:文本以二進(jìn)制形式存儲在計算機中
頭文件:<fstream>
操作文件的三大類:
1.ofstream:寫操作
2.ifstream:讀操作
3.fstream:讀寫操作
5.1文本文件
5.1.1 寫文件
寫文件步驟:
1.包含頭文件
#include <fstream>
2.創(chuàng)建流對象
ofstream ofs;
3.打開文件
ofs.open("文件路徑",打開方式)
4.寫數(shù)據(jù)
ofs<<"數(shù)據(jù)";
5.關(guān)閉文件
ofs.close();
文件的打開方式:
| 打開方式 | 解釋 |
|---|---|
| ios::in | 讀 |
| ios::out | 寫 |
| ios::ate | 初始位置:文件尾 |
| ios::app | 追加方式寫文件 |
| ios::trunc | 如果文件存在,先刪除 再創(chuàng)建 |
| ios::binary | 二進(jìn)制形式 |
//文本文件 寫文件
void test()
{
ofstream ofs;
ofs.open("test.txt",ios::out);
ofs<<"張三 18";
ofs.close();
}
5.1.2 讀文件
讀文件步驟:
1.包含頭文件
#include <fstream>
2.創(chuàng)建流對象
ifstream ifs;
3.打開文件
ifs.open("文件路徑",打開方式)
4.寫數(shù)據(jù)
四種方式讀?。?br>
5.關(guān)閉文件
ifs.close();
void test()
{
ifstream ifs;
ifs.open("test.txt",ios::in);
if(!ifs.is_open())
{
cout<<"文件打開失敗"<<endl;
return;
}
//方式1:讀數(shù)據(jù)
// char buf[1024] = {0};
// while(ifs >> buf)
// {
// cout<<buf<<endl;
// }
//方式2:讀數(shù)據(jù)
// char buf[1024] = {0};
// while(ifs.getline(buf,sizeof(buf)))
// {
// cout<<buf<<endl;
// }
//方式3:讀數(shù)據(jù)
// string buf;
// while(getline(ifs,buf))
// {
// cout<<buf<<endl;
// }
//方式4:讀數(shù)據(jù)
char c;
while((c = ifs.get())!=EOF)
{
cout<<c;
}
ifs.close();
}
5.2 二進(jìn)制文件
以二進(jìn)制的方式對文件進(jìn)行讀寫操作,
打開方式要指定為ios::binary
5.2.1 二進(jìn)制方式寫文件
class Person
{
public:
char m_Name[64];
int m_age;
} ;
void test()
{
//1.引入頭文件 #include <fstream>
//2.創(chuàng)建流對象
ofstream ofs;//或者ofs("person.txt",ios::out|ios::binary);
//3.打開文件
ofs.open("person.txt",ios::out|ios::binary);
//4.寫文件
Person p = {"張三",18};
ofs.write((const char*)&p,sizeof(Person));
//5.關(guān)閉文件
ofs.close();
}
5.2.2 二進(jìn)制方式讀文件
二進(jìn)制方式讀文件主要利用流對象的成員函數(shù)read
函數(shù)原型:istream& read(char *buffer,int len);
class Person
{
public:
char m_Name[64];
int m_age;
} ;
void test()
{
//1.引入頭文件
//2.創(chuàng)建流對象
ifstream ifs;
//3.打開文件
ifs.open("person.txt",ios::in);
if(!ifs.is_open())
{
cout<<"文件打開失??!"<<endl;
return;
}
// 4.讀文件
Person p;
ifs.read((char *)&p,sizeof(Person));
cout<<p.m_age<<" "<<p.m_Name<<endl;
}
繼續(xù)當(dāng)一名咸魚( ̄︶ ̄)!