const的思考


const的思考

1、什么是const?
   常類型是指使用類型修飾符const說明的類型,常類型的變量或對象的值是不能被更新的。(當然,我們可以偷梁換柱進行更新:)
  2、為什么引入const?
  const 推出的初始目的,正是為了取代預編譯指令,消除它的缺點,同時繼承它的優點。
  3、cons有什么主要的作用?
   (1)可以定義const常量,具有不可變性。
   例如:
   const int Max=100;
   int Array[Max];
   (2)便于進行類型檢查,使編譯器對處理內容有更多了解,消除了一些隱患。
  例如:
   void f(const int i) { .........}
   編譯器就會知道i是一個常量,不允許修改;
   (3)可以避免意義模糊的數字出現,同樣可以很方便地進行參數的調整和修改。
   同宏定義一樣,可以做到不變則已,一變都變!如(1)中,如果想修改Max的內容,只需要:const int Max=you want;即可!
   (4)可以保護被修飾的東西,防止意外的修改,增強程序的健壯性。
   還是上面的例子,如果在函數體內修改了i,編譯器就會報錯;
   例如:
   void f(const int i) { i=10;//error! }
   (5) 為函數重載提供了一個參考。
   class A
   {
   ......
   void f(int i) {......} file://一個函數
   void f(int i) const {......} file://上一個函數的重載
   ......
   };
   (6) 可以節省空間,避免不必要的內存分配。
   例如:
   #define PI 3.14159 file://常量宏
   const doulbe Pi=3.14159; file://此時并未將Pi放入ROM中
   ......
   double i=Pi; file://此時為Pi分配內存,以后不再分配!
   double I=PI; file://編譯期間進行宏替換,分配內存
   double j=Pi; file://沒有內存分配
   double J=PI; file://再進行宏替換,又一次分配內存!
   const定義常量從匯編的角度來看,只是給出了對應的內存地址,而不是象#define一樣給出的是立即數,所以,const定義的常量在程序運行過程中只有一份拷貝,而#define定義的常量在內存中有若干個拷貝。
   (7) 提高了效率。
   編譯器通常不為普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成為一個編譯期間的常量,沒有了存儲與讀內存的操作,使得它的效率也很高。
  3、如何使用const?
   (1)修飾一般常量
   一般常量是指簡單類型的常量。這種常量在定義時,修飾符const可以用在類型說明符前,也可以用在類型說明符后。
   例如:
   int const x=2;或const int x=2;
   (2)修飾常數組
   定義或說明一個常數組可采用如下格式:
   int const a[5]={1, 2, 3, 4, 5};
   const int a[5]={1, 2, 3, 4, 5};
   (3)修飾常對象
   常對象是指對象常量,定義格式如下:
   class A;
   const A a;
   A const a;
   定義常對象時,同樣要進行初始化,并且該對象不能再被更新,修飾符const可以放在類名后面,也可以放在類名前面。
   (4)修飾常指針
   const int *A; file://const修飾指向的對象,A可變,A指向的對象不可變
   int const *A; file://const修飾指向的對象,A可變,A指向的對象不可變
   int *const A; file://const修飾指針A, A不可變,A指向的對象可變
   const int *const A; file://指針A和A指向的對象都不可變
   (5)修飾常引用
   使用const修飾符也可以說明引用,被說明的引用為常引用,該引用所引用的對象不能被更新。其定義格式如下:
   const double & v;
  (6)修飾函數的常參數
   const修飾符也可以修飾函數的傳遞參數,格式如下:
   void Fun(const int Var);
   告訴編譯器Var在函數體中的無法改變,從而防止了使用者的一些無意的或錯誤的修改。
   (7)修飾函數的返回值:
   const修飾符也可以修飾函數的返回值,是返回值不可被改變,格式如下:
   const int Fun1();
   const MyClass Fun2();
   (8)修飾類的成員函數:
   const修飾符也可以修飾類的成員函數,格式如下:
   class ClassName
   {
   public:
   int Fun() const;
   .....
   };
   這樣,在調用函數Fun時就不能修改類里面的數據
   (9)在另一連接文件中引用const常量
   extern const int i; file://正確的引用
   extern const int j=10; file://錯誤!常量不可以被再次賦值
   另外,還要注意,常量必須初始化!
   例如:
   const int i=5;
  4、幾點值得討論的地方:
   (1)const究竟意味著什么?
   說了這么多,你認為const意味著什么?一種修飾符?接口抽象?一種新類型?
   也許都是,在Stroustup最初引入這個關鍵字時,只是為對象放入ROM做出了一種可能,對于const對象,C++既允許對其進行靜態初始化,也允許對他進行動態初始化。理想的const對象應該在其構造函數完成之前都是可寫的,在析夠函數執行開始后也都是可寫的,換句話說,const對象具有從構造函數完成到析夠函數執行之前的不變性,如果違反了這條規則,結果都是未定義的!雖然我們把const放入ROM中,但這并不能夠保證const的任何形式的墮落,我們后面會給出具體的辦法。無論const對象被放入ROM中,還是通過存儲保護機制加以保護,都只能保證,對于用戶而言這個對象沒有改變。換句話說,廢料收集器(我們以后會詳細討論,這就一筆帶過)或數據庫系統對一個const的修改怎沒有任何問題。
   (2)位元const V.S. 抽象const?
   對于關鍵字const的解釋有好幾種方式,最常見的就是位元const 和 抽象const。下面我們看一個例子:
   class A
   {
   public:
   ......
   A f(const A& a);
   ......
   };
   如果采用抽象const進行解釋,那就是f函數不會去改變所引用對象的抽象值,如果采用位元const進行解釋,那就成了f函數不會去改變所引用對象的任何位元。
   我們可以看到位元解釋正是c++對const問題的定義,const成員函數不被允許修改它所在對象的任何一個數據成員。
   為什么這樣呢?因為使用位元const有2個好處:
   最大的好處是可以很容易地檢測到違反位元const規定的事件:編譯器只用去尋找有沒有對數據成員的賦值就可以了。另外,如果我們采用了位元const,那么,對于一些比較簡單的const對象,我們就可以把它安全的放入ROM中,對于一些程序而言,這無疑是一個很重要的優化方式。(關于優化處理,我們到時候專門進行討論)
   當然,位元const也有缺點,要不然,抽象const也就沒有產生的必要了。
   首先,位元const的抽象性比抽象const的級別更低!實際上,大家都知道,一個庫接口的抽象性級別越低,使用這個庫就越困難。
   其次,使用位元const的庫接口會暴露庫的一些實現細節,而這往往會帶來一些負面效應。所以,在庫接口和程序實現細節上,我們都應該采用抽象const。
   有時,我們可能希望對const做出一些其它的解釋,那么,就要注意了,目前,大多數對const的解釋都是類型不安全的,這里我們就不舉例子了,你可以自己考慮一下,總之,我們盡量避免對const的重新解釋。
   (3)放在類內部的常量有什么限制?
   看看下面這個例子:
   class A
   {
   private:
   const int c3 = 7; // ???
   static int c4 = 7; // ???
   static const float c5 = 7; // ???
   ......
   };
   你認為上面的3句對嗎?呵呵,都不對!使用這種類內部的初始化語法的時候,常量必須是被一個常量表達式初始化的整型或枚舉類型,而且必須是static和const形式。這顯然是一個很嚴重的限制!
   那么,我們的標準委員會為什么做這樣的規定呢?一般來說,類在一個頭文件中被聲明,而頭文件被包含到許多互相調用的單元去。但是,為了避免復雜的編譯器規則,C++要求每一個對象只有一個單獨的定義。如果C++允許在類內部定義一個和對象一樣占據內存的實體的話,這種規則就被破壞了。
   (4)如何初始化類內部的常量?
   一種方法就是static 和 const 并用,在內部初始化,如上面的例子;
   另一個很常見的方法就是初始化列表:
   class A
   {
   public:
   A(int i=0):test(i) {}
   private:
   const int i;
   };
   還有一種方式就是在外部初始化,例如:
   class A
   {
   public:
   A() {}
   private:
   static const int i; file://注意必須是靜態的!
   };
   const int A::i=3;
   (5)常量與數組的組合有什么特殊嗎?
   我們給出下面的代碼:
   const int size[3]={10,20,50};
   int array[size[2]];
   有什么問題嗎?對了,編譯通不過!為什么呢?
   const可以用于集合,但編譯器不能把一個集合存放在它的符號表里,所以必須分配內存。在這種情況下,const意味著“不能改變的一塊存儲”。然而,其值在編譯時不能被使用,因為編譯器在編譯時不需要知道存儲的內容。自然,作為數組的大小就不行了:)
   你再看看下面的例子:
   class A
   {
   public:
   A(int i=0):test[2]({1,2}) {} file://你認為行嗎?
   private:
   const int test[2];
   };
   vc6下編譯通不過,為什么呢?
   關于這個問題,前些時間,njboy問我是怎么回事?我反問他:“你認為呢?”他想了想,給出了一下解釋,大家可以看看:我們知道編譯器堆初始化列表的操作是在構造函數之內,顯式調用可用代碼之前,初始化的次序依據數據聲明的次序。初始化時機應該沒有什么問題,那么就只有是編譯器對數組做了什么手腳!其實做什么手腳,我也不知道,我只好對他進行猜測:編譯器搜索到test發現是一個非靜態的數組,于是,為他分配內存空間,這里需要注意了,它應該是一下分配完,并非先分配test[0],然后利用初始化列表初始化,再分配test[1],這就導致數組的初始化實際上是賦值!然而,常量不允許賦值,所以無法通過。
   呵呵,看了這一段冠冕堂皇的話,真讓我笑死了!njboy別怪我揭你短呀:)我對此的解釋是這樣的:C++標準有一個規定,不允許無序對象在類內部初始化,數組顯然是一個無序的,所以這樣的初始化是錯誤的!對于他,只能在類的外部進行初始化,如果想讓它通過,只需要聲明為靜態的,然后初始化。
   這里我們看到,常量與數組的組合沒有什么特殊!一切都是數組惹的禍!
   (6)this指針是不是const類型的?
   this指針是一個很重要的概念,那該如何理解她呢?也許這個話題太大了,那我們縮小一些:this指針是個什么類型的?這要看具體情況:如果在非const成員函數中,this指針只是一個類類型的;如果在const成員函數中,this指針是一個const類類型的;如果在volatile成員函數中,this指針就是一個volatile類類型的。
   (7)const到底是不是一個重載的參考對象?
   先看一下下面的例子:
   class A
   {
   ......
   void f(int i) {......} file://一個函數
   void f(int i) const {......} file://上一個函數的重載
   ......
   };
   上面是重載是沒有問題的了,那么下面的呢?
   class A
   {
   ......
   void f(int i) {......} file://一個函數
   void f(const int i) {......} file://?????
   ......
   };
   這個是錯誤的,編譯通不過。那么是不是說明內部參數的const不予重載呢?再看下面的例子:
   class A
   {
   ......
   void f(int& ) {......} file://一個函數
   void f(const int& ) {......} file://?????
   ......
   };
   這個程序是正確的,看來上面的結論是錯誤的。為什么會這樣呢?這要涉及到接口的透明度問題。按值傳遞時,對用戶而言,這是透明的,用戶不知道函數對形參做了什么手腳,在這種情況下進行重載是沒有意義的,所以規定不能重載!當指針或引用被引入時,用戶就會對函數的操作有了一定的了解,不再是透明的了,這時重載是有意義的,所以規定可以重載。
   (8)什么情況下為const分配內存?
   以下是我想到的可能情況,當然,有的編譯器進行了優化,可能不分配內存。
   A、作為非靜態的類成員時;
   B、用于集合時;
   C、被取地址時;
   D、在main函數體內部通過函數來獲得值時;
   E、const的 class或struct有用戶定義的構造函數、析構函數或基類時;。
   F、當const的長度比計算機字長還長時;
   G、參數中的const;
   H、使用了extern時。
   不知道還有沒有其他情況,歡迎高手指點:)
   (9)臨時變量到底是不是常量?
   很多情況下,編譯器必須建立臨時對象。像其他任何對象一樣,它們需要存儲空間而且必須被構造和刪除。區別是我們從來看不到編譯器負責決定它們的去留以及它們存在的細節。對于C++標準草案而言:臨時對象自動地成為常量。因為我們通常接觸不到臨時對象,不能使用與之相關的信息,所以告訴臨時對象做一些改變有可能會出錯。當然,這與編譯器有關,例如:vc6、vc7都對此作了擴展,所以,用臨時對象做左值,編譯器并沒有報錯。
   (10)與static搭配會不會有問題?
   假設有一個類:
   class A
   {
   public:
   ......
   static void f() const { ......}
   ......
   };
   我們發現編譯器會報錯,因為在這種情況下static不能夠與const共存!
   為什么呢?因為static沒有this指針,但是const修飾this指針,所以...
   (11)如何修改常量?
   有時候我們卻不得不對類內的數據進行修改,但是我們的接口卻被聲明了const,那該怎么處理呢?我對這個問題的看法如下:
   1)標準用法:mutable
   class A
   {
   public:
   A(int i=0):test(i) { }
   void SetValue(int i)const { test=i; }
   private:
   mutable int test; file://這里處理!
   };
   2)強制轉換:const_cast
   class A
   {
   public:
   A(int i=0):test(i) { }
   void SetValue(int i)const
   { const_cast <int>(test)=i; }//這里處理!
   private:
   int test;
   };
   3)靈活的指針:int*
   class A
   {
   public:
   A(int i=0):test(i) { }
   void SetValue(int i)const
   { *test=i; }
   private:
   int* test; file://這里處理!
   };
   4)未定義的處理
   class A
   {
   public:
   A(int i=0):test(i) { }
   void SetValue(int i)const
   { int *p=(int*)&test; *p=i; }//這里處理!
   private:
   int test;
   };
   注意,這里雖然說可以這樣修改,但結果是未定義的,避免使用!
   5)內部處理:this指針
   class A
   {
   public:
   A(int i=0):test(i) { }
   void SetValue(int i)const
   { ((A*)this)->test=i; }//這里處理!
   private:
   int test;
   };
   6)最另類的處理:空間布局
   class A
   {
   public:
   A(int i=0):test(i),c('a') { }
   private:
   char c;
   const int test;
   };
   int main()
   {
   A a(3);
   A* pa=&a;
   char* p=(char*)pa;
   int* pi=(int*)(p+4);//利用邊緣調整
   *pi=5; file://此處改變了test的值!
   return 0;
   }
   雖然我給出了6中方法,但是我只是想說明如何更改,但出了第一種用法之外,另外5種用法,我們并不提倡,不要因為我這么寫了,你就這么用,否則,我真是要誤人子弟了:)
   (12)最后我們來討論一下常量對象的動態創建。
   既然編譯器可以動態初始化常量,就自然可以動態創建,例如:
   const int* pi=new const int(10);
   這里要注意2點:
   1)const對象必須被初始化!所以(10)是不能夠少的。
   2)new返回的指針必須是const類型的。
   那么我們可不可以動態創建一個數組呢?
   答案是否定的,因為new內置類型的數組,不能被初始化。
   這里我們忽視了數組是類類型的,同樣對于類內部數組初始化我們也做出了這樣的忽視,因為這涉及到數組的問題,我們以后再討論。(王朝網絡 wangchao.net.cn)

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

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

相關文章

深度模型壓縮論文(02)- BlockSwap: Fisher-guided Block Substitution for Network Compression

文章目錄1.摘要和背景1.1 摘要1.2 背景2.方法和貢獻2.1 方法2.2 貢獻3.實驗和結果3.1 實驗3.2 結果4.總結和展望4.1 總結4.2 展望本系列是在閱讀深度神經網絡模型小型化方面論文時的筆記&#xff01;內容大部分從論文中摘取&#xff0c;也會有部分自己理解&#xff0c;有錯誤的…

NLP復習資料(8)-知識圖譜、信息抽取

NLP復習資料第16講—知識圖譜第17講-信息抽取&#xff08;知識圖譜生命周期中信息獲取的關鍵技術&#xff09;國科大&#xff0c;宗老師《自然語言處理》課程復習筆記&#xff0c;個人整理&#xff0c;僅供參考。第16講—知識圖譜 知識圖譜經典知識表示理論語義網資源描述框架…

Collection源碼閱讀

package java.util;import java.util.function.Predicate; import java.util.stream.Stream; import java.util.stream.StreamSupport;/*** 集合層次結構的根接口&#xff0c;一個集合表示一組對象&#xff0c;稱為元素* JDK不提供任何該接口的直接實現&#xff0c;JDK提供實現…

socket阻塞和非阻塞的區別

讀操作 對于阻塞的socket,當socket的接收緩沖區中沒有數據時,read調用會一直阻塞住,直到有數據到來才返 回。當socket緩沖區中的數據量小于期望讀取的數據量時,返回實際讀取的字節數。當sockt的接收緩沖 區中的數據大于期望讀取的字節數時,讀取期望讀取的字節數,返回實際讀…

深度模型壓縮論文(01)- Meta Filter Pruning to Accelerate Deep Convolutional Neural Networks

文章目錄1.摘要和介紹1.1摘要部分2.背景和方法2.1 背景2.2 貢獻2.3 方法3.實驗和結果3.1 實驗3.2 結果4.總結和展望4.1 總結4.2 展望本系列是在閱讀深度神經網絡模型小型化方面論文時的筆記&#xff01;內容大部分從論文中摘取&#xff0c;也會有部分自己理解&#xff0c;有錯誤…

架構分享--微博架構

先來分享下大神Tim Yang的關于微博的架構設計&#xff1a; 這里主要從 存儲和接口角度來講 對于大流量系統的架構設計&#xff0c;對于寫入方面是特別需要注意的&#xff0c;基本上現在遇到的系統都是對于主數據庫的寫入&#xff0c;然后對于從數據庫實現流量的分發。 對于存…

Pytorch(7)-自己設計神經網絡會遇到的問題

操作pytorch架構遇到的問題1.網絡參數初始化2.查看當前可學習參數3.增加可學習的參數4.參數優化函數Adagrad5.直接修改網絡梯度值6.optimizers.zero_grad()報錯7.tensor.detach() 和 tensor.data 的區別1.網絡參數初始化 在pytorch中&#xff0c;有自己默認初始化參數方式&…

Python里的OS模塊常用函數說明

Python的標準庫中的os模塊包含普遍的操作系統功能。如果你希望你的程序能夠與平臺無關的話&#xff0c;這個模塊是尤為重要的。即它允許一個程序在編寫后不需要任何改動&#xff0c;也不會發生任何問題&#xff0c;就可以在Linux和Windows下運行。 下面列出了一些在os模塊中比較…

深度模型壓縮論文(03)- Be Your Own Teacher: Improve the Performance of Convolutional Neural Networks via Self

文章目錄1.摘要和背景1.1 摘要1.2 背景2.方法和貢獻2.1 方法2.1.1 訓練過程2.1.2 loss介紹2.2 貢獻3.實驗和結果3.1 實驗3.2 結果4.總結和展望4.1 總結4.2 展望主要貢獻&#xff1a;基于網絡蒸餾方法&#xff0c;提出了一種提升裁剪后模型的精度的方法&#xff01;將訓練時間大…

關系數據庫——基礎

數據庫系統概論 四個基本概念 數據&#xff1a;數據庫中存儲的基本對象&#xff0c;描述一個事物的符號記錄&#xff0c;數據和其語義不可分開說 數據庫&#xff08;DB&#xff09;&#xff1a;是長期儲存在計算機內、有組織的、可共享的大量數據的集合。 數據庫管理系統&a…

Python(27)-模塊

模塊、包1.模塊導入的方式2.使用as給模塊取一個別名&#xff08;大駝峰命名法&#xff09;3.從模塊中導入部分工具4.從模塊中導入全部工具5.模塊搜索順序6__name__7.包8.發布模塊、安裝模塊、卸載包9.pip安裝第三方模塊本系列博文來自學習《Python基礎視頻教程》筆記整理&#…

Wow6432Node

64 位版本 Windows 中的注冊表分為 32 位注冊表項和 64 位注冊表項。許多 32 位注冊表項與其相應的 64 位注冊表項同名,反之亦然。 64 位版本 Windows 包含的默認 64 位版本注冊表編輯器 (Regedit.exe) 可顯示 64 位和 32 位的 注冊表項。WOW64 注冊表重定向器為 32 位程序提供…

如何使用docker配置深度學習開發環境

文章目錄1.底層驅動的安裝1.1 操作系統的安裝1.2 顯卡驅動的安裝1.3 cuda的安裝2.使用docker配置深度學習開發環境2.1 docker的安裝2.2 nvidia_docker的安裝2.3 安裝過程中的問題2.3.1 docker和nvidia_docker的版本不匹配的問題。2.3.2 解決每次運行docker命令的時候要加sudo.2…

反射全解

反射的概念 反射的引入&#xff1a; Object obj new Student(); 若程序運行時接收到外部傳入的一個對象&#xff0c;該對象的編譯類型是Object&#xff0c;但程序又需要調用該對象運行類型的方法&#xff1a; 1.若編譯和運行類型都知道&#xff0c;使用 instanceof判斷后&…

MachineLearning(4)-核函數與再生核希爾伯特空間

核函數與再生核希爾伯特空間1.支持向量積-核函數2.一個函數為核函數的條件3.核函數與希爾伯特空間3.1希爾伯特空間-Hilbert空間1.支持向量積-核函數 核(kernel)的概念由Aizenman et al.于1964年引入模式識別領域&#xff0c;原文介紹的是勢函數的方法。在那之后&#xff0c;核…

CRegKey 注冊表操作

1.簡介 CRegKey提供了對系統注冊表的操作方法&#xff0c;通過CRegKey類&#xff0c;可以方便的打開注冊表的某個分支或子鍵&#xff08;CRegKey::Open&#xff09;&#xff0c;可以方便的修改一個鍵的鍵值&#xff08;CRegKey::SetValue&#xff09;&#xff0c;也可以查詢某…

進程基礎

進程的基本概念 程序順序執行的特征&#xff1a; 1&#xff09;順序性&#xff1a;處理機嚴格按照程序所規定的順序執行&#xff0c;每一步操作必須在下一步操作開始前執行 2&#xff09;封閉性&#xff1a;程序在封閉的環境下運行&#xff0c;程序獨占資源&#xff0c;資源的狀…

用Docker容器自帶的tensorflow serving部署模型對外服務

相信很多人和我一樣&#xff0c;在試圖安裝tensorflow serving的時候&#xff0c;翻遍了網上的博客和官網文檔&#xff0c;安裝都是以失敗而告終&#xff0c;我也是一樣&#xff0c;這個問題折磨了我兩個星期之久&#xff0c;都快放棄了。幸運的是在同事的建議下&#xff0c;我…

C資源

云風最近寫了一篇博客《C語言的前世今生》。作為長期使用C語言開發網絡游戲服務器的程序員&#xff0c;云風是有理由寫這樣一篇文字&#xff0c;不過還是感覺談的不夠深入&#xff0c;C語言在業界使用的現狀沒有怎么描寫&#xff0c;有些意猶未盡。在這里想比較系統的談談個人對…

學點數學(2)-特征函數

特征函數1.數列特征方程2.矩陣特征方程3.微分方程特征方程4.積分方程特征方程特征方程是為研究相應的數學對象而引入的一些等式&#xff0c;這些等式描述了特定對象的特性。依據研究的對象不同&#xff0c;特征方程包括數列特征方程、矩陣特征方程、微分方程特征方程、積分方程…