C++類與對象(1) 類的設計:可以把屬性和行為放在不同的許可權下 struct和class區別在於某人的訪問許可權不同 struct:預設共有 class:預設私有 對象的初始化和清理 如果我們不寫,系統會自己給我沒寫 構造函數的語法 類名(){} 沒有返回值,也不寫void 函數名和類型相同 可以有 ...
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