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
靜態成員分成:
- 靜態成員變數
- 所有物件共享一份資料
- 再編譯階段分配記憶體
- 類內宣告,類外初始化
- 靜態成員函式
- 所有物件共享一個函式
- 靜態成員函式只能訪問靜態成員變數(函式體內無法區分普通變數是那個物件的成員)
- 也是有訪問許可權的,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指標是隱含在每一個非靜態成員函式內的一種指標,不用定義,直接使用
用途
- 當形參和成員變數重名時,可用this來區分
- 在類的非靜態成員函式返回物件本身(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;
}
結果:
運算子的過載
概念:對已有運算子重新進行定義,賦予其另一種功能,以適應不同的資料型別
對於內建的資料型別,系統知道如何進行運算
加號運算子過載(其他同理)
- 成員函式過載+號
本質: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(相加成功)
- 全域性函式過載+號
本質: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