網站首頁 編程語言 正文
C++類成員函數中的名字查找
在C++類的成員函數中,名字查找是按照由內到外進行的。首先查找成員函數中的名字,之后再查找類中定義的名字,最后查找類外定義的名字。
查找順序
自定義類MyClass代碼如下
int i = 1;
class MyClass {
? public:
? ? int i = 2;
? ? void myFunc() {
? ? ? int i = 3;
? ? ? int j = i;
? ?}
};
其中,在MyClass類的成員函數myFunc()中使用了變量i,而在myFunc()函數、MyClass類以及類外都定義了變量i,此時myFunc()成員函數會根據由內向外的順序對i進行查找,即此時j的值應該是3。
使用指定的名字
使用類的變量
在myFunc()中如果需要使用類myFunc()函數的變量i,可以進行如下定義
int j = MyClass::i;
或者
int j = this->i;
此時,表示使用MyClass中定義的變量i,j的值是2。
使用類外變量
在myFunc()中如果需要使用類外定義的變量i,可以進行如下定義
int j = ::i;
此時,j的值是1。
C++名字查找與類的作用域
每個類都會定義它自己的作用域。在類的作用域之外,普通的數據和函數成員只能由對象、引用或者指針使用成員訪問運算符來訪問。對于類類型成員則使用作用域運算符訪問。不論哪種情況,跟在運算符之后的名字都必須是對應類的成員。
Screen::pos ht = 24, wd = 80; ?// 使用 Screen 定義的 pos 類型
Screen scr(ht, wd, ' ');
Screen *p = &scr;
char c = scr.get(); ?// 訪問 scr 對象的 get 成員
c = p->get(); ?// 訪問 p 所指對象的 get 成員
作用域和定義在類外部的成員
類的作用域 (class scope) 每個類定義一個作用域。類作用域比其他作用域更加復雜,類中定義的成員函數甚至有可能使用定義語句之后的名字。
一個類就是一個作用域,我們在類的外部定義成員函數時必須同時提供類名和函數名。在類的外部,成員的名字被隱藏起來了。一旦遇到了類名,定義的剩余部分就在類的作用域之內了,這里的剩余部分包括參數列表和函數體。結果就是,我們可以直接使用類的其他成員而無須再次授權了。
void Window::clear(ScreenIndex i)
{
?? ?Screen &s = screens[i];
?? ?s.contents = string(s.height * s.width, ' ');
}
編譯器在處理參數列表之前已經明確了我們當前正位于 Window 類的作用域中,所以不必再專門說明 ScreenIndex 是 Window 類定義的。出于同樣的原因,編譯器也能知道函數體中用到的 screens 也是在 Window 類中定義的。
函數的返回類型通常出現在函數名之前。因此當成員函數定義在類的外部時,返回類型中使用的名字都位于類的作用域之外。這時,返回類型必須指明它是哪個類的成員。我們可能向 Window 類添加一個新的名為 add_screen 的函數,它負責向顯示器添加一個新的屏幕。這個成員的返回類型將是 ScreenIndex,用戶可以通過它定位到指定的 Screen。
class Window {
public:
?? ?// 向窗口添加一個 Screen,返回它的編號
?? ?ScreenIndex add_screen(const Screen &);
?? ?......
};
// 首先處理返回類型,之后我們才進入 Window 的作用域
Window::ScreenIndex
Window::add_screen(const Screen &s)
{
?? ?screens.push_back(s);
?? ?return screens.size() - 1;
}
因為返回類型出現在類名之前,所以事實上它是位于 Window 類的作用域之外的。在這種情況下,要想使用 Screenlndex 作為返回類型,我們必須明確指定哪個類定義了它。
名字查找與類的作用域
名字查找 (name lookup) (尋找與所用名字最匹配的聲明的過程) 的過程比較直截了當。
- 首先,在名字所在的塊中尋找其聲明語句,只考慮在名字的使用之前出現的聲明。
- 如果沒找到,繼續查找外層作用域。
- 如果最終沒有找到匹配的聲明,則程序報錯。
對于定義在類內部的成員函數來說,解析其中名字的方式與上述的查找規則有所區別,不過在當前的這個例子中體現得不太明顯。
類的定義分兩步處理:
- 首先,編譯成員的聲明。
- 直到類全部可見后才編譯函數體。
編譯器處理完類中的全部聲明后才會處理成員函數的定義。按照這種兩階段的方式處理類可以簡化類代碼的組織方式。因為成員函數體直到整個類可見后才會被處理,所以它能使用類中定義的任何名字。
名字查找 (name lookup) 是根據名字的使用尋找匹配的聲明的過程。
用于類成員聲明的名字查找
這種兩階段的處理方式只適用于成員函數中使用的名字。聲明中使用的名字,包括返回類型或者參數列表中使用的名字,都必須在使用前確保可見。如果某個成員的聲明使用了類中尚未出現的名字,則編譯器將會在定義該類的作用域中繼續査找。
typedef double Money;
std::string bal;
class Account {
public:
?? ?Money balance() { return bal; }
private:
?? ?Money bal;
};
當編譯器看到 balance 函數的聲明語句時,它將在 Account 類的范圍內尋找對 Money 的聲明。編譯器只考慮 Account 中在使用 Money 前出現的聲明,因為沒找到匹配的成員,所以編譯器會接著到 Account 的外層作用域中查找。在這個例子中,編譯器會找到 Money 的 typedef 語句,該類型被用作 balance 函數的返回類型以及數據成員 bal 的類型。balance 函數體在整個類可見后才被處理,該函數的 return 語句返回名為 bal 的成員,而非外層作用域的 std::string 對象。
類型名要特殊處理
內層作用域可以重新定義外層作用域中的名字,即使該名字已經在內層作用域中使用過。然而在類中,如果成員使用了外層作用域中的某個名字,而該名字代表一種類型,則類不能在之后重新定義該名字。
typedef double Money;
class Account {
public:
?? ?Money balance() { return bal; } ?// 使用外層作用域的 Money
private:
?? ?typedef double Money; ?// 錯誤:不能重新定義 Money
?? ?Money bal;
};
需要特別注意的是,即使 Account 中定義的 Money 類型與外層作用域一致,上述代碼仍然是錯誤的。盡管重新定義類型名字是一種錯誤的行為,但是編譯器并不為此負責。一些編譯器仍將順利通過這樣的代碼,而忽略代碼有錯的事實。類型名的定義通常出現在類的開始處,這樣就能確保所有使用該類型的成員都出現在類名的定義之后。
成員定義中的普通塊作用域的名字查找
成員函數中使用的名字按照如下方式解析:
- 首先,在成員函數內查找該名字的聲明。和前面一樣,只有在函數使用之前岀現的 聲明才被考慮。
- 如果在成員函數內沒有找到,則在類內繼續査找,這時類的所有成員都可以被考慮。
- 如果類內也沒找到該名字的聲明,在成員函數定義之前的作用域內繼續查找。
一般來說,不建議使用其他成員的名字作為某個成員函數的參數。為了更好地解釋名字的解析過程,我們不妨在 dummy_fcn 函數中暫時違反一下這個約定。
// 這段代碼僅為了說明而用,不是一段很好的代碼
// 通常情況下不建議為參數和成員使用同樣的名字
int height; ?// 定義了一個名字,稍后將在 Screen 中使用
class Screen {
public:
?? ?typedef std::string::size_type pos;
?? ?void dummy_fcn(pos height) {
?? ??? ?cursor = width * height; // height 是那個參數
?? ?}
private:
?? ?pos cursor = 0;
?? ?pos height = 0, width = 0;
};
當編譯器處理 dummy_fcn 中的乘法表達式時,它首先在函數作用域內查找表達式中用到的名字。函數的參數位于函數作用域內,因此 dummy_fcn 函數體內用到的名字 height 指的是參數聲明。在此例中,height 參數隱藏了同名的成員。如果想繞開上面的查找規則,應該將代碼變為:
// 不建議的寫法:成員函數中的名字不應該隱藏同名的成員
void Screen::dummy_fcn(pos height) {
?? ?cursor = width * this->height; ?// 成員 height
?? ?// 另外一種表示該成員的方式
?? ?cursor = width * Screen::height; ?// 成員 height
}
盡管類的成員被隱藏了,但我們仍然可以通過加上類的名字或顯式地使用 this 指針來強制訪問成員,其實最好的確保我們使用 height 成員的方法是給參數起個其他名字。
// 建議的寫法:不要把成員名字作為參數或其他局部變量使用
void Screen::dummy_fcn(pos ht) {
?? ?cursor = width * height; ?// 成員 height
}
在此例中,當編譯器查找名字 height 時,顯然在 dummy_fcn 函數內部是找不到的。編譯器接著會在 Screen 內查找匹配的聲明,即使 height 的聲明出現在 dummy_fcn 使用它之后,編譯器也能正確地解析函數使用的是名為 height 的成員。
類作用域之后,在外圍的作用域中查找
如果編譯器在函數和類的作用域中都沒有找到名字,它將接著在外圍的作用域中查找。在我們的例子中,名字 height 定義在外層作用域中,且位于 Screen 的定義之前。 然而,外層作用域中的對象被名為 height 的成員隱藏掉了。如果我們需要的是外層作用域中的名字,可以顯式地通過作用域運算符來進行請求。
// 不建議的寫法:不要隱藏外層作用域中可能被用到的名字
void Screen::dummy_fcn(pos height) {
?? ?cursor = width * ::height; ?// height 是哪個全局的
}
盡管外層的對象被隱藏掉了,但我們仍然可以用作用域運算符訪問它。
在文件中名字的出現處對其進行解析
當成員定義在類的外部時,名字査找的第三步不僅要考慮類定義之前的全局作用域中的聲明,還需要考慮在成員函數定義之前的全局作用域中的聲明。
int height; ?// 定義了一個名字,稍后將在 Screen 中使用
class Screen {
public:
?? ?typedef std::string::size_type pos;
?? ?void setHeight(pos);
?? ?pos height = 0; ?// 隱藏了外層作用域中的 height
};
Screen::pos verify(Screen::pos);
void Screen::setHeight(pos var) {
?? ?// var: 參數
?? ?// height: 類的成員
?? ?// verify: 全局函數
?? ?height = verify(var);
}
全局函數 verify 的聲明在 Screen 類的定義之前是不可見的。名字査找的第三步包括了成員函數出現之前的全局作用域。在此例中,verify 的聲明位于 setHeight 的定義之前,因此可以被正常使用。
原文鏈接:https://blog.csdn.net/hou09tian/article/details/109256842
相關推薦
- 2022-04-23 git如何提交本地倉庫并同步碼云倉庫
- 2022-10-22 ListView下拉列表控件使用方法詳解_Android
- 2022-08-13 從源碼理解SpringBootServletInitializer的作用
- 2023-03-01 Python開根號的幾種方式詳解_python
- 2022-09-13 go語言中基本數據類型及應用快速了解_Golang
- 2023-07-22 垃圾回收的核心知識點解析
- 2022-10-16 C語言中關于計算字符串長度的幾種方式_C 語言
- 2022-10-29 subplots_adjust()函數--matplotlib
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支