C++ 類的知識 | 構造函數再探、匿名對象、友元函數、內部類、類的const成員、類的static成員

文章目錄

  • 構造函數再探
    • 以下代碼共調用多少次拷貝構造函數
    • 委托構造函數
      • 概念
      • 形式
  • 匿名對象
  • 友元
    • 友元的聲明
    • 友元類
    • 令成員函數作為友元
    • 函數重載和友元
    • 注意
  • 內部類
    • 特性
  • 類的const成員
    • 可變數據成員
  • 類的static成員
    • 概念
    • 關于static
    • 靜態成員的類內初始化
    • 靜態成員能用于某些普通成員不能的場景


構造函數再探

以下代碼共調用多少次拷貝構造函數

Widget f(Widget u)
{  Widget v(u);Widget w=v;return w;
}int main(){Widget x;Widget y=f(f(x));}

這道題我第一眼看,在調用f的時候,會用實參來構造對象u,在用對象u構建v,然后用v構建w,最后因為是傳值返回,會用w再構造一個臨時量,所以調用一次f會使用4次拷貝構造函數,最后再用這兩次f的返回值來構造y,應該是9次。

但是答案卻是7次,讓我十分不解,所以我去論壇搜索了一下,發現這里涉及到了編譯器的優化。

我們可以看到,在第一次調用f的結束的時候,會返回一個由w構造的臨時量,再將這個臨時量作為實參來初始化第二個f的形參u,(第二個f調用到return這一步時,返回一個由w構造的臨時量,再將這個臨時量作為實參來初始化拷貝構造函數的形參)編譯器覺得這一步有點多余,會將其優化為一步(第一次直接用w構造u,第二次直接用w構造y),所以每次調用f的時候其實只經過了3次的拷貝構造,最后再加上y的拷貝構造,一共是7次。


委托構造函數

概念

使用它所屬類的其他構造函數執行它自己的初始化過程,換言之,它把它自己的一些(或者全部)指責委托給了其他構造函數。

形式

有一個成員初始值的列表和一個函數體。成員初始值列表只有一個唯一的入口,即類名本身。

class Date
{
public:// 非委托構造函數Date(int year, int month, int day):_year(year), _month(month), _day(day) {	}// 其余構造函數全部委托給上面的構造函數Date():Date(0,1,1){}Date(int i):Date(i,0,0){}Date(std::istream &is):Date(){is >> this->_year >> this->_month >> this->_day;}void pr(){cout << this->_year << ends << this->_month << ends << this->_day << endl;}
private:int _year;int _month;int _day;
};

本例中,共有四個構造函數。

第一個構造函數接受三個實參,使用這些實參初始化數據成員,然后結束工作。

第二個默認構造函數使用三參數的構造函數完成初始化過程,因為函數體為空可知無需再執行其他任務。

第三個構造函數接受一個實參,同樣委托給了三參數的構造函數。

第四個構造函數先委托給了默認構造函數,默認構造函數又接著委托給了三參數構造函數。當這些受委托的構造函數執行完后,接著執行std::istream &is構造函數體的內容。

ps:當一個構造函數委托給另一個構造函數時,受委托的構造函數的初始值列表和函數體依次被執行,然后控制權才會交還給委托者的函數體。


匿名對象

當我們想要調用對象中的一個方法,或者只是想在這一句使用這個對象,其他地方不再使用該對象的時候。如果我們直接構造一個對象使用,這無疑是一種很大的浪費,因為這個對象我們用了一次就扔了,不再需要了,而一個對象的生命周期是整個棧幀。

這時就需要用到匿名對象,匿名對象是一種臨時對象,它的生命周期只有使用它的那一行語句,執行完則立即銷毀

class Date
{int _year;int _month;int _day;
public:Date(int year = 2020, int month = 4, int day = 24){_year = year;_month = month;_day = day;cout << "gouzao" << this << endl;}void print(){cout << this->_year << endl;}~Date(){cout << "xigou" << this << endl;}
};int main()
{Date d1; //創建一個對象,生命周期為整個函數棧幀d1.print();Date().print();//創建一個匿名對象,生命周期只有這一行語句,實行完則立即調用析構函數Date d2; //創建一個對象,生命周期為整個函數棧幀d2.print();return 0;
}

運行結果:
在這里插入圖片描述


友元

類中的private部分非類的成員是無法訪問的。但類可以允許其他類或者函數訪問它的非公有成員,方法是令其他類或者函數成為它的友元(friend)。

  1. 友元函數可訪問類的私有和保護成員,但不是類的成員函數
  2. 友元函數不能用const修飾
  3. 一個函數可以是多個類的友元函數
  4. 友元函數的調用與普通函數的調用和原理相同

友元的聲明

友元聲明只能出現在類定義的內部,但是在類內出現的具體位置不限。友元不是類的成員也不受它所在區域訪問控制級別的約束。一般來說,最好在類定義開始或結束前的位置集中聲明友元。

友元的聲明僅僅指定了訪問的權限,而非一個通常意義上的函數聲明。如果我們希望類的用戶能夠調用某個友元函數,那么我們就必須在友元聲明之外再專門對函數進行一次聲明。

在這里插入圖片描述
ps:上述代碼想講的還是要知道友元聲明的作用是規定訪問權限,不能起到普通意義聲明的作用。

友元類

class A
{friend class Date;//將Date聲明為友元類,Date可以訪問A的所有成員變量
private:string str;
};
class Date
{
public:void Print(){cout << a.str << endl;//訪問a的私有成員}
private:int _year;int _month;int _day;A a;
};

令成員函數作為友元

如果不想讓整個類作為自己的友元,也可以只為那個需要訪問自己的private對象的函數提供訪問權限。當把一個成員函數聲明成友元時,必須明確指出該成員函數屬于哪個類:

class A;
class Date
{
public:void Print(A);private:int _year;int _month;int _day;
};class A
{friend void Date::Print(A);//將Date的成員函數Print聲明為友元,Print可以訪問A的所有成員變量
private:string str;
};void Date::Print(A a){cout << a.str << endl;//訪問a的私有成員
}

要想令某個成員函數作為友元,我們必須仔細組織程序的結構以滿足聲明和定義的彼此依賴關系。該例中,我們必須按照如下方式設計程序:

  • 首先前向聲明A類(因為Print的聲明會用到)
  • 定義Date類,聲明Print函數,但不能定義它(因為要訪問A的私有成員,A還沒有被定義好,無法訪問其私有成員)
  • 定義A類,包括對Print的友元聲明
  • 定義Print,此時它才可以使用A的成員

函數重載和友元

盡管重載函數的名字相同,但它們仍然是不同的函數。因此,如果一個類想把一組重載函數聲明成它的友元,需要對這組函數中的每一個分別聲明:
在這里插入圖片描述
在A中并沒有關于Print(A, A)的友元聲明,因此其不能訪問A的私有成員。

注意

1.友元關系是單向的,不具有交換性。
Date為A的友元,可以訪問A的私有成員,但是A并不能訪問Date的

2.友元關系不能傳遞。
如果B是A的友元,C是B的友元,則不能說明C時A的友元。


內部類

有沒有想過,既然類的成員變量可以是自定義類型,那能不能在類中再構建一個類呢?

class Date
{
public:void Print(){cout << _year << endl;}private:class A{public:void Print(){cout << _data << endl;}private:int _data;};int _year;int _month;int _day;};

可以看到,這是可以的,但是這兩個類有什么關系嗎?

這個內部類其實是一個獨立的類,它不屬于外部類,同時外部類對它也沒有任何特權,但是它同時還是外部類的友元類,可以通過外部類的對象參數來訪問外部類的所有成員。

特性

  1. 內部類可以定義在外部類的public、protected、private都是可以的。
  2. 注意內部類可以直接訪問外部類中的static、枚舉成員,不需要外部類的對象/類名。
  3. sizeof(外部類)=外部類,和內部類沒有任何關系

類的const成員

因為對于類和對象,封裝性是一個很重要的東西,但是訪問限定符只對外部有影響,對自身的成員函數沒有影響,如果我們不想讓一個成員函數對類的成員進行修改,這時,就需要用const來修飾成員函數。

class Date
{
public://等價于 void print(const Date* this)void Print() const{cout << _year << '-' << _month << '-' << _day << endl;}
private:int _year;int _month;int _day;};

對于用const修飾的成員函數,需要將const放在最后面,來區分開const參數和const返回值。這里的const其實修飾的是該成員函數的this指針,所以該成員函數就無法對類的成員進行修改。

  1. const對象可以調用非const成員函數嗎?
    答案:不行,因為const對象的只能讀不能寫,而非const的成員函數則可讀可寫,使權限放大了,不行。

  2. 非const對象可以調用const成員函數嗎?
    答案:可以,因為非const對象的可讀可寫,const成員函數只可讀,使權限縮小,可行。

在這里插入圖片描述

  1. const成員函數內可以調用其它的非const成員函數嗎?
    答案:不行,因為const成員函數的this指針是常量,只可讀,而非const的成員函數則可讀可寫,使權限放大了,不行。

  2. 非const成員函數內可以調用其它的const成員函數嗎?
    答案:可以,因為非const成員函數的this指針可讀可寫,const成員函數只可讀,使權限縮小,可行。

在這里插入圖片描述

可變數據成員

有時會發生這樣一種情況,我們希望能修改類的某個數據成員,即使是在一個const成員函數內。可以通過在變量的聲明中加入mutable關鍵字實現這一目的。

一個可變數據成員永遠不會是const,即使它是const對象的成員。因此,一個const成員函數可以改變成員的值。

舉個例子,我們需要一個可變成員num來追蹤每個成員函數被調用了多少次:

class Date
{
public:void Print() const{num++;cout << num << endl;}private:mutable size_t num = 0;
};int main(int argc, char const *argv[]) {Date d;d.Print();d.Print();d.Print();return 0;
}

運行結果:
在這里插入圖片描述
盡管Print是一個const成員函數,它仍然能夠改變num的值。因為num是個可變成員,因此任何成員函數,包括const函數在內都能改變它的值。


類的static成員

概念

有的時候類需要它的一些成員與類本身直接相關,而不是與類的各個對象保持關聯。這樣的成員叫做類的靜態成員。


關于static

我們通過在成員的聲明前加上關鍵字static使得其與類關聯在一起。對于用static修飾的成員函數,稱為靜態成員函數,成員變量稱為靜態成員變量。因為靜態的成員的生命域不在類中,在靜態區,所以靜態的成員只能在類外初始化。

  1. 靜態成員為所有類對象所共享,不屬于某個具體的實例
  2. 靜態成員變量必須在類外定義,定義時不添加static關鍵字,一個靜態數據成員只能定義一次
  3. 靜態成員函數可以在類內部也可以在類外部定義,在類外部定義時,不能添加siatic關鍵字(外部有static關鍵字表示這個函數在文件內有效)
  4. 類靜態成員亦可用 類名::靜態成員 或者 對象.靜態成員 或者 指向對象的指針->靜態成員 來訪問
  5. 靜態成員函數不與任何對象綁定在一起,它們不包含this指針(static成員函數也就不能被聲明成const,上章提到const是為了將this指針賦予底層const),也不能在static函數體內使用this指針,不包含this指針也就不能訪問任何非靜態成員
  6. 靜態成員和類的普通成員一樣,也有public、protected、private3種訪問類別,也可以具有返回值
  7. 靜態成員和全局變量雖然都存儲在靜態區,但是靜態成員的生命周期只在本文件中,而全局變量不是

因為靜態成員函數不屬于某個對象,所以它沒有this指針,無法訪問任何非靜態的成員,但是非靜態的成員函數具有this指針,可以不用通過作用域運算符直接使用靜態成員。

關于第二點和第三點的代碼示例:

class Date
{int db = 666;static int num;static double init();
public:static int print();void test(){db += db * num;// 成員函數不用通過作用域運算符就能直接使用靜態成員}
};
int Date::num = init(); // 定義并初始化一個靜態成員int main(int argc, char const *argv[]) {int r;r = Date::print(); // 使用作用域運算符訪問靜態成員Date d1;Date *d2 = &d1;r = d1.print(); // 通過Date的對象或引用r = d2->print(); // 通過指向Date對象的指針return 0;
}

關于第二點:
在這里插入圖片描述
從類名開始,這條定義語句的剩余部分就都位于類的作用域之內了。因此,我們可以直接使用init函數,雖然其是私有的。


靜態成員的類內初始化

通常情況下,類的靜態成員不應該在類的內部初始化。然而,我們可以為靜態成員提供const整數類型的類內初始值,不過要求靜態成員必須是字面值常量類型的constexpr,初始值必須是常量表達式。

class Date
{constexpr static int num = 2*3; // num是常量表達式double db[num];
public:
};

如果某個靜態成員的應用場景僅限于編譯器可以替換他的值的情況,則一個初始化的const或者static constexpr不需要分別定義。相反,如果我們將它用于值不能替換的場景中,該成員必須有一條定義語句。

如果在類的內部提供了一個初始值,則在類外部成員的定義不能再指定一個初始值了:
在這里插入圖片描述
即使一個常量靜態數據成員在類內部被初始化了,通常情況下也應該在類的外部定義一下該成員。


靜態成員能用于某些普通成員不能的場景

  1. 靜態數據成員可以是不完全類型(聲明之后定義之前)
  2. 靜態數據成員的類型可以就是他所屬的類類型。而非靜態數據成員只能聲明成他所屬類的指針或引用
    在這里插入圖片描述
  3. 可以使用靜態成員作為默認實參
    在這里插入圖片描述

非靜態數據成員不可以,因為它的值本身屬于對象的一部分這么做的結果是無法真正提供一個對象以便從中獲取成員的值,最終將引發錯誤。
在這里插入圖片描述
(圖中error:非靜態數據成員“Date::test”的使用無效)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/443822.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/443822.shtml
英文地址,請注明出處:http://en.pswp.cn/news/443822.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

截取全部數值字符并將其轉化為數值類型

功能 從name中找出全部數值字符&#xff0c;之后將name&#xff08;string類&#xff09;轉為d&#xff08;double類&#xff09; 代碼 #include <iostream> #include <list> #include <deque> #include <vector> #include <forward_list> #i…

替換string中的部分字符

功能 向函數fun中傳入三個參數&#xff1a;將s中所有oldval替換為newval 代碼 #include <iostream> #include <list> #include <deque> #include <vector> #include <forward_list> #include <array> using namespace std;void fun(str…

順序容器(vector、list、string、deque、forward_list)及迭代器、容器適配器

文章目錄概述所有容器都支持的操作迭代器迭代器支持的操作迭代器支持的算術運算容器類型size_typeiterator 和 const_iterator容器定義和初始化拷貝初始化順序容器獨有的構造函數&#xff08;array除外&#xff09;array的初始化與內置數組類型的區別6種初始化方法&#xff08;…

jQuery實現表格隔行換顏色:

jQuery實現表格各行換顏色&#xff1a; 截圖如下&#xff1a; 代碼如下&#xff1a; <span style"font-family:Microsoft YaHei;font-size:14px;"><% page language"java" import"java.util.*" pageEncoding"UTF-8"%> &…

用stack處理中綴表達式【+、-、*、/、()】

文章目錄題目描述思路使用getline()存儲輸入的字符串邊讀取邊壓棧完整代碼題目描述 使用stack處理括號化的表達式。當你看到一個左括號&#xff0c;將其記錄下來。當你在一個左括號之后看到一個右括號&#xff0c;從stack中pop對象&#xff0c;直至遇到左括號&#xff0c;將左括…

原地置換法尋找數組中重復的數

文章目錄題目描述代碼實現題目描述 在一個長度為 n 的數組 nums 里的所有數字都在 0&#xff5e;n-1 的范圍內。數組中某些數字是重復的&#xff0c;但不知道有幾個數字重復了&#xff0c;也不知道每個數字重復了幾次。請找出數組中任意一個重復的數字。 輸入&#xff1a; [2,…

二維數組的查找

文章目錄題目描述思路注意代碼題目描述 在一個 n * m 的二維數組中&#xff0c;每一行都按照從左到右遞增的順序排序&#xff0c;每一列都按照從上到下遞增的順序排序。請完成一個高效的函數&#xff0c;輸入這樣的一個二維數組和一個整數&#xff0c;判斷數組中是否含有該整數…

雙指針

文章目錄題目描述思路注意代碼實現題目描述 請實現一個函數&#xff0c;把字符串 s 中的每個空格替換成"%20"。 示例 1&#xff1a; 輸入&#xff1a;s “We are happy.” 輸出&#xff1a;“We%20are%20happy.” 限制&#xff1a; 0 < s 的長度 < 10000 思…

Springmvc,Spring MVC文件上傳

Springmvc文件上傳&#xff1a; 1.代碼截圖如下&#xff1a; 2.UploadController.java: package cn.csdn.controller;import java.io.File;import javax.servlet.http.HttpServletRequest;import org.springframework.stereotype.Controller; import org.springframework.ui.…

倒序輸出鏈表

文章目錄題目描述思路遞歸法棧題目描述 輸入一個鏈表的頭節點&#xff0c;從尾到頭反過來返回每個節點的值&#xff08;用數組返回&#xff09;。 示例 1&#xff1a; 輸入&#xff1a;head [1,3,2] 輸出&#xff1a;[2,3,1] 限制&#xff1a; 0 < 鏈表長度 < 10000 思…

插入迭代器、流迭代器、反向迭代器、移動迭代器

文章目錄前言插入迭代器inserterfront_inserterback_inserteriostream迭代器istream_iterator 讀取輸入流istream_iterator允許使用懶惰求值ostream_iterator操作反向迭代器reverse_iterator的base成員函數前言 除了為每個容器定義的迭代器之外&#xff0c;標準庫在頭文件iter…

泛型算法(lambda表達式、function類模板、bind函數適配器、迭代器類別、鏈表數據結構獨有的算法)

文章目錄概念find()函數迭代器令算法不依賴于容器但算法依賴于元素類型的操作算法永遠不會執行容器的操作只讀算法accumulate()函數從兩個序列中讀取元素&#xff08;equal函數為例&#xff09;迭代器作為參數形成兩個序列equal()寫容器元素的算法概念fill()fill_n()插入迭代器…

jsp,div 限制字數,超出部分用省略號代替

1.我是用struts2標簽做的&#xff1a;如下&#xff1a; <% page language"java" import"java.util.*" pageEncoding"UTF-8"%> <% taglib prefix"s" uri"/struts-tags"%> <%String path request.getContext…

C++之關聯容器

文章目錄概述及類型mapsetpair類型概念初始化默認初始化提供初始化器允許的操作可以創建一個pair類的函數可以作為容器的類型關聯容器迭代器概念map的迭代器set的迭代器是const的初始化map and setmultimap and multiset關聯容器的操作額外的類型別名關聯容器和算法刪除元素添加…

動態內存、智能指針(shared_ptr、unique_ptr、weak_ptr)、動態數組

文章目錄三種對象的分類三種內存的區別動態內存概念智能指針允許的操作智能指針的使用規范new概念內存耗盡/定位new初始化默認初始化直接初始化值初始化delete概念手動釋放動態對象空懸指針shared_ptr類格式獨有的操作make_shared函數shared_ptr的計數器通過new用普通指針初始化…

動態數組的簡單應用

文章目錄連接兩個字符串字面常量題目注意代碼輸出結果處理輸入的變長字符串題目注意代碼連接兩個字符串字面常量 題目 連接兩個字符串字面常量&#xff0c;將結果保存在一個動態分配的char數組中。重寫&#xff0c;連接兩個標準庫string對象。 注意 使用頭文件cstring的str…

二分查找算法實現

文章目錄思路代碼以二分區間作為while判定條件以給定值作為while判定條件主函數思路 while判定條件的選擇&#xff0c;注意最外層的return的內容就好。下面提供了兩個代碼版本。計算 mid 時需要防止溢出&#xff08;對應類型【如本例中的int】可能存不下&#xff09;&#xff…

Windows下Spring3.x計劃任務實現定時備份MySql數據庫

今天在空閑之余查了一下關于MySql數據庫備份的方案&#xff0c;最后結合自己的項目情況寫了一個關于Spring計劃任務的例子&#xff0c;目前我這個版本是在Windwos下測試成功&#xff0c;希望對大家有所幫助&#xff0c;不足之處還請大家多多包含&#xff0c;有什么建議盡管提出…

根據中序、前序遍歷重建二叉樹

文章目錄題目遞歸思路細節易錯代碼復雜度分析迭代思路細節易錯代碼復雜度分析題目 輸入某二叉樹的前序遍歷和中序遍歷的結果&#xff0c;請重建該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。 例如&#xff0c;給出 前序遍歷 preorder [3,9,20,15,7] 中…

深搜+剪枝

文章目錄題目思路注意代碼復雜度分析題目 給定一個 m x n 二維字符網格 board 和一個字符串單詞 word 。如果 word 存在于網格中&#xff0c;返回 true &#xff1b;否則&#xff0c;返回 false 。 單詞必須按照字母順序&#xff0c;通過相鄰的單元格內的字母構成&#xff0c…