1. 程式人生 > >面試中一些基本概念的辨析

面試中一些基本概念的辨析

第一問 const與#define相比有什麼不同?

答案 1.const定義的只讀變數在程式執行過程中只有一份拷貝(因為它是全域性的只讀變數,存放在靜態區),而#define定義的巨集常量在記憶體中有若干個拷貝。 2.#define巨集是在預編譯階段進行替換,而const修飾的只讀變數是在編譯的時候確定其值。
3.#define巨集沒有型別,而const修飾的只讀變數具有特定的型別(編譯器可以對後者進行型別安全檢查,而對前者只進行字元替換,沒有安全檢查,並且在字元替換中可能會產生意料不到的錯誤)。 4.有些整合化的除錯工具可以對const常量進行除錯,但是不能對巨集變數進行除錯。C++中可以用const定義常量,也可以用#define定義常量,但是基本在C++中只使用const。

第二問 指標和引用的差別?

答: 1.非空區別。在任何情況下都不能使用指向空值的引用。一個引用必須總是指向某些物件。 2.合法性區別。在使用引用之前不需要測試它的合法性。相反,指標則應該總是被測試,防止其為空。 3.可修改區別。指標和引用的另一個重要區別就是指標可以重新賦值以指向另一個不同物件。但是引用則總是指向在初始化時被指定的物件,以後不能改變,但是指定的物件其內容可以改變。 4.應用區別。總的來說,在以下情況下應該使用指標:一是考慮到存在不指向任何物件的可能(在這種情況下,能夠設定指標為空),二是需要能夠在不同的時刻指向不同的物件(在這種情況下,你能改變指標的指向)。

例題1:

//下面程式中哪裡有錯?</span>
#include <iostream>
using namespace std;
int main() {
    int iv;                                
    int iv2 = 1024;                        
    int iv3 = 399;                         
    int &reiv;                             
    int &reiv2 = iv;                           
    int &reiv3 = iv;                       
    int *pi;                                   
    *pi = 5;                               
    pi = &iv3;                         
    const double di;
    const double maxWage = 10.0;
    const double minWage = 0.5;
    const double *pc = &maxWage;
 
    cout << pi;
    return 0;
}
錯誤:7,11,13行。
7:引用必須在宣告時同時初始化。
11:pi沒有指向,不可以對其賦值。
13: 本地的const常量必須在第一次宣告時就初始化。

例題2:

//這個程式有什麼問題?該如何修改? 
char *strA() {
    char str[] = “Hello World”;
    return str;
}
Answer:
str[]陣列是屬於函式strA()的區域性變數,當呼叫完這個函式後,區域性變數str被銷燬,所以返回的結果是不確定且不安全的。
要獲得正確的結果,可做如下修改:
a.  
const char* strA() {
    char *str = “Hello World”;
    //”Hello World”是字串常量,它位於靜態儲存區,在程式的生命期內是恆定不變的。
    //str是一個指標變數,它指向這個字串常量的首地址。
//指標變數str的值是可以改變的(可以改變指向),不能改變的是記憶體中儲存字串常量的記憶體單元的值。
    return str;
}


b.
const char* strA() {
    static char str[] = “Hello World”;
/*靜態區域性變數的生存期雖然為整個源程式,但是其作用域仍與自動變數相同,即只能在定義該變數的
函式內使用該變數。退出該函式後,儘管該變數還繼續存在,但不能使用它。*/
    return str;
}

第三問 二維陣列與指標

void foo(int[][3]);
int main(void)
{
 int a[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
 foo(a);
 printf("%d\n", a[2][1]);
 return 0;
}
void foo(int b[][3])
{
 ++b;
 b[1][1] = 9;
}
解析:++b的步長實際上是3個int,也就是++b運算以後,b指向{4,5,6}這個陣列的開始,而b[1]就是{7,8,9}, b[1][1]實際上就是8這個值也就是main函式中的a[2][1].

第四問 陣列指標  

int main()
{
int a[][3] = {1, 2, 3, 4, 5, 6};
int (*ptr)[3] = a;
printf("%d %d ", (*ptr)[1], (*ptr)[2]);
++ptr;
printf("%d %d\n", (*ptr)[1], (*ptr)[2]);
}
解析:依然是2維陣列相關題目,ptr為指向int [3]陣列的指標,首先指向a[0],所以(*ptr)[1], (*ptr)[2]就是a[0][1], a[0][2].然後++ptr,相當於ptr指向了a[1],這時得到的是a[1][1],a[1][2],所以結果就是2,3, 5, 6。

第五問 在int(*prt)[3];定義中,識別符號prt()。

A)定義不合法; B)是一個指標陣列名,每個元素都是一個指向整型變數的指標 C)是一個指標,它指向一個具有三個元素的一位陣列 D)是一個指向整型變數的指標

第六問 空指標和迷途指標的區別

#include <iostream.h>
int main(){
 int *pInt = new int;
 *pInt=10;
 cout<<*pInt<<endl;
 delete pInt;
 pInt=0;
 *pInt=20;    // oh no, this was deleled.
 cout<<*pInt<<endl;
 return 0;
}
//Compile this program successfully,but when running it, collapse.

迷途指標也叫懸浮指標,失控指標,是對一個指標delete後---這樣會釋放它所指向的記憶體---並沒有把它設定為空時產生的.而後,如果你沒有賦值就試圖再次使用該指標,引起的結果是不可預料的.

當delete一個指標時,實際上僅是讓編譯器釋放記憶體,但指標本身依然存在。這時它就是一個迷途指標。

當使用以下語句時,可以把迷途指標改為空指標:

MyPtr=0;

通常,如果在刪除一個指標後又把它刪除了一次,程式就會變得非常不穩定,任何情況都有可能發生。但是如果你只是刪除了一個空指標,則什麼事都不會發生,這樣做非常安全。

使用迷途指標或空指標(如MyPtr=0)是非法的,而且有可能造成程式崩潰。如果指標是空指標,儘管同樣是崩潰,但它同迷途指標造成的崩潰相比是一種可預料的崩潰。這樣除錯起來會方便的多。  

第七問 下面這3個函式哪一個最可能引起指標方面的問題

(a) 只有 f3
(b) 只有f1 and f3
(c) 只有f1 and f2
(d) f1 , f2 ,f3
int *f1(void)
{ 
	int x =10; 
	return(&x);
}
int *f2(void)
{ 
	int*ptr;  
	*ptr =10; 
	return ptr;
}
int *f3(void)
{ 
	int *ptr;  
	ptr=(int*)malloc(sizeof(int)); 
	return ptr;
}
解析:

f1:x是在函式f1中定義的區域性變數,生命週期開始於f1函式開始執行,結束於f1函式執行完畢,當f1返回呼叫它的函式中時,f1就執行完畢了,在f1中定義的變數(如int x)都結束了自己的生命週期,實際上就是儲存x的空間被釋放了。所以返回指向x的指標時,這個指標指向的記憶體區域已經被釋放了,這個指標也就成了野指標。
f2:定義了int *ptr但是這個指標並沒有指向任何記憶體空間(正確的分配空間的方法可參考f3的分配記憶體,指標為野指標),就比如現在你只有一個電話號碼,而這個號碼是空號,你就不能再這個電話號碼對應的電話機上做上標記。
f3:函式內申請動態記憶體空間,函式結束,該記憶體空間不會釋放,函式返回該記憶體空間地址,外部可以使用,但外部需要手動釋放該空間(free(動態記憶體空間地址))。

第八問 malloc/ free與new/ delete的區別

相同點:

都可用於申請動態記憶體和釋放記憶體

不同點:

(1)操作物件有所不同。

malloc與free是C++/C 語言的標準庫函式,new/delete 是C++的運算子。對於非內部資料類的物件而言,光用maloc/free 無法滿足動態物件的要求。物件在建立的同時要自動執行建構函式, 物件消亡之前要自動執行解構函式。由於malloc/free 是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式和解構函式的任務強加malloc/free。

(2)用法上也有所不同。

Malloc/free使用要點:

函式malloc 的原型如下void * malloc(size_t size);用malloc 申請一塊長度為length 的整數型別的記憶體,程式如下:int *p = (int *) malloc(sizeof(int) * length);我們應當把注意力集中在兩個要素上:“型別轉換”和“sizeof”。1、malloc 返回值的型別是void *,所以在呼叫malloc 時要顯式地進行型別轉換,將void * 轉換成所需要的指標型別。2、 malloc 函式本身並不識別要申請的記憶體是什麼型別,它只關心記憶體的總位元組數。函式free 的原型如下void free( void * memblock );為什麼free 函式不象malloc 函式那樣複雜呢?這是因為指標p 的型別以及它所指的記憶體的容量事先都是知道的,語句free(p)能正確地釋放記憶體。如果p 是NULL 指標,那麼free對p 無論操作多少次都不會出問題。如果p 不是NULL 指標,那麼free 對p連續操作兩次就會導致程式執行錯誤。

new/delete 的使用要點:

運算子new 使用起來要比函式malloc 簡單得多,例如:int *p1 = (int *)malloc(sizeof(int) * length);int *p2 = new int[length];這是因為new 內建了sizeof、型別轉換和型別安全檢查功能。對於非內部資料型別的物件而言,new 在建立動態物件的同時完成了初始化工作。如果物件有多個建構函式,那麼new 的語句也可以有多種形式。如果用new 建立物件陣列,那麼只能使用物件的無引數建構函式。例如Obj *objects = new Obj[100];       // 建立100 個動態物件不能寫成Obj *objects = new Obj[100](1);        // 建立100 個動態物件的同時賦初值1在用delete 釋放物件陣列時,留意不要丟了符號‘[]’。例如delete []objects; // 正確的用法delete objects; // 錯誤的用法後者相當於delete objects[0],漏掉了另外99 個物件。/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 1、new自動計算需要分配的空間,而malloc需要手工計算位元組數        2、new是型別安全的,而malloc不是,比如:                 int* p = new float[2]; // 編譯時指出錯誤                 int* p = malloc(2*sizeof(float)); // 編譯時無法指出錯誤          new operator 由兩步構成,分別是 operator new 和 construct        3、operator new對應於malloc,但operator new可以過載,可以自定義記憶體分配策略,甚至不做記憶體分配,甚至分配到非記憶體裝置上。而malloc無能為力        4、new將呼叫constructor,而malloc不能;delete將呼叫destructor,而free不能。        5、malloc/free要庫檔案支援,new/delete則不要。/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

第九問 realloc/ malloc/ calloc的區別

三個函式的申明分別是: 
void* realloc(void* ptr, unsigned newsize); 
void* malloc(unsigned size); 
void* calloc(size_t numElements, size_t sizeOfElement); 
都在stdlib.h函式庫內
它們的返回值都是請求系統分配的地址,如果請求失敗就返回NULL 

malloc用於申請一段新的地址,引數size為需要記憶體空間的長度,如: 
char* p; 
p=(char*)malloc(20);

calloc與malloc相似,引數sizeOfElement為申請地址的單位元素長度,numElements為元素個數,如: 
char* p; 
p=(char*)calloc(20,sizeof(char)); 
這個例子與上一個效果相同

realloc是給一個已經分配了地址的指標重新分配空間,引數ptr為原有的空間地址,newsize是重新申請的地址長度 
如: 
char* p; 
p=(char*)malloc(sizeof(char)*20); 
p=(char*)realloc(p,sizeof(char)*40);

注意,這裡的空間長度都是以位元組為單位。 
C語言的標準記憶體分配函式:malloc,calloc,realloc,free等。 
malloc與calloc的區別為1塊與n塊的區別: 
malloc呼叫形式為(型別*)malloc(size):在記憶體的動態儲存區中分配一塊長度為“size”位元組的連續區域,返回該區域的首地址。 
calloc呼叫形式為(型別*)calloc(n,size):在記憶體的動態儲存區中分配n塊長度為“size”位元組的連續區域,返回首地址。 
realloc呼叫形式為(型別*)realloc(*ptr,size):將ptr記憶體大小增大到size。 


free的呼叫形式為free(void*ptr):釋放ptr所指向的一塊記憶體空間。 
C++中為new/delete函式。

第十問 全域性變數、 區域性變數、 靜態區域性變數及靜態全域性變數的區別

(一)、程式的記憶體分配

一個由C/C++編譯的程式佔用的記憶體分為以下幾個部分:

1、棧區(stack)

— 由編譯器自動分配釋放 ,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。

2、堆區(heap) 

— 一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由OS回收 。注意它與資料結構中的堆是兩回事,分配方式倒是類似於連結串列。

3、全域性區(靜態區)(static)

— 全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域, 未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。程式結束後有系統釋放

4、文字常量區 

— 常量字串就是放在這裡的。 程式結束後由系統釋放。

5、程式程式碼區 

— 存放函式體的二進位制程式碼。

(二)、例子程式

//main.cpp

int a = 0; 全域性初始化區

char *p1; 全域性未初始化區

main()

{

int b;// 棧

char s[] = "abc"; //棧

char *p2; //棧

char *p3 = "123456"; 123456\0";//在常量區,p3在棧上。

static int c =0; //全域性(靜態)初始化區

p1 = (char *)malloc(10);

p2 = (char *)malloc(20);

//分配得來得10和20位元組的區域就在堆區。

strcpy(p1, "123456"); //123456\0放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。

}

C++變數根據定義位置的不同,具有不同的作用域,作用域可分為6種:全域性作用域,區域性作用域,語句作用域,類作用域,命名作用域和檔案作用域。

(三)、從作用域看

全域性變數具有全域性作用域

全域性變數只需在一個原始檔中定義,就可以作用於所有的原始檔。當然,其他不包括全域性變數定義的原始檔需要用extern關鍵字再次宣告這個全域性變數。

靜態區域性變數具有區域性作用域

它只被初始化一次,自從第一次初始化直到程式與你新內閣結束都一直存在,他和全域性變數的區別在於全域性變數對所有的函式都是可見的,而靜態區域性變數只對定義自己的函式體始終可見。

區域性變數也只有區域性作用域

他是自動物件,他在程式執行期間不是一直存在,而是隻在函式執行期間存在,函式的一次呼叫結束後,變數就被撤銷,其所佔用的記憶體也被收回。

靜態全域性變數也具有全域性作用域

他與全域性變數的區別在於如果程式包含多個檔案的話,他作用於定義它的檔案裡,不能作用到其他檔案裡,即被static關鍵字修飾過的變數具有檔案作用域。這樣即使兩個不同的原始檔都定義了相同的靜態全域性變數,他們也是不同的變數。

(四)、從分配記憶體空間看

全域性變數、靜態區域性變數、靜態全域性變數都在靜態儲存區分配空間,而區域性變數在棧分配空間

全域性變數本身就是靜態儲存方式,靜態全域性變數當然也是靜態儲存方式。這兩者在儲存方式上沒有什麼不同。區別在於非靜態全域性變數的作用域是整個源程式,當一個源程式由多個原始檔組成時,非靜態的全域性變數在各個原始檔中都是有效的。而靜態全域性變數則限制了其作用域,即只在定義該變數的原始檔內有效,在同一源程式的其他原始檔中不能使用它。由於靜態全域性變數的作用域侷限於一個原始檔內,只能為該原始檔內的函式公用,因此可以避免在其他原始檔中引起錯誤。

1、靜態變數會被放在程式的靜態資料儲存區裡,這樣可以在下一次呼叫的時候還可以保持原來的賦值。這一點是他與堆疊變數和堆變數的區別

2、變數用static告知編譯器,自己僅僅在變數的作用域範圍內可見。這一點是他與全域性變數的區別。

從以上分析可以看出,把區域性變數改變為靜態變數後是改變了他的儲存方式,即改變了他的生存期。把全域性變數改變為靜態變數後是改變了他的作用域,限制了他的使用範圍,因此static這個說明符在不同的地方起的作用是不同的。

TIPS:

1、若全域性變數僅在單個檔案中訪問,則可以講這個變數修改為靜態全域性變數。

2、若全域性變數僅在單個函式中使用,則可以將這個變數修改為該函式的靜態區域性變數。

3、全域性變數、靜態區域性變數、靜態全域性變數都存放在靜態資料儲存區。

4、函式中必須要使用static變數的情況:當某函式的返回值為指標型別時,則必須是static的區域性變數的地址作為返回值,若為auto型別,則返回為錯指標。

第十一問 main/ int main/ void main

main()是省略了返回值型別,C語言會預設認為成main的型別為int,在main()的函式體內要返回一個值,如return 0; void main()的返回值是void型別,也就是說沒有返回值。這樣在main()的函式體內你就不用寫return 0;或者return 1;之類的返回語句。 

在一個程式中,可以說你看不出兩者的差別,因為main()是C語言的入口點,入口點如果返回了程式也就結束了,因此C語言的這個特性似乎沒用。可沒用的話,C語言的設計者為什麼要這麼做呢?!肯定有用。 

沒錯,前面我說的是在一個程式中,它似乎沒用。可如果一個程式prog2呼叫另一個程式prog1的話(這裡指的是prog2直接呼叫prog1編譯好的可執行檔案),那麼這個特性就有用了。因為prog2要知道prog1執行之後的狀態。比如你用C寫了一個刪除檔案的程式(暫且命名為delf),然後用另一個程式去呼叫delf,那麼呼叫結果怎麼樣啊?這個檔案是否刪掉了?這是int main()就會派上用場了。C語言中預設的main如果返回為0,那麼這個程式呼叫就成功了,其他值,則為不同的錯誤程式碼。在你的delf程式中,如果那個檔案刪除成功,那麼就在int main()中返回0,其他呼叫delf的程式就知道,噢,這個操作成功了。 

在unix/linux系統中的很多命令都是通過這種方式獲得執行結果的。 

void main(int argc, char** argv)

 這個函式搞個兩星號,看上去比較複雜,但是如果改成這樣就感覺熟悉的了許多void main(int argc, char* argv[ ]).

void main(int argc, char* argv[ ]) == void main(int argc, char* *argv)

這行中有三個字串分別為 cp.exe doc1.txt doc2.txt 

則argc為3,即argc是命令列中引數的個數。

char *argv[]為一個指像字串的陣列。

argv[0]="cp.exe"

argv[1]="doc1.txt" 

argv[2]="doc2.txt"

agv[0]為一個字串指標,它就象 *p=”goodbye”  實際上是*argv[0]=”cp.exe”

p為goodbye的首地址,*p則指向goodbye的第一個字元, *p=”goodbye”與p[ ]=”goodbye”完全致的作用, 同理argv[0]就指向cp.exe的地址,*argv[0]指向cp.exe的第一個字元,即c。

再如下一個檔案為test.c的檔案

#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
int main(int argc, char **argv)
{   //用 gcc –o a test.c  然後執行 ./a  12345,以下為原程式和執行結果
char *p="goodbye";
char *p1[2]={"hello!!!!!!!","world"};
 
printf("%s\n",p); // goodbye
 
printf("%c\n",*p);//  g
 
printf("%s\n",p1[0]); // hello!!!!!!!
 
printf("%s\n",p1[1]); // world
 
printf("%s\n",argv[0]);//  ./a
 
printf("%s\n",argv[1]);//  12345
 
}
 
 
在你執行程式以後,作業系統會自動將引數傳給你。 
例如你編譯好的程式叫做program.exe 
你執行 program a b 
這個時候,argc = 3 
argv[0] = "program" 
argv[1] = "a" 
argv[2] = "b"

第十二問 const的用法

const用法主要是防止定義的物件再次被修改,定義物件變數時要初始化變數

下面我就介紹一下幾種常見的用法

1.用於定義常量變數,這樣這個變數在後面就不可以再被修改

const int Val = 10;
 //Val = 20; //錯誤,不可被修改

2. 保護傳參時引數不被修改,如果使用引用傳遞引數或按地址傳遞引數給一個函式,在這個函式裡這個引數的值若被修改,則函式外部傳進來的變數的值也發生改變,若想保護傳進來的變數不被修改,可以使用const保護

void  fun1(const int &val)
  {
     //val = 10; //出錯
}
void fun2(int &val)
{
   val = 10; //沒有出錯
}
void main()
{
   int a = 2;
   int b = 2;
   fun1(a); //因為出錯,這個函式結束時a的值還是2
   fun2(b);//因為沒有出錯,函式結束時b的值為10
}

如果只想把值傳給函式,而且這個不能被修改,則可以使用const保護變數,有人會問為什麼不按值傳遞,按值傳遞還需要把這個值複製一遍,而引用不需要,使用引用是為了提高效率,如果按值傳遞的話,沒必要加const,那樣根本沒意義。

3. 節約記憶體空間

#define  PI  3.14 //使用#define巨集
 const double Pi = 3.14 //使用const,這時候Pi並沒有放入記憶體中
 double  a = Pi;  //這時候才為Pi分配記憶體,不過後面再有這樣的定義也不會再分配記憶體
 double  b = PI;  //編譯時分配記憶體
 double  c = Pi;  //不會再分配記憶體,
 double  d = PI;  //編譯時再分配記憶體

const定義的變數,系統只為它分配一次記憶體,而使用#define定義的常量巨集,能分配好多次,這樣const就很節約空間。

4.類中使用const修飾函式防止修改非static類成員變數

class
{
 public:
  void fun() const //加const修飾
   {
     a = 10; //出錯,不可修改非static變數
     b = 10; //對,可以修改
}
 private:
  int  a ;
  static int b;
}

5.修飾指標

const int *A; 或 int const *A;  //const修飾指向的物件,A可變,A指向的物件不可變
int *const A;              //const修飾指標A, A不可變,A指向的物件可變 
const int *const A;           //指標A和A指向的物件都不可變

6.用const修飾函式的引數

6.1 

如果引數作輸出用,不論它是什麼資料型別,也不論它採用“指標傳遞”還是“引用傳遞”,都不能加const 修飾,否則該引數將失去輸出功能。

6.2 

const 只能修飾輸入引數:

6.2.1如果輸入引數採用“指標傳遞”,那麼加const 修飾可以防止意外地改動該指標,起到保護作用

例如StringCopy 函式:

void StringCopy(char *strDestination, const char *strSource);

其中strSource 是輸入引數,strDestination 是輸出引數。給strSource 加上const修飾後,如果函式體內的語句試圖改動strSource 的內容,編譯器將指出錯誤。

6.2.2如果輸入引數採用“值傳遞”,由於函式將自動產生臨時變數用於複製該引數,該輸入引數本來就無需保護,所以不要加const 修飾。

例如不要將函式void Func1(int x) 寫成void Func1(const int x)。同理不要將函式void Func2(A a) 寫成void Func2(const A a)。其中A 為使用者自定義的資料型別。

6.2.3對於非內部資料型別的引數而言,象void Func(A a) 這樣宣告的函式註定效率比較。因為函式體內將產生A 型別的臨時物件用於複製引數a,而臨時物件的構造、複製、析構過程都將消耗時間。

為了提高效率,可以將函式宣告改為void Func(A &a),因為“引用傳遞”僅借用一下引數的別名而已,不需要產生臨時物件。但是函式void Func(A &a) 存在一個缺點:

“引用傳遞”有可能改變引數a,這是我們不期望的。解決這個問題很容易,加const修飾即可,因此函式最終成為void Func(const A &a)。

以此類推,是否應將void Func(int x) 改寫為void Func(const int &x),以便提高效率?完全沒有必要,因為內部資料型別的引數不存在構造、析構的過程,而複製也非常快,“值傳遞”和“引用傳遞”的效率幾乎相當。

問題是如此的纏綿,我只好將“const &”修飾輸入引數的用法總結一下。

對於非內部資料型別的輸入引數,應該將“值傳遞”的方式改為“const 引用傳遞”,目的是提高效率。例如將void Func(A a) 改為void Func(const A &a)。

對於內部資料型別的輸入引數,不要將“值傳遞”的方式改為“const 引用傳遞”。否則既達不到提高效率的目的,又降低了函式的可理解性。例如void Func(int x) 不應該改為void Func(const int &x)。

7.用const 修飾函式的返回值

如果給以“指標傳遞”方式的函式返回值加const 修飾,那麼函式返回值(即指標)的內容不能被修改,該返回值只能被賦給加const 修飾的同類型指標

例如函式:

const char * GetString(void);

如下語句將出現編譯錯誤:

char *str = GetString();

正確的用法是

const char *str = GetString();

如果函式返回值採用“值傳遞方式”,由於函式會把返回值複製到外部臨時的儲存單元中,加const 修飾沒有任何價值。

例如不要把函式int GetInt(void) 寫成const int GetInt(void)。

同理不要把函式A GetA(void) 寫成const A GetA(void),其中A 為使用者自定義的資料型別。

如果返回值不是內部資料型別,將函式A GetA(void) 改寫為const A & GetA(void)的確能提高效率。但此時千萬千萬要小心,一定要搞清楚函式究竟是想返回一個物件的“拷貝”還是僅返回“別名”就可以了,否則程式會出錯。

函式返回值採用“引用傳遞”的場合並不多,這種方式一般只出現在類的賦值函式中,目的是為了實現鏈式表達。

例如:

class A  
{  
  A & operate = (const A &other); // 賦值函式  
 };  
 A a, b, c; // a, b, c 為A 的物件  
   
 a = b = c; // 正常的鏈式賦值  
 (a = b) = c; // 不正常的鏈式賦值,但合法 

如果將賦值函式的返回值加const 修飾,那麼該返回值的內容不允許被改動。上例中,語句 a = b = c 仍然正確,但是語句 (a = b) = c 則是非法的。

8.const 成員函式

                        --------------(const的作用:說明其不會修改資料成員)

任何不會修改資料成員的函式都應該宣告為const 型別。如果在編寫const 成員函式時,不慎修改了資料成員,或者呼叫了其它非const 成員函式,編譯器將指出錯誤,這無疑會提高程式的健壯性。以下程式中,類stack 的成員函式GetCount 僅用於計數,從邏輯上講GetCount 應當為const 函式。編譯器將指出GetCount 函式中的錯誤。

class Stack  
 {  
 public:  
  void Push(int elem);  
  int Pop(void);  
  int GetCount(void) const; // const 成員函式  
 private:  
  int m_num;  
  int m_data[100];  
 };  
 int Stack::GetCount(void) const  
 {  
  ++m_num; // 編譯錯誤,企圖修改資料成員m_num  
  Pop(); // 編譯錯誤,企圖呼叫非const 函式  
  return m_num;  
 } 

const 成員函式的宣告看起來怪怪的:const 關鍵字只能放在函式宣告的尾部,大概是因為其它地方都已經被佔用了。

關於Const函式的幾點規則:

a. const物件只能訪問const成員函式,而非const物件可以訪問任意的成員函式,包括const成員函式.

b. const物件的成員是不可修改的,然而const物件通過指標維護的物件卻是可以修改的.

c. const成員函式不可以修改物件的資料,不管物件是否具有const性質.它在編譯時,以是否修改成員資料為依據,進行檢查.

e. 然而加上mutable修飾符的資料成員,對於任何情況下通過任何手段都可修改,自然此時的const成員函式是可以修改它的。

對於這樣的const function,關鍵詞const到底影響了什麼?下面用例子來說明。

class my {  
  public:  
// ...  
  string& operator[](const string& s) const  
  { return table[s]; }  
 private:  
  map table;  
 }; 

上述過載的下標運算函式能否通過編譯呢?不能(在mingw32 gcc3.4上)通過。給出瞭如下的錯誤提示:

passing `const std::map, std::allocator > >'

as `this' argument of `_Tp& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const _Key&) [with _Key = std::string, _Tp = std::string, _Compare = std::less, _Alloc = std::allocator >]' discards qualifiers

錯誤原因就在於,當本過載函式宣告為const後,就只能呼叫其他的同樣也宣告為const的函式;而table[s]實際上呼叫的是map的下標過載運算函式,該函式並沒有宣告為const。所以去掉本函式的const宣告,就可以順利通過編譯了。

下面再用一個小例子驗證一下吧。

#include  
 #include  
using namespace std;  
class A{  
public:  
 A(int n) { num = n; }  
 void incr() { num += 5; }  
 void disp() const  
 { cout << num ; }  
   
 int times(int m)  
 { return num * m; }  
   
 int f() const  
 {  
 incr();//passing `const A' as `this' argument of `void A::incr()' discards qualifiers  
 disp();// ok  
 times(2);//passing `const A' as `this' argument of `int A::times(int)' discards qualifiers  
 return num;  
 }  
   
 private: int num;  
 };  
   
 int main(int argc, char *argv[])  
 {  
 A a(5);  
 a.f();  
 system("PAUSE");  
 return EXIT_SUCCESS;  
 }  

const 函式只能呼叫 const 函式,即使某個函式本質上沒有修改任何資料,但沒有宣告為const,也是不能被const函式呼叫的。

class Test  
{  
 public :  
 int & GetValue()const;  
	 private:  
	 int value;  
};  
 	int &Test::GetValue() const  
 {  
	 return value; //value此時具有const屬性,與返回值型別int &的非const屬性不匹配  
}  


這樣的程式碼在vs2003中提示的錯誤:error C2440: “return” : 無法從“const int”轉換為“int &”。

在const函式中傳遞this的時候把this變成了const T* const this(個人理解),所以一個非const的引用指向一個const型別的變數,就會error。

可以這樣改,

1.把int value 改成mutable int value. mutable修飾的變數使之在const函式中可以被改變的。

2.return value 改成。 return const_cast(value)。const_cast去掉了const性質。

3.把函式寫成const int &Test::GetValue() const ,.這樣做的目的是使引用的變數也是const型別的,就相當於const int & b 。

4.把引用去掉,寫成返回值型別的。

5.把函式後面的const去掉。

6.返回值不是類的成員變數。

int &Test::GetValue() const  
 {  
 int temp = value;  
 return temp;  
 } 

9.修飾類的成員變數

  使用const修飾的變數必須初始化,在類中又不能在定義時初始化,

如;

class
{
private:
  int a = 10;
  const int b = 10;
  static const int c = 10;
//這樣初始化都是錯的,
}

 下列關於const關鍵字的說法錯誤的是:

A用const常量代替巨集定義可以讓編譯器進行安全性檢查

B類的const成員函式不能修改類的成員變數,而且一個const類物件只能呼叫其const成員函式,不能呼叫非const成員函式

C  const成員函式與同名、同返回值、同參數列表的非const成員函式屬於過載現象

D  推薦使用以下方面定義類成員陣列: class A{ … const size_t SIZE=100; int _array[SIZE]; };                                                         【D】

初始化const int型別(沒有static),在類的建構函式上初始化

Class Test
{
Public:
  Test():b(23) //建構函式上初始化b的值為23
   {
}
private:
     const int b ;
}
 
初始化static const int這個型別的(帶有static的),在類的外面初始化
class Test
{
private:
  static const int c;
} 
const int Test::c=10; //類的外部初始化c為10

10.const和static

成員函式都會在自己的形參表中加一個引數:這個引數是指向函式所在類的型別的CONST指標.比如:

class A
{
 private:
  int n;
 public:
  void set()
  {
    n=10;  
  };
};

上邊的set函式,是給n賦值的,但是它怎麼知道是給哪一個物件的n賦值呢?這就需要在引數中告訴它。編譯器為我們作了這些工作。實際生成的set函式可以理解成這樣的:

public A::set(A* const this)

{

  this-〉n=10;

}

而我們呼叫的時候其實是這樣的:

A a;

A::set(&a);

理解這個對你理解你的這個問題很關鍵.如果是const成員函式,編譯器所作的這項工作也會改變。它會生成一個指向cosnt物件的const指標。所以你不能通過this來改變它所指向的物件。但是要知道static物件並不需要this指標來改變,所以它和const函式是沒關係的。

第十三問 逗號運算子   

int main()
{
int a, b, c, d;
a = 3;
b = 5;
c = a, b;
d = (a, b);
printf("c=%d  ", c);
printf("d=%d\n", d);
}
解析:
C 語言中,逗號(,)也可以是運算子,稱為逗號運算子(Comma Operator)。逗號運算子可以把兩個以上(包含兩個)的表示式連線成一個表示式,稱為逗號表示式。其一般形式為:

子表示式1, 子表示式2, ..., 子表示式n

例如:

a + b, c = b, c++
逗號運算子的優先順序是所有運算子中級別最低的,通常配合 for 迴圈使用。逗號表示式最右邊的子表示式的值即為逗號表示式的值。上例中,c++ 的值(c 自增之前的值)即為該表示式的值。

逗號運算子保證左邊的子表示式運算結束後才進行右邊的子表示式的運算。也就是說,逗號運算子是一個序列點,其左邊所有副作用都結束後,才對其右邊的子表示式進行運算。因此,上例中,c 得到 b 的值後,才進行自增運算。

第十四問 sizeof

int main()
{
int i = 3;   int j;
j = sizeof(++i + ++i);
short m;    int n;     double dn;
int t = sizeof ( m + n);
int k = sizeof ( n + n);
int l = sizeof ( m);
int l2 = sizeof (m * m);
int l3 = sizeof (m + dn);
int l4 = sizeof (m + m);
printf("i=%d j=%d t=%d k=%d l=%d l2=%d l3=%d l4=%d\n", i,j,t,k,l,l2,l3,l4);
}

解析:這是求表示式的sizeof,詳細參考http://blog.csdn.net/u014186096/article/details/48290013

第十五問 傳遞

void f1(int*, int);
void (*p[2])(int*, int);
int main(void)
{
 int a = 3;  int b = 5;
 p[0] = f1;
 p[1] = f1;
 p[0](&a, b);
 printf("%d %d ", a, b);
 p[1](&a, b);
 printf("%d %d\n", a, b);
 return 0;
}
void f1(int *p, int q)
{
 int tmp = *p;   *p = q;   q = tmp;
}

解析:詳解參考(值傳遞、指標傳遞、引用傳遞)http://blog.csdn.net/u014186096/article/details/48785533

第十六問 CPU程序與執行緒的關係和區別

程序是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程序是系統進行資源分配和排程的一個獨立單位。
  執行緒是程序的一個實體,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位。

程序和執行緒的關係:

  (1)一個執行緒只能屬於一個程序,而一個程序可以有多個執行緒,但至少有一個執行緒。
  (2)資源分配給程序,同一程序的所有執行緒共享該程序的所有資源。
  (3)處理機分給執行緒,即真正在處理機上執行的是執行緒。
  (4)執行緒在執行過程中,需要協作同步。不同程序的執行緒間要利用訊息通訊的辦法實現同步。

程序與執行緒的區別:

  (1)排程:執行緒作為排程和分配的基本單位,程序作為擁有資源的基本單位
  (2)併發性:不僅程序之間可以併發執行,同一個程序的多個執行緒之間也可併發執行
  (3)擁有資源:程序是擁有資源的一個獨立單位,執行緒不擁有系統資源,但可以訪問隸屬於程序的資源.
  (4) 系統開銷:在建立或撤消程序時,由於系統都要為之分配和回收資源,導致系統的開銷明顯大於建立或撤消執行緒時的開銷。但是程序有獨立的地址空間,一個程序崩潰後,在保護模式下不會對其它程序產生影響,而執行緒只是一個程序中的不同執行路徑。執行緒有自己的堆疊和區域性變數,但執行緒之間沒有單獨的地址空間,一個程序死掉就等於所有的執行緒死掉,所以多程序的程式要比多執行緒的程式健壯,但在程序切換時,耗費資源較大,效率要差一些

結論:

  (1)執行緒是程序的一部分
  (2)CPU排程的是執行緒
  (3)系統為程序分配資源,不對執行緒分配資源