一,日期類實現
學習建議:
對于計算機學習來說,調試十分重要,所以在日常學習中一定要加大代碼練習,刷代碼題和課后自己敲出課上代碼例題,注意不要去對比正確代碼或者網上找正確代碼直接使用,一定要自己去調試更改。
調試技巧:1,通過不斷改變運算對象改變調試范圍(學會縮小和放大范圍),判斷可能出現錯誤的程序范圍
? ? ? ? ? ? ? ? ? ?2,當不知道問題出現在哪的時候,對于一個對象用監視一步一步走,看該對象的變化,再對比自己的計算,判斷出那塊代碼出現問題
還有很多調試技巧需要自己去理解總結
Date.h
#pragma once#include<iostream>
using namespace std;
#include<assert.h>class Date
{// 友元函數聲明 friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 直接定義類??,他默認是inline // 只有頻繁調?的小函數在h文件中定義//得到月份所對應的天數int GetMonthDay(int year, int month){assert(month > 0 && month < 13);static int monthDayArray[13] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))//閏年的二月份是20天的判斷+返回{// 2return 29;}else{return monthDayArray[month];}}//d1+=天數Date& operator+=(int day);Date operator+(int day);//d1-=天數Date& operator-=(int day);Date operator-(int day);// ++d1 ->d1.operator++() 前置++Date& operator++();// d1++ ->d1.operator++(1) 后置++ 為了區分,構成重載,給后置++,強?增加了?個int形參 // 這?不需要寫形參名,因為接收值是多少不重要,也不需要? // 這個參數僅僅是為了跟前置++構成重載區分 Date operator++(int);//--d1 前置--Date& operator--();//d1-- 后置--Date operator--(int);//大于 大于等于 小于 小于等于 等于 不等于bool operator<(const Date& d);bool operator<=(const Date& d);bool operator>(const Date& d);bool operator>=(const Date& d);bool operator==(const Date& d);bool operator!=(const Date& d);// d1-d2int operator-(const Date& d); // 流插? // 不建議,因為Date* this占據了?個參數位置,使?d<<cout不符合習慣 //void operator<<(ostream& out);//打印void Print();
private:int _year;int _month;int _day;
};//重載
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
Date.cpp
#include"Date.h"bool Date::CheckDate()//檢查日期是否正確·
{if (_month < 1 || _month > 12|| _day < 1 || _day > GetMonthDay(_year, _month)){return false;//天數,月數,年數不對時直接報錯}else{return true;}
}Date::Date(int year, int month, int day)//對日期賦值
{_year = year;_month = month;_day = day;if (!CheckDate()){cout << "?期?法" << endl;}
}void Date::Print()
{cout << _year << "-" << _month << "-" << _day << endl;
}// d1 < d2
bool Date::operator<(const Date& d)
{if (_year < d._year)//先對年份進行判斷{return true;}else if (_year == d._year){if (_month < d._month)//年份相同再對月份進行判斷{return true;}else if (_month == d._month)//月份相同時則d1<d2{return _day < d._day;}}return false;
}// d1 <= d2
bool Date::operator<=(const Date& d)
{return *this < d || *this == d;//直接借用上述的 < 運算符重載
}//同理
//d1>d2
bool Date::operator>(const Date& d)
{return !(*this <= d);//直接利用上述小于等于運算符取逆
}//d1>=d2
bool Date::operator>=(const Date& d)
{return !(*this < d);
}//d1==d2
bool Date::operator==(const Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}//d1!=d2
bool Date::operator!=(const Date& d) const
{return !(*this == d);
}//d1+=天數
Date& Date::operator+=(int day)//重載運算函數是成員函數,,第一個參數是this指針,參數比運算對象少一個
{if (day < 0)// 當要加的數為負數時要求借用減法完成{return *this -= -day;}_day += day;//完成加法運算while (_day > GetMonthDay(_year, _month))//天數超越了對應月份的天數{_day -= GetMonthDay(_year, _month);//天數減去對應月份的天數++_month;//同時月份跳轉到下一個月if (_month == 13)//當月份跳轉到13時進入下一年,同時月份重新定義為1{_year++;_month = 1;}}return *this;//返回第一個運算對象
}//d1+天數
Date Date::operator+(int day)//完成加法運算
{Date tmp(*this);//拷貝一份*this用拷貝的tmp完成+=運算tmp += day;return tmp;
}//d1-=天數
Date& Date::operator-=(int day)//同理,重載運算函數是成員函數
{if (day < 0)//減去的數為負數的情況{return *this += -day;}_day -= day;//減法運算while (_day <= 0)//天數小于等于0{--_month;//月份-1if (_month = 0)//當月份為0時,跳轉到上一年,同時月份重新定義為12{_year--;_month = 12;}_day += GetMonthDay(_year, _month); // 不斷借助上一個月天數,使得負_day絕對值變小,直至不滿足循環條件 }return *this;//返回自身
}//d1-天數
Date Date::operator-(int day)
{Date tmp(*this);//自身不能改變,拷貝,借助拷貝對象改變返回tmp -= day;return tmp;
}//++d1
Date& Date::operator++()
{*this += 1;return *this;
}//d1++
Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;
}//--d1
Date& Date::operator--()
{*this -= 1;return *this;
}//d1--
Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}//d1-d2
int Date::operator-(const Date& d)
{int flag = 1;//假設法Date max = *this;//假設第一個大,第二個小Date min = d;if (*this < d)//假設錯了就交換{max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;//借助n計算相差個數}return n * flag;//flag就是在兩個數大小不同情況下確定運算是正還是負
}ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "?" << d._day << "?" << endl;return out;
}istream& operator>>(istream& in, Date& d)
{cout << "請依次輸?年??:>";in >> d._year >> d._month >> d._day;if (!d.CheckDate()){cout << "?期?法" << endl;}return in;
}
d1-d2的實現思路:
思路一:
1,小的月和天,朝大的對齊,算出來差了多少天
2,年直接減,算出來差多少天
例子:2025.3.11? 和2020.5.1
先將3.11向5.1對齊發現三月十一到五月一號差了17+30+1天,此時為2025.5.1和2020.5.1相差五年再看其中閏年,從而計算相差天數再減去48天
思路二:直接賦用
將小的日期不斷++直到與大的一致,算出來++了多少次。
上述代碼就是借助思路二實現的
Test.cpp
#include"Date.h"
void TestDate1()
{// 這?需要測試?下?的數據+和- Date d1(2024, 4, 14);Date d2 = d1 + 30000;d1.Print();d2.Print();Date d3(2024, 4, 14);Date d4 = d3 - 5000;d3.Print();d4.Print();Date d5(2024, 4, 14);d5 += -5000;d5.Print();
}void TestDate2()
{Date d1(2024, 4, 14);Date d2 = ++d1;d1.Print();d2.Print();Date d3 = d1++;d1.Print();d3.Print();d1.operator++(1);d1.operator++(100);d1.operator++(0);d1.Print();
}void TestDate3()
{Date d1(2024, 4, 14);Date d2(2034, 4, 14);int n = d1 - d2;cout << n << endl;n = d2 - d1;
}void TestDate4()
{Date d1(2024, 4, 14);Date d2 = d1 + 30000;// operator<<(cout, d1)cout << d1;cout << d2;cin >> d1 >> d2;cout << d1 << d2;
}
void TestDate5()
{const Date d1(2024, 4, 14);d1.Print();//d1 += 100;d1 + 100;Date d2(2024, 4, 25);d2.Print();d2 += 100;d1 < d2;d2 < d1;
}int main()
{return 0;
}
二,取地址運算符重載
1,const成員函數
(1)將const修飾的成員函數稱之為const成員函數,const修飾成員函數放到成員函數參數列表的后 ?。
(2)const實際修飾該成員函數隱含的this指針,表明在該成員函數中不能對類的任何成員進?修改。 const修飾Date類的Print成員函數,Print隱含的this指針由 Date* const this 變為 const Date* const this
在上述的成員函數后面都可以加上const。
下面例子出現權限放大:調用print函數需要傳遞this指針,也就是&d1,而this指針這里相當于Date const this
而上面的const修飾的相當于const Date*,傳遞過去時會發生權限放大,報錯
例子2:
const Date*傳遞過去會發生權限的平移
Date*傳遞過去發生權限縮小
2,取地址運算符重載
取地址運算符重載分為普通取地址運算符重載和const取地址運算符重載,?般這兩個函數編譯器?動 ?成的就可以夠我們?了,不需要去顯?實現。除??些很特殊的場景,?如我們不想讓別?取到當前類對象的地址,就可以??實現?份,胡亂返回?個地址。
class Date
{
public :Date* operator&(){return this;// return nullptr;//當不想讓別人取到地址,可以返回nullptr或者return (Date*)0x112245EF 返回一個假地址}const Date* operator&()const{return this;// return nullptr;}
private :int _year ; // 年 int _month ; // ? int _day ; // ?
};
三,再探構造函數
(1)之前我們實現構造函數時,初始化成員變量主要使?函數體內賦值,構造函數初始化還有?種? 式,就是初始化列表,初始化列表的使??式是以?個冒號開始,接著是?個以逗號分隔的數據成 員列表,每個"成員變量"后?跟?個放在括號中的初始值或表達式。
class Date
{
public://初始化列表Date(int& x, int year = 1, int month = 1, int day = 1):_year(year), _month(month), _n(10),_ref(x)
//可以混合使用兩者初始化方法{_day = day;_n=1;//不可行,初始化列表的變量只能初始化一次}private:int _year;int _month;int _day;// 必須在初始化列表初始化的成員const int _n;//常量初始化int& _ref;//引用初始化};
(2)每個成員變量在初始化列表中只能出現?次,語法理解上初始化列表可以認為是每個成員變量定義 初始化的地?。
(3)引?成員變量,const成員變量,沒有默認構造的類類型變量,必須放在初始化列表位置進?初始 化,否則會編譯報錯。
class Date
{
public:Date(int& x, int year = 1, int month = 1, int day = 1):_year(year),_month(month),_day(day),_t(12),_ref(x),_n(1){// error C2512: “Time”: 沒有合適的默認構造函數可? // error C2530 : “Date::_ref” : 必須初始化引? // error C2789 : “Date::_n” : 必須初始化常量限定類型的對象 }void Print() const
{cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;Time _t; // 沒有默認構造 int& _ref; // 引? const int _n; // const
};
(4)C++11?持在成員變量聲明的位置給缺省值,這個缺省值主要是給沒有顯?在初始化列表初始化的 成員使?的。
class Time
{
public:Time(int hour):_hour(hour){cout << "Time()" << endl;}
private:int _hour;
};class Date
{
public:Date():_month(2){cout << "Date()" << endl;}void Print() const{cout << _year << "-" << _month << "-" << _day << endl;}private:// 注意這里不是初始化,這里給的是缺省值,這個缺省值是給初始化列表的// 如果初始化列表沒有顯示初始化,默認就會用這個缺省值初始化int _year = 1;//year沒有在列表中初始化,但是通過了缺省值進行初始化。int _month = 1;int _day;const int _n = 1;Time _t = 10;int* _ptr = (int*)malloc(12);//缺省值也可以是一個表達式,其實只要是一個值就行
};class Stack
{
public:Stack(int n = 4){cout << "Stack(int n)" << endl;}
private://...
};class MyQueue
{
public:MyQueue(int n);_pushst(n),popost(n){}private:// 聲明Stack _pushst;//自定義類型沒有默認構造,需要顯示的寫構造函數Stack _popst;const int _n = 1; // 缺省值初始化
};
int main()
{Date d1;MyQueue q;//調用初始化構造函數return 0;
}
(5)盡量使?初始化列表初始化,因為那些你不在初始化列表初始化的成員也會?初始化列表,如果這 個成員在聲明位置給了缺省值,初始化列表會?這個缺省值初始化。如果你沒有給缺省值,對于沒 有顯?在初始化列表初始化的內置類型成員是否初始化取決于編譯器,C++并沒有規定。對于沒有 顯?在初始化列表初始化的?定義類型成員會調?這個成員類型的默認構造函數,如果沒有默認構 造會編譯錯誤。
(6)初始化列表中按照成員變量在類中聲明順序進?初始化,跟成員在初始化列表出現的的先后順序? 關。建議聲明順序和初始化列表順序保持?致。
初始化列表:
?論是否顯?寫初始化列表,每個構造函數都有初始化列表;
?論是否在初始化列表顯?初始化成員變量,每個成員變量都要?初始化列表初始化;
練習題:
#include<iostream>
using namespace std;
class A
{public:A(int a):_a1(a), _a2(_a1)//初始化列表中按照成員變量在類中聲明順序進?初始化,跟成員在初始化列表出現的的先后順序?關{}void Print(){cout << _a1 << " " << _a2 << endl;}private:int _a2 = 2;//列表中已經進行了顯式的初始化,所以這里與缺省值無關了int _a1 = 2;
};int main()
{A aa(1);aa.Print();
}
先用a1初始化a2,但a1未進行初始化,所以a1是一個隨機值,a2就是一個隨機值。
再用a=1初始化a1,a1就是1
上述程序的運行結果是:輸出1和隨機值
四,類型轉換
(1)C++?持內置類型隱式類型轉換為類類型對象,需要有相關內置類型為參數的構造函數。
不同編譯器是否支持優化是不一樣的。
(2)構造函數前?加explicit就不再?持隱式類型轉換。
(3)類類型的對象之間也可以隱式轉換,需要相應的構造函數?持。
class A
{
public:// 構造函數explicit就不再支持隱式類型轉換// explicit A(int a1)A(int a1):_a1(a1){cout << "A(int a1)" << endl;}A(const A& aa){cout << "A(const A& aa)" << endl;}//explicit A(int a1, int a2)A(int a1, int a2):_a1(a1), _a2(a2){}void Print(){cout << _a1 << " " << _a2 << endl;}int Get() const{return _a1 + _a2;}
private:int _a1 = 1;int _a2 = 2;
};//自定義類型之間也可以進行類型轉換
class B
{
public:B(const A& a):_b(a.Get()){}
private:int _b = 0;
};class Stack
{
public:void Push(const A& aa)//臨時對象有常性{}void Push(const B& bb){}
};int main()
{// 構造A aa1(1);// 隱式類型轉換// 1構造臨時對象,臨時對象拷貝構造aa2,優化后直接構造// 1構造?個A的臨時對象,再?這個臨時對象拷?構造aa3 // 編譯器遇到連續構造+拷?構造->優化為直接構造 A aa2 = 1;A& aa3 = aa2;//前面加上一個const時為普通引用,不可行,因為臨時對象具有常性const A& aa4 = 1;Stack st;A aa10(10);st.Push(aa10);//原來需要定義一個aa10對象,拷貝構造給Push函數,再進行push操作//而類型轉換可以直接創建臨時變量進行拷貝構造,進行優化,傳參st.Push(10); // 隱式類型轉換支持這樣的間接傳參// C++11之后才?持多參數轉化 A aa3 = { 2,2 };A aa5(1, 1);// 隱式類型轉換// { 1, 1 }構造臨時對象,臨時對象拷貝構造aa5,優化后直接構造A aa6 = { 1, 1 };A aa11(11, 11);st.Push(aa11);st.Push({ 11, 11 }); // 隱式類型轉換支持這樣的間接傳參B b1(aa1);//自定義類型轉換為自定義類型// 隱式類型轉換B b1 = aa1;B b3(aa1);st.Push(b3);st.Push(aa1);return 0;
}
當優化關閉后,會出現構造+拷貝構造
五,static成員
static:
(1)?static修飾的成員變量,稱之為靜態成員變量,靜態成員變量?定要在類外進?初始化。
(2)靜態成員變量為所有類對象所共享,不屬于某個具體的對象,不存在對象中,存放在靜態區,生命周期是全局的。因此對象大小不包括靜態成員
(3)?static修飾的成員函數,稱之為靜態成員函數,靜態成員函數沒有this指針。
(4)靜態成員函數中可以訪問其他的靜態成員,但是不能訪問?靜態的,因為沒有this指針。
(5)?靜態的成員函數,可以訪問任意的靜態成員變量和靜態成員函數。
(6)突破類域就可以訪問靜態成員,可以通過類名::靜態成員或者對象.靜態成員來訪問靜態成員變量 和靜態成員函數。
(7)靜態成員也是類的成員,受public、protected、private訪問限定符的限制。
(8)靜態成員變量不能在聲明位置給缺省值初始化,因為缺省值是個構造函數初始化列表的,靜態成員 變量不屬于某個對象,不?構造函數初始化列表。
聲明:
private:// 類??聲明 static int _scount;
初始化:
// 類外?初始化
int A::_scount = 0;
計算程序中創建了多少個類的對象
通過count看調用了多少次構造函數
靜態成員變量不走初始化列表,不能給缺省值。
通過GetACount公有的成員函數對私有的靜態成員變量進行訪問。
靜態成員變量是公有時的訪問方式
1,靜態成員變量屬于所有對象,可以通過指定對象來訪問。
count<<a1._scount<<endl;
2,可以指定類域進行訪問。
count<<A::_scount<<endl;
注意:靜態成員函數無法對非靜態成員進行訪問
原因:(1)靜態成員沒有this指針
(2)而非靜態成員可以通過類域訪問
題目1:
思路:通過Solution成員函數構建對象,從而去調用Sum類域中的構造函數,構造對象大小有多大就調用多少次,并且是靜態成員變量,所以全局調用,每次使用的ret和i都是上次變化后的,來實現求和。
一般刷題網站都是支持變長數組的,但VS是不支持的。
VS中更改:
Sum*ptr=new Sum[n];
delete[]ptr;//動態開辟
#include<iostream>
using namespace std;
class A
{
public:A(){++_scount;}
private://.......
};int main()
{A a1, a2;A a3(a1);//定義一個對象調用一次構造A aa1[10];//一次性定義多個對象,調用多次構造函數return 0;
}
題目2:
構造和析構順序
構造:全局變量在main函數之前創建,調用其構造函數,C最先調用,而main函數中靜態成員變量只要在使用時才會構造,所以先調用A,B的構造。同樣,對于多次調用有靜態變量函數只會調用一次構造函數,因為只會初始化一次。
析構:main函數棧幀先銷毀,先銷毀局部變量,后定義的先析構,對于全局變量(可以通過監視地址,進入析構函數監視this指針觀察)發現靜態成員變量先析構。
六,友元
1,流插入和提取的運算符重載
void TestDate6()
{Date d1(2025,3,12);printf("%");
}
對于printf和scanf只能打印內置類型,而對于自定義類型無法進行打印,C++為了解決這個問題,設計了流插入和流提取。
void TestDate6()
{int i = 22;double d = 1.2;cout << i;cout.operator<<(i);//運算符重載 相當于調用運算符重載調用函數,自動識別類型是因為重載機制,自動調用對應類型的重載cout << endl;cout << d;cout.operator<<(d);cout << i << " " << d << endl;*/
}
這里下圖更能直觀解釋每次重載會調用其對于類型的重載函數。
但是這種方法效率會降低,可以加上以下三行代碼提高效率。
ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
流插入ostream函數:

如:
ostream& operator<<(ostream& out){out << _year << "年" << _month << "月" << _day << "日" << endl;return out;}
但是對應的會出現錯誤
void TestDate6()
{//cout<<d1此時會報錯,因為左右參數配不上//d1 << cout;也會有一點小錯誤
}
參數配不上的圖解:這里out就是cout的別名
所以上述ostream函數有錯誤,不能重載為成員函數,否則參數會對不上。
//cout<<d1參數對應上了
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}
這里問題就是訪問私有變量:
解決方法:(1)提供一個get成員函數? (2)聲明為友元函數
......
//友元聲明 可以放到任意位置,一般放到類開始
friend ostream& operator<<(ostream& out, const Date& d);
private:int _year;int _month;int _day;
};
但是此時友元為全局函數,會在多個文件中包含,符號表就會重復,需要聲明和定義分離。
解決方法:(1)h中聲明,cpp中定義? (2)在函數前加上inline
流提取函數:
istream& operator>>(istream& in, Date& d);
不加const,因為提取的值要放到該對象中,所以加引用。
友元聲明:
friend istream& operator>>(istream& in, Date& d);
inline istream& operator>>(istream& in, Date& d)
{while (1){cout << "請依次輸入年月日:>";in >> d._year >> d._month >> d._day;if (!d.CheckDate()){cout << "輸入日期非法,請重新輸入!!!" << endl;}else{break;}}return in;
}void TestDate6()
{Date d1(2025, 3, 0);Date d2;cin >> d1 >> d2;//連續輸入輸出cout << d1 << d2;
}
2友元
(1)友元提供了?種突破類訪問限定符封裝的?式,友元分為:友元函數和友元類,在函數聲明或者類 聲明的前?加friend,并且把友元聲明放到?個類的??。
(2)外部友元函數可訪問類的私有和保護成員,友元函數僅僅是?種聲明,他不是類的成員函數。
(3)友元函數可以在類定義的任何地?聲明,不受類訪問限定符限制。
(4)?個函數可以是多個類的友元函數。
void func(const A& aa, const B& bb)
{cout << aa._a1 << endl;cout << bb._b1 << endl;
}
//可以把這個函數既放到類域A中又放到類域B中
(5)友元類中的成員函數都可以是另?個類的友元函數,都可以訪問另?個類中的私有和保護成員。
// 前置聲明,否則A的友元函數聲明編譯器不認識B
class B;
class A
{// 友元聲明 friend void func(const A& aa, const B& bb);
private:int _a1 = 1;int _a2 = 2;
};
class A
{// 友元聲明 friend class B;
private:int _a1 = 1;int _a2 = 2;
};
class B
{
public:void func1(const A& aa){cout << aa._a1 << endl;cout << _b1 << endl;}void func2(const A& aa){cout << aa._a2 << endl;cout << _b2 << endl;}
private:int _b1 = 3;int _b2 = 4;
};
(6)友元類的關系是單向的,不具有交換性,?如A類是B類的友元,但是B類不是A類的友元。
(7)友元類關系不能傳遞,如果A是B的友元,B是C的友元,但是A不是C的友元。
(8)有時提供了便利。但是友元會增加耦合度,破壞了封裝,所以友元不宜多?。
七,內部類
(1)如果?個類定義在另?個類的內部,這個內部類就叫做內部類。內部類是?個獨?的類,跟定義在 全局相?,他只是受外部類類域限制和訪問限定符限制,所以外部類定義的對象中不包含內部類。
(2)內部類默認是外部類的友元類。
(3)內部類本質也是?種封裝,當A類跟B類緊密關聯,A類實現出來主要就是給B類使?,那么可以考 慮把A類設計為B的內部類,如果放到private/protected位置,那么A類就是B類的專屬內部類,其 他地?都?不了。
class A
{
private:static int _k;int _h = 1;
public:class B // B默認就是A的友元 {public:void foo(const A& a){cout << _k << endl; //OKcout << a._h << endl; //OK}int _b1;};
};int A::_k = 1;int main()
{cout << sizeof(A) << endl;//B:: _b1; 是訪問不到的,受到A類域的限制A::B b;A aa;b.foo(aa);return 0;
}
題目1:
求1+2+3+...+n_牛客題霸_牛客網
class Solution
{// 內部類 class Sum{public:Sum(){_ret += _i;++_i;}};static int _i;static int _ret;public:int Sum_Solution(int n){// 變?數組 Sum arr[n];return _ret;}
};
int Solution::_i = 1;
int Solution::_ret = 0;
圖解:
八,匿名對象
臨時對象:(1)類型轉換時會產生? ?(2)傳值返回會產生
1,?類型(實參)定義出來的對象叫做匿名對象,相?之前我們定義的類型對象名(實參)定義出來的 叫有名對象。
2,匿名對象?命周期只在當前??,?般臨時定義?個對象當前??下即可,就可以定義匿名對象。
int main()
{// 有名對象A aa1(1);// 轉換產生的臨時對象 const引用可以延長其生命周期const A& aa2 = 1;// 但是他的?命周期只有這??,我們可以看到下??他就會?動調?析構函數 // 匿名對象A(2);A aa1;// 不能這么定義對象,因為編譯器?法識別下?是?個函數聲明,還是對象定義 //A aa1();// 但是我們可以這么定義匿名對象,匿名對象的特點不?取名字, return 0;
}
class Solution
{public:int Sum_Solution(int n){//...return n;}
};
int main()
{//有名調用Solution s;s.Sum_Solution(10);//匿名調用Solution().Sum_Solution(10);
}
九,對象拷貝時的編譯器優化
(1)現代編譯器會為了盡可能提?程序的效率,在不影響正確性的情況下會盡可能減少?些傳參和傳返 回值的過程中可以省略的拷?。
(2)如何優化C++標準并沒有嚴格規定,各個編譯器會根據情況??處理。當前主流的相對新?點的編 譯器對于連續?個表達式步驟中的連續拷?會進?合并優化,有些更新更"激進"的編譯器還會進? 跨?跨表達式的合并優化。
(3)linux下可以將下?代碼拷?到test.cpp?件,編譯時? g++ test.cpp -fno-elide-constructors 的?式關閉構造相關的優化。
int main()
{// 構造臨時對象,臨時對象再拷貝構造aa1,優化為直接構造//A aa1 = 1;// 傳值傳參// 無優化A aa1;//f1(aa1); 已經創建出對象,再去拷貝構造是沒有優化的//cout << endl;//下面兩者情況是有優化的隱式類型,連續構造+拷貝構造->優化為直接構造f1(1);一個表達式中,連續構造+拷貝構造->優化為一個構造f1(A(2));//cout << endl;
}
圖解觀察:
傳值返回:先構造,再創建臨時對象,返回臨時對象,再調用拷貝構造。
A f2()
{A aa;return aa;
}int main()
{// 傳值返回// 返回時一個表達式中,連續拷貝構造+拷貝構造->優化一個拷貝構造 (vs2019 debug)// 一些編譯器會優化得更厲害,進行跨行合并優化,直接變為構造。(vs2022 debug)f2();cout << endl;// 返回時一個表達式中,連續拷貝構造+拷貝構造->優化一個拷貝構造 (vs2019 debug)// 一些編譯器會優化得更厲害,進行跨行合并優化,直接變為構造。(vs2022 debug)A aa2 = f2();cout << endl;// 接收返回值時A ret=f2();cout<<endl;
}
未接收返回值時圖解:左側關閉優化,右側開啟
接收返回值時圖解:構造+拷貝構造+析構,返回值給ret時又要拷貝構造+析構。
拷貝構造+賦值重載:
未優化:構造+拷貝構造+析構+賦值重載+析構
int main()
{// 一個表達式中,連續拷貝構造+賦值重載->無法優化aa1 = f2();cout << endl;return 0;
}
圖解:
總結: