There's [almost] never any need to use the keyword extern when declaring a function, either in C or in C++. In C and in C++ all functions have external linkage by default.
http://stackoverflow.com/questions/11712707/extern-functions-in-c-vs-c
2014年3月5日 星期三
Re: [C++] 有關 inline 函式...
作者 khoguan (Khoguan Phuann) 看板 C_and_CPP
標題 Re: [C++] 有關 inline 函式...
時間 Sun Aug 21 15:33:07 2005
───────────────────────────────────────
※ 引述《eliang ()》之銘言:
: 在 C++ Primer 這本書裡, 提到 inline 函式應該定義在 header file 裡
: 書上說明原因如下:
: An inline function may be defined more than once in a program as long as the
: definition appears only once in a given source file and the definition is
: exactly the same in each source file. By putting inline functions in headers,
: we ensure that the same definition is used whenever the function is called
: and that the compiler has the function definition available at the point of
: call.
: 我看不太懂它的意思...有沒有人可以解釋一下?
: 我之前都比較習慣把函式定義放在 .cpp 檔, 函式宣告放 .h 檔
: 因為這樣程式碼感覺起來比較乾淨, 像是這樣:
: // foo.h
: class foo {
: public:
: int f();
: };
: // foo.cpp
: inline int foo::f() {
: // short statement...
: }
既然要讓它 inline, 就把函式定義一起寫在 foo.h 中。
就是把你的 foo.cpp 檔的內容直接copy到 foo.h 檔原來的
內容後面。這樣一樣很乾淨啦。
: 可是我發現這樣寫 "有時候" 編譯會發生錯誤, 訊息大概就類似:
: [Linker error] undefined reference to 'foo::f()'
: 如果把 inline 去掉, 或照書上說的, 把定義改寫到 .h 檔, 就可以正常編譯,
: 請問這是什麼原因? 謝謝!!
因為 inline 的本質就是可以讓編譯器在單獨編譯某個
使用到這個 inline function 的 source file的時候,
可以將整個 inline function 的定義插入並取代呼叫
inline function的那行敘述。你沒給他函式定義,它
怎麼做這個動作呢?
當然,你指定要 inline 編譯器不一定真能做到 inline,
inline 只是吾人對編譯器的「建議」。但是好歹你要將
inline function 的定義透過 .h 檔 include 進來,不然,
編譯器根本沒機會幫你 inline.
inline 的時機是定義簡短的函式。太長的、或是 virtual
function 或是 recursive function 都不宜或不能 inline.
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 220.130.208.168
推 renderer:推 滿重要的 inline 概念 61.228.217.108 08/21
推 eliang:說明得很詳細,感謝!! 210.58.40.9 08/21
標題 Re: [C++] 有關 inline 函式...
時間 Sun Aug 21 15:33:07 2005
───────────────────────────────────────
※ 引述《eliang ()》之銘言:
: 在 C++ Primer 這本書裡, 提到 inline 函式應該定義在 header file 裡
: 書上說明原因如下:
: An inline function may be defined more than once in a program as long as the
: definition appears only once in a given source file and the definition is
: exactly the same in each source file. By putting inline functions in headers,
: we ensure that the same definition is used whenever the function is called
: and that the compiler has the function definition available at the point of
: call.
: 我看不太懂它的意思...有沒有人可以解釋一下?
: 我之前都比較習慣把函式定義放在 .cpp 檔, 函式宣告放 .h 檔
: 因為這樣程式碼感覺起來比較乾淨, 像是這樣:
: // foo.h
: class foo {
: public:
: int f();
: };
: // foo.cpp
: inline int foo::f() {
: // short statement...
: }
既然要讓它 inline, 就把函式定義一起寫在 foo.h 中。
就是把你的 foo.cpp 檔的內容直接copy到 foo.h 檔原來的
內容後面。這樣一樣很乾淨啦。
: 可是我發現這樣寫 "有時候" 編譯會發生錯誤, 訊息大概就類似:
: [Linker error] undefined reference to 'foo::f()'
: 如果把 inline 去掉, 或照書上說的, 把定義改寫到 .h 檔, 就可以正常編譯,
: 請問這是什麼原因? 謝謝!!
因為 inline 的本質就是可以讓編譯器在單獨編譯某個
使用到這個 inline function 的 source file的時候,
可以將整個 inline function 的定義插入並取代呼叫
inline function的那行敘述。你沒給他函式定義,它
怎麼做這個動作呢?
當然,你指定要 inline 編譯器不一定真能做到 inline,
inline 只是吾人對編譯器的「建議」。但是好歹你要將
inline function 的定義透過 .h 檔 include 進來,不然,
編譯器根本沒機會幫你 inline.
inline 的時機是定義簡短的函式。太長的、或是 virtual
function 或是 recursive function 都不宜或不能 inline.
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 220.130.208.168
推 renderer:推 滿重要的 inline 概念 61.228.217.108 08/21
推 eliang:說明得很詳細,感謝!! 210.58.40.9 08/21
C&C++: Re: [問題] 請問static是什麼意思...
作者 meltice (三億兩千萬大散戶) 看板 C_and_CPP
標題 Re: [問題] 請問static是什麼意思...
in C/C++
static variable就像是global變數
差別只在它的visible範圍只在該scope內
所以static變數的初始值就是0
當然你也可以像global變數一樣指定初始值給它
static function表示該function的visible範圍只在該.cpp內
可以避免污染global namespace
所以很多個.cpp內可以有同樣名稱的static function
在C++ class之中
static member variable在該class中始終只存在一份
不會因為new delete而有數量上的變化
需要在global定義初始值
不用宣告實體object即可存取該值
static member function
只允許存取static member variable或function
不用宣告實體object即可呼叫該function
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 60.245.93.33
標題 Re: [問題] 請問static是什麼意思...
in C/C++
static variable就像是global變數
差別只在它的visible範圍只在該scope內
所以static變數的初始值就是0
當然你也可以像global變數一樣指定初始值給它
static function表示該function的visible範圍只在該.cpp內
可以避免污染global namespace
所以很多個.cpp內可以有同樣名稱的static function
在C++ class之中
static member variable在該class中始終只存在一份
不會因為new delete而有數量上的變化
需要在global定義初始值
不用宣告實體object即可存取該值
static member function
只允許存取static member variable或function
不用宣告實體object即可呼叫該function
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 60.245.93.33
C&C++: [重要] 發文前務必閱讀:常見問題十三誡
作者 nowar100 (拋磚引玉) 看板 C_and_CPP
標題 [重要] 發文前務必閱讀:常見問題十三誡
C 語言新手十誡(The Ten Commandments for Newbie C Programmers)
by Khoguan Phuann
請注意:
(1) 本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。
但不能取代完整的學習,請自己好好研讀一兩本 C 語言的好書,
並多多實作練習。
(2) 強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並
解答了。
(3) 以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果,
那只是不足為恃的一時僥倖。
(4) 不守十誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則
引爆核彈、毀滅地球(如果你的 C 程式是用來控制核彈發射器的話)。
████████ 請選擇相對應的數字鍵,或按 End 結束播放 ████████
1. 不可以使用尚未給予適當初值的變數
2. 不能存取超過陣列既定範圍的空間
3. 不可以提取不知指向何方的指標
4. 不要試圖用 char* 去更改一個"字串常數"
5. 不能在函式中回傳一個指向區域性自動變數的指標
6. 不可以只做 malloc(), 而不做相應的 free()
7. 在數值運算、賦值或比較中不可以隨意混用不同型別的數值
8. 在一個運算式中,不能對一個基本型態的變數修改其值超過一次以上
9. 在 Macro 定義中, 務必為它的參數個別加上括號
A(10). 不可以在 stack 設置過大的變數
B(11). 使用浮點數精確度造成的誤差問題
C(12). 不要猜想二維陣列可以用 pointer to pointer 來傳遞
D(13). 函式內 new 出來的空間記得要讓主程式的指標接住
01. 你不可以使用尚未給予適當初值的變數
錯誤例子:
int accumulate(int max) /* 從 1 累加到 max,傳回結果 */
{
int sum; /* 未給予初值的區域變數,其內容值是垃圾 */
int num;
for (num = 1; num <= max; num++) { sum += num; }
return sum;
}
正確例子:
int accumulate(int max)
{
int sum = 0; /* 正確的賦予適當的初值 */
int num;
for (num = 1; num <= max; num++) { sum += num; }
return sum;
}
02. 你不可以存取超過陣列既定範圍的空間
錯誤例子:
int str[5];
int i;
for (i = 0 ; i <= 5 ; i++) str[i] = i;
正確例子:
int str[5];
int i;
for (i = 0; i < 5; i++) str[i] = i;
說明:宣告陣列時,所給的陣列元素個數值如果是 N, 那麼我們在後面
透過 [索引值] 存取其元素時,所能使用的索引值範圍是從 0 到 N-1
C/C++ 為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界,
我們要自己來確保不會越界。一旦越界,操作的不再是合法的空間,
將導致無法預期的後果。
03. 你不可以提取(dereference)不知指向何方的指標(包含 null 指標)。
錯誤例子:
char *pc1; /* 未給予初值,不知指向何方 */
char *pc2 = 0; /* pc2 起始化為 null pointer */
*pc1 = 'a'; /* 將 'a' 寫到不知何方,錯誤 */
*pc2 = 'b'; /* 將 'b' 寫到「位址0」,錯誤 */
正確例子:
char c; /* c 的內容尚未起始化 */
char *pc1 = &c; /* pc1 指向字元變數 c */
*pc1 = 'a'; /* c 的內容變為 'a' */
/* 動態分配 10 個 char(其值未定),並將第一個char的位址賦值給 pc2 */
char *pc2 = (char *) malloc(10);
pc2[0] = 'b'; /* 動態配置來的第 0 個字元,內容變為 'b'
free(pc2);
說明:指標變數必需先指向某個可以合法操作的空間,才能進行操作。
( 使用者記得要檢查 malloc 回傳是否為 NULL,
礙於篇幅本文假定使用上皆合法,也有正確歸還記憶體 )
錯誤例子:
char *name; /* name 尚未指向有效的空間 */
printf("Your name, please: ");
gets(name); /* 您確定要寫入的那塊空間合法嗎??? */
printf("Hello, %s\n", name);
正確例子:
/* 如果編譯期就能決定字串的最大空間,那就不要宣告成 char* 改用 char[] */
char name[21]; /* 可讀入字串最長 20 個字元,保留一格空間放 '\0' */
printf("Your name, please: ");
gets(name);
printf("Hello, %s\n", name);
正確例子(2):
/* 若是在執行時期才能決定字串的最大空間,則需利用 malloc() 函式來動態
分配空間 */
size_t length;
char *name;
printf("請輸入字串的最大長度(含null字元): ");
scanf("%u", &length);
name = (char *)malloc(length);
printf("Your name, please: ");
scanf("%s", name);
printf("Hello, %s\n", name);
/* 最後記得 free() 掉 malloc() 所分配的空間 */
free(name);
04. 你不可以試圖用 char* 去更改一個"字串常數"
錯誤例子:
char* pc = "john"; /* pc 現在指著一個字串常數 */
*pc = 'J'; /* 但是 pc 沒有權利去更改這個常數! */
正確例子:
char pc[] = "john"; /* pc 現在是個合法的陣列,裡面住著字串 john */
/* 也就是 pc[0]='j', pc[1]='o', pc[2]='h',
pc[3]='n', pc[4]='\0' */
*pc = 'J';
pc[2] = 'H';
說明:字串常數的內容是"唯讀"的。您有使用權,但是沒有更改的權利。
若您希望使用可以更改的字串,那您應該將其放在合法空間
錯誤例子:
char *s1 = "Hello, ";
char *s2 = "world!";
/* strcat() 不會另行配置空間,只會將資料附加到 s1 所指唯讀字串的後面,
造成寫入到程式無權碰觸的記憶體空間 */
strcat(s1, s2);
正確例子(2):
/* s1 宣告成陣列,並保留足夠空間存放後續要附加的內容 */
char s1[20] = "Hello, ";
char *s2 = "world!";
/* 因為 strcat() 的返回值等於第一個參數值,所以 s3 就不需要了 */
strcat(s1, s2);
05. 你不可以在函式中回傳一個指向區域性自動變數的指標。否則,會得到垃圾值
[感謝 gocpp 網友提供程式例子]
錯誤例子:
char *getstr(char *name)
{
char buf[30] = "hello, "; /*將字串常數"hello, "的內容複製到buf陣列*/
strcat(buf, name);
return buf;
}
說明:區域性自動變數,將會在離開該區域時(本例中就是從getstr函式返回時)
被消滅,因此呼叫端得到的指標所指的字串內容就失效了。
正確例子:
void getstr(char buf[], int buflen, char const *name)
{
char const s[] = "hello, ";
strcpy(buf, s);
strcat(buf, name);
}
正確例子:
int* foo()
{
int* pInteger = (int*) malloc( 10*sizeof(int) );
return pInteger;
}
int main()
{
int* pFromfoo = foo();
}
說明:上例雖然回傳了函式中的指標,但由於指標內容所指的位址並非區域變數,
而是用動態的方式抓取而得,換句話說這塊空間是長在 heap 而非 stack,
又因 heap 空間並不會自動回收,因此這塊空間在離開函式後,依然有效
(但是這個例子可能會因為 programmer 的疏忽,忘記 free 而造成
memory leak)
[針對字串操作,C++提供了更方便安全更直觀的 string class, 能用就盡量用]
正確例子:
#include <string> /* 並非 #include <cstring> */
using std::string;
string getstr(string const &name)
{
return string("hello, ") += name;
}
06. 你不可以只做 malloc(), 而不做相應的 free(). 否則會造成記憶體漏失
但若不是用 malloc() 所得到的記憶體,則不可以 free()。已經 free()了
所指記憶體的指標,在它指向另一塊有效的動態分配得來的空間之前,不可
以再被 free(),也不可以提取(dereference)這個指標。
[C++] 你不可以只做 new, 而不做相應的 delete
注:new 與 delete 對應,new[] 與 delete[] 對應,不可混用
切記,做了幾次 new,就必須做幾次 delete
小技巧: 可在 delete 之後將指標指到 0,由於 delete 本身會先做檢查,
因此可以避免掉多次 delete 的錯誤
正確例子:
int *ptr = new int(99);
delete ptr;
ptr = NULL;
delete ptr; /* delete 只會處理指向非 NULL 的指標 */
07. 你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考
慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。必須隨時注意數值運算
的結果,其範圍是否會超出變數的型別
錯誤例子:
unsigned int sum = 2000000000 + 2000000000; /* 超出 int 存放範圍 */
unsigned int sum = (unsigned int) (2000000000 + 2000000000);
double f = 10 / 3;
正確例子:
/* 全部都用 unsigned int, 注意數字後面的 u, 大寫 U 也成 */
unsigned int sum = 2000000000u + 2000000000u;
/* 或是用顯式的轉型 */
unsigned int sum = (unsigned int) 2000000000 + 2000000000;
double f = 10.0 / 3.0;
錯誤例子:
unsigned int a = 0;
int b[10];
for(int i = 9 ; i >= a ; i--) { b[i] = 0; }
說明:由於 int 與 unsigned 共同運算的時候,會提升 int 為 unsigned,
因此迴圈條件永遠滿足,與預期行為不符
錯誤例子: (感謝 sekya 網友提供)
unsigned char a = 0x80; /* no problem */
char b = 0x80; /* implementation-defined result */
if( b == 0x80 ) { /* 不一定恒真 */
printf( "b ok\n" );
}
說明:語言並未規定 char 天生為 unsigned 或 signed,因此將 0x80 放入
char 型態的變數,將會視各家編譯器不同作法而有不同結果
08. 你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值
超過一次以上。否則,將導致未定義的行為(undefined behavior)
錯誤例子:
int i = 7;
int j = ++i + i++;
正確例子:
int i = 7;
int j = ++i;
j += i++;
你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,
而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的,
是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。
錯誤例子:
x = x++;
錯誤例子:
int arr[5];
int i = 0;
arr[i] = i++;
正確例子:
int arr[5];
int i = 0;
arr[i] = i;
i++;
錯誤例子:
int i = 10;
cout << i << "==" << i++;
正確例子:
int i = 10;
cout << i << "==";
cout << i++;
錯誤例子:
int Integer=10;
printf( "%d %d %d", Integer++, Integer++, Integer++ );
錯誤例子:
void foo(int a, int b) { ... }
int main() {
int i=0;
foo(i++, i++);
}
說明: C/C++ 並沒有強制規定參數會由哪個方向開始處理(不像Java是由左到右),
因此可能會造成與預期不符的情況
09. 在 Macro 定義中, 務必為它的參數個別加上括號
錯誤例子:
#include <stdio.h>
#define SQUARE(x) (x * x)
int main()
{
printf("%d\n", SQUARE(10-5));
return 0;
}
正確例子:
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
int main()
{
printf("%d\n", SQUARE(10-5));
return 0;
}
說明:如果是用 C++, 請多多利用 inline function 來取代上述的 macro,
以免除 macro 定義的種種危險性。如:
inline int square(int x) { return x * x; }
macro 定義出的「偽函式」至少缺乏下列數項函式本有的能力:
(1) 無法進行參數型別的檢查。
(2) 無法遞迴呼叫。
(3) 無法用 & 加在 macro name 之前,取得函式位址。
(4) 呼叫時往往不能使用具有 side effect 的引數。例如:
錯誤例子: (感謝 yaca 網友提供)
#define MACRO(x) (((x) * (x)) - ((x) * (x)))
int main()
{
int x = 3;
printf("%d\n", MACRO(++x));
return 0;
}
10. 不可在 stack 設置過大的變數,否則會造成 stack overflow
(感謝 VictorTom 版友幫忙)
錯誤例子:
int array[10000000]; // 僅舉例說明
說明:由於編譯器會自行決定 stack 的上限,某些預設是數 KB 或數十 KB,
當變數所需的空間過大時,很容易造成 stack overflow,程式亦隨之
當掉,若真正需要如此大的空間,那麼建議配置在 heap 上,或是採用
static / globla variable,亦或是改變編譯器的設定
使用 heap 時,雖然整個 process 可用的空間是有限的,但採用動態抓取
的方式,new 無法配置時會丟出 std::bad_alloc 例外,malloc 無法配置
時會回傳 null,不會影響到正常使用下的程式功能
正確例子:
int *array = (int*) malloc( 10000000*sizeof(int) );
說明:由於此時 stack 上只需配置一個 int* 的空間,可避免 stack overflow
更多說明請參考精華區 z-10-13
11. 使用浮點數千萬要注意精確度所造成的誤差問題
根據 IEEE 754 的規範,又電腦中是用有限的二進位儲存數字,因此常有可
能因為精確度而造成誤差,例如加減乘除,等號大小判斷,分配律等數學上
常用到的操作,很有可能因此而出錯(不成立)
更詳細的說明可以參考精華區 z-8-11
或參考冼鏡光老師所發表的一文 "使用浮點數最最基本的觀念"
http://blog.dcview.com/article.php?a=VmgBZFE5AzI%3D
12. 不要猜想二維陣列可以用 pointer to pointer 來傳遞
(感謝 loveme00835 legnaleurc 版友的幫忙)
首先必須有個觀念,C 語言中陣列是無法直接拿來傳遞的!
不過這時候會有人跳出來反駁:
void pass1DArray( int array[] );
int a[10];
pass1DArray( a ); /* 可以合法編譯,而且執行結果正確!! */
事實上,編譯器會這麼看待
void pass1DArray( int *array );
int a[10];
pass1DArray( &a[0] );
我們可以順便看出來,array 變數本身可以 decay 成記憶體起頭的位置
因此我們可以 int *p = a; 這種方式,拿指標去接陣列。
也因為上述的例子,許多人以為那二維陣列是不是也可以改成 int **
錯誤例子:
void pass2DArray( int **array );
int a[5][10];
pass2DArray( a );
/* 這時候編譯器就會報錯啦 */
/* expected ‘int **’ but argument is of type ‘int (*)[10]’*/
在一維陣列中,指標的移動操作,會剛好覆蓋到陣列的範圍
例如,宣告了一個 a[10],那我可以把 a 當成指標來操作 *a 至 *(a+9)
因此我們可以得到一個概念,在操作的時候,可以 decay 成指標來使用
也就是我可以把一個陣列當成一個指標來使用 (again, 陣列!=指標)
但是多維陣列中,無法如此使用,事實上這也很直觀,試圖拿一個
pointer to pointer to int 來操作一個 int 二維陣列,這是不合理的!
^L#@N,f+1,下一頁#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
儘管我們無法將二維陣列直接 decay 成兩個指標,但是我們可以換個角度想,
二維陣列可以看成 "外層大的一維陣列,每一維內層各又包含著一維陣列"
如果想通了這一點,我們可以仿造之前的規則,
把外層大的一維陣列 decay 成指標,該指標指向內層的一維陣列
void pass2DArray( int (*array) [10] ); // array 是個指標,指向 int [10]
int a[5][10];
pass2DArray( a );
這時候就很好理解了,函數 pass2DArray 內的 array[0] 會代表什麼呢?
答案是它代表著 a[0] 外層的那一維陣列,裡面包含著內層 [0]~[9]
也因此 array[0][2] 就會對應到 a[0][2],array[4][9] 對應到 a[4][9]
結論就是,只有最外層的那一維陣列可以 decay 成指標,其他維陣列都要
明確的指出陣列大小,這樣多維陣列的傳遞就不會有問題了
也因為剛剛的例子,我們可以清楚的知道在傳遞陣列時,實際行為是在傳遞
指標,也因此如果我們想用 sizeof 來求得陣列元素個數,那是不可行的
錯誤例子:
void print1DArraySize( int* arr ) {
printf("%u", sizeof(arr)/sizeof(arr[0])); /* sizeof(arr) 只是 */
} /* 一個指標的大小 */
受此限制,我們必須手動傳入大小
void print1DArraySize( int* arr, size_t arrSize );
C++ 提供 reference 的機制,使得我們不需再這麼麻煩,
可以直接傳遞陣列的 reference 給函數,大小也可以直接求出
正確例子:
void print1DArraySize( int (&array)[10] ) { // 傳遞 reference
cout << sizeof(array) / sizeof(int); // 正確取得陣列元素個數
}
13. 函式內 new 出來的空間記得要讓主程式的指標接住
對指標不熟悉的使用者會以為以下的程式碼是符合預期的
void newArray(int* local, int size) {
local = (int*) malloc( size * sizeof(int) );
}
int main() {
int* ptr;
newArray(ptr, 10);
}
接著就會找了很久的 bug,最後仍然搞不懂為什麼 ptr 沒有指向剛剛拿到的合法空間
讓我們再回顧一次,並且用圖表示
______________
1. int* ptr; ptr -> |__未知的空間__|
______________
2. 呼叫函式 newArray ptr -> |__未知的空間__| <- local
______________
3. malloc 取得合法空間 ptr -> |__未知的空間__|
______________
|___合法空間___| <- local
______________
4. 離開函式 ptr -> |__未知的空間__|
用圖看應該一切就都明白了,我也不需冗言解釋
也許有人會想問,指標不是傳址嗎?
精確來講,指標也是傳值,只不過該值是一個位址 (ex: 0xfefefefe)
local 接到了 ptr 指向的那個位置,接著函式內 local 要到了新的位置
但是 ptr 指向的位置還是沒變的,因此離開函式後就好像事什麼都沒發生
( 嚴格說起來還發生了 memory leak )
以下是一種解決辦法
int* createNewArray(int size) {
return (int*) malloc( size * sizeof(int) );
}
int main() {
int* ptr;
ptr = createNewArray(10);
}
改成這樣亦可 ( 為何用 int** 就可以?想想他會傳什麼過去給local )
void createNewArray(int** local, int size) {
*local = (int*) malloc( size * sizeof(int) );
}
int main() {
int *ptr;
createNewArray(&ptr, 10);
}
如果是 C++,別忘了可以善用 Reference
void newArray(int*& local, int size) {
local = new int[size];
}
後記:從「古時候」流傳下來一篇文章
"The Ten Commandments for C Programmers"(Annotated Edition)
by Henry Spencer
http://www.lysator.liu.se/c/ten-commandments.html
一方面它不是針對 C 的初學者,一方面它特意模仿中古英文
聖經的用語,寫得文謅謅。所以我現在另外寫了這篇,希望
能涵蓋最重要的觀念以及初學甚至老手最易犯的錯誤。
作者:潘科元(Khoguan Phuann) (c)2005. 感謝 ptt.cc BBS 的 C_and_CPP
看板眾多網友提供寶貴意見及程式實例。
nowar100 多次加以修改整理,擴充至 13 項,並且製作成動畫版。
如發現 Bug 請推文回報,謝謝您!
--
※ 發信站: 批踢踢實業坊(ptt.cc)
推 cosmosQQ:第8戒 int i = 7; int j = ++i + i++; 蠻多公司有這考題 09/26 11:00
→ cosmosQQ:printf("i=%d, j=%d \n", i, j); => i=9, j=16 09/26 11:00
→ cosmosQQ:一般推論答案無誤, 但就與第8戒違反了@@ 該如何解.... 09/26 11:01
→ nowar100:問題不存在 不應該出現的就是不應該 就算大家約定成俗 09/26 13:06
→ nowar100:討論它也是沒什麼意義的 09/26 13:07
※ 編輯: nowar100 來自: 140.112.30.82 (10/08 13:53)
→ wawi2:第13點的最後一個例子是不是錯了? int (&array)[10]?? 06/22 00:14
標題 [重要] 發文前務必閱讀:常見問題十三誡
C 語言新手十誡(The Ten Commandments for Newbie C Programmers)
by Khoguan Phuann
請注意:
(1) 本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。
但不能取代完整的學習,請自己好好研讀一兩本 C 語言的好書,
並多多實作練習。
(2) 強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並
解答了。
(3) 以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果,
那只是不足為恃的一時僥倖。
(4) 不守十誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則
引爆核彈、毀滅地球(如果你的 C 程式是用來控制核彈發射器的話)。
████████ 請選擇相對應的數字鍵,或按 End 結束播放 ████████
1. 不可以使用尚未給予適當初值的變數
2. 不能存取超過陣列既定範圍的空間
3. 不可以提取不知指向何方的指標
4. 不要試圖用 char* 去更改一個"字串常數"
5. 不能在函式中回傳一個指向區域性自動變數的指標
6. 不可以只做 malloc(), 而不做相應的 free()
7. 在數值運算、賦值或比較中不可以隨意混用不同型別的數值
8. 在一個運算式中,不能對一個基本型態的變數修改其值超過一次以上
9. 在 Macro 定義中, 務必為它的參數個別加上括號
A(10). 不可以在 stack 設置過大的變數
B(11). 使用浮點數精確度造成的誤差問題
C(12). 不要猜想二維陣列可以用 pointer to pointer 來傳遞
D(13). 函式內 new 出來的空間記得要讓主程式的指標接住
01. 你不可以使用尚未給予適當初值的變數
錯誤例子:
int accumulate(int max) /* 從 1 累加到 max,傳回結果 */
{
int sum; /* 未給予初值的區域變數,其內容值是垃圾 */
int num;
for (num = 1; num <= max; num++) { sum += num; }
return sum;
}
正確例子:
int accumulate(int max)
{
int sum = 0; /* 正確的賦予適當的初值 */
int num;
for (num = 1; num <= max; num++) { sum += num; }
return sum;
}
02. 你不可以存取超過陣列既定範圍的空間
錯誤例子:
int str[5];
int i;
for (i = 0 ; i <= 5 ; i++) str[i] = i;
正確例子:
int str[5];
int i;
for (i = 0; i < 5; i++) str[i] = i;
說明:宣告陣列時,所給的陣列元素個數值如果是 N, 那麼我們在後面
透過 [索引值] 存取其元素時,所能使用的索引值範圍是從 0 到 N-1
C/C++ 為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界,
我們要自己來確保不會越界。一旦越界,操作的不再是合法的空間,
將導致無法預期的後果。
03. 你不可以提取(dereference)不知指向何方的指標(包含 null 指標)。
錯誤例子:
char *pc1; /* 未給予初值,不知指向何方 */
char *pc2 = 0; /* pc2 起始化為 null pointer */
*pc1 = 'a'; /* 將 'a' 寫到不知何方,錯誤 */
*pc2 = 'b'; /* 將 'b' 寫到「位址0」,錯誤 */
正確例子:
char c; /* c 的內容尚未起始化 */
char *pc1 = &c; /* pc1 指向字元變數 c */
*pc1 = 'a'; /* c 的內容變為 'a' */
/* 動態分配 10 個 char(其值未定),並將第一個char的位址賦值給 pc2 */
char *pc2 = (char *) malloc(10);
pc2[0] = 'b'; /* 動態配置來的第 0 個字元,內容變為 'b'
free(pc2);
說明:指標變數必需先指向某個可以合法操作的空間,才能進行操作。
( 使用者記得要檢查 malloc 回傳是否為 NULL,
礙於篇幅本文假定使用上皆合法,也有正確歸還記憶體 )
錯誤例子:
char *name; /* name 尚未指向有效的空間 */
printf("Your name, please: ");
gets(name); /* 您確定要寫入的那塊空間合法嗎??? */
printf("Hello, %s\n", name);
正確例子:
/* 如果編譯期就能決定字串的最大空間,那就不要宣告成 char* 改用 char[] */
char name[21]; /* 可讀入字串最長 20 個字元,保留一格空間放 '\0' */
printf("Your name, please: ");
gets(name);
printf("Hello, %s\n", name);
正確例子(2):
/* 若是在執行時期才能決定字串的最大空間,則需利用 malloc() 函式來動態
分配空間 */
size_t length;
char *name;
printf("請輸入字串的最大長度(含null字元): ");
scanf("%u", &length);
name = (char *)malloc(length);
printf("Your name, please: ");
scanf("%s", name);
printf("Hello, %s\n", name);
/* 最後記得 free() 掉 malloc() 所分配的空間 */
free(name);
04. 你不可以試圖用 char* 去更改一個"字串常數"
錯誤例子:
char* pc = "john"; /* pc 現在指著一個字串常數 */
*pc = 'J'; /* 但是 pc 沒有權利去更改這個常數! */
正確例子:
char pc[] = "john"; /* pc 現在是個合法的陣列,裡面住著字串 john */
/* 也就是 pc[0]='j', pc[1]='o', pc[2]='h',
pc[3]='n', pc[4]='\0' */
*pc = 'J';
pc[2] = 'H';
說明:字串常數的內容是"唯讀"的。您有使用權,但是沒有更改的權利。
若您希望使用可以更改的字串,那您應該將其放在合法空間
錯誤例子:
char *s1 = "Hello, ";
char *s2 = "world!";
/* strcat() 不會另行配置空間,只會將資料附加到 s1 所指唯讀字串的後面,
造成寫入到程式無權碰觸的記憶體空間 */
strcat(s1, s2);
正確例子(2):
/* s1 宣告成陣列,並保留足夠空間存放後續要附加的內容 */
char s1[20] = "Hello, ";
char *s2 = "world!";
/* 因為 strcat() 的返回值等於第一個參數值,所以 s3 就不需要了 */
strcat(s1, s2);
05. 你不可以在函式中回傳一個指向區域性自動變數的指標。否則,會得到垃圾值
[感謝 gocpp 網友提供程式例子]
錯誤例子:
char *getstr(char *name)
{
char buf[30] = "hello, "; /*將字串常數"hello, "的內容複製到buf陣列*/
strcat(buf, name);
return buf;
}
說明:區域性自動變數,將會在離開該區域時(本例中就是從getstr函式返回時)
被消滅,因此呼叫端得到的指標所指的字串內容就失效了。
正確例子:
void getstr(char buf[], int buflen, char const *name)
{
char const s[] = "hello, ";
strcpy(buf, s);
strcat(buf, name);
}
正確例子:
int* foo()
{
int* pInteger = (int*) malloc( 10*sizeof(int) );
return pInteger;
}
int main()
{
int* pFromfoo = foo();
}
說明:上例雖然回傳了函式中的指標,但由於指標內容所指的位址並非區域變數,
而是用動態的方式抓取而得,換句話說這塊空間是長在 heap 而非 stack,
又因 heap 空間並不會自動回收,因此這塊空間在離開函式後,依然有效
(但是這個例子可能會因為 programmer 的疏忽,忘記 free 而造成
memory leak)
[針對字串操作,C++提供了更方便安全更直觀的 string class, 能用就盡量用]
正確例子:
#include <string> /* 並非 #include <cstring> */
using std::string;
string getstr(string const &name)
{
return string("hello, ") += name;
}
06. 你不可以只做 malloc(), 而不做相應的 free(). 否則會造成記憶體漏失
但若不是用 malloc() 所得到的記憶體,則不可以 free()。已經 free()了
所指記憶體的指標,在它指向另一塊有效的動態分配得來的空間之前,不可
以再被 free(),也不可以提取(dereference)這個指標。
[C++] 你不可以只做 new, 而不做相應的 delete
注:new 與 delete 對應,new[] 與 delete[] 對應,不可混用
切記,做了幾次 new,就必須做幾次 delete
小技巧: 可在 delete 之後將指標指到 0,由於 delete 本身會先做檢查,
因此可以避免掉多次 delete 的錯誤
正確例子:
int *ptr = new int(99);
delete ptr;
ptr = NULL;
delete ptr; /* delete 只會處理指向非 NULL 的指標 */
07. 你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考
慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。必須隨時注意數值運算
的結果,其範圍是否會超出變數的型別
錯誤例子:
unsigned int sum = 2000000000 + 2000000000; /* 超出 int 存放範圍 */
unsigned int sum = (unsigned int) (2000000000 + 2000000000);
double f = 10 / 3;
正確例子:
/* 全部都用 unsigned int, 注意數字後面的 u, 大寫 U 也成 */
unsigned int sum = 2000000000u + 2000000000u;
/* 或是用顯式的轉型 */
unsigned int sum = (unsigned int) 2000000000 + 2000000000;
double f = 10.0 / 3.0;
錯誤例子:
unsigned int a = 0;
int b[10];
for(int i = 9 ; i >= a ; i--) { b[i] = 0; }
說明:由於 int 與 unsigned 共同運算的時候,會提升 int 為 unsigned,
因此迴圈條件永遠滿足,與預期行為不符
錯誤例子: (感謝 sekya 網友提供)
unsigned char a = 0x80; /* no problem */
char b = 0x80; /* implementation-defined result */
if( b == 0x80 ) { /* 不一定恒真 */
printf( "b ok\n" );
}
說明:語言並未規定 char 天生為 unsigned 或 signed,因此將 0x80 放入
char 型態的變數,將會視各家編譯器不同作法而有不同結果
08. 你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值
超過一次以上。否則,將導致未定義的行為(undefined behavior)
錯誤例子:
int i = 7;
int j = ++i + i++;
正確例子:
int i = 7;
int j = ++i;
j += i++;
你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,
而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的,
是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。
錯誤例子:
x = x++;
錯誤例子:
int arr[5];
int i = 0;
arr[i] = i++;
正確例子:
int arr[5];
int i = 0;
arr[i] = i;
i++;
錯誤例子:
int i = 10;
cout << i << "==" << i++;
正確例子:
int i = 10;
cout << i << "==";
cout << i++;
錯誤例子:
int Integer=10;
printf( "%d %d %d", Integer++, Integer++, Integer++ );
錯誤例子:
void foo(int a, int b) { ... }
int main() {
int i=0;
foo(i++, i++);
}
說明: C/C++ 並沒有強制規定參數會由哪個方向開始處理(不像Java是由左到右),
因此可能會造成與預期不符的情況
09. 在 Macro 定義中, 務必為它的參數個別加上括號
錯誤例子:
#include <stdio.h>
#define SQUARE(x) (x * x)
int main()
{
printf("%d\n", SQUARE(10-5));
return 0;
}
正確例子:
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
int main()
{
printf("%d\n", SQUARE(10-5));
return 0;
}
說明:如果是用 C++, 請多多利用 inline function 來取代上述的 macro,
以免除 macro 定義的種種危險性。如:
inline int square(int x) { return x * x; }
macro 定義出的「偽函式」至少缺乏下列數項函式本有的能力:
(1) 無法進行參數型別的檢查。
(2) 無法遞迴呼叫。
(3) 無法用 & 加在 macro name 之前,取得函式位址。
(4) 呼叫時往往不能使用具有 side effect 的引數。例如:
錯誤例子: (感謝 yaca 網友提供)
#define MACRO(x) (((x) * (x)) - ((x) * (x)))
int main()
{
int x = 3;
printf("%d\n", MACRO(++x));
return 0;
}
10. 不可在 stack 設置過大的變數,否則會造成 stack overflow
(感謝 VictorTom 版友幫忙)
錯誤例子:
int array[10000000]; // 僅舉例說明
說明:由於編譯器會自行決定 stack 的上限,某些預設是數 KB 或數十 KB,
當變數所需的空間過大時,很容易造成 stack overflow,程式亦隨之
當掉,若真正需要如此大的空間,那麼建議配置在 heap 上,或是採用
static / globla variable,亦或是改變編譯器的設定
使用 heap 時,雖然整個 process 可用的空間是有限的,但採用動態抓取
的方式,new 無法配置時會丟出 std::bad_alloc 例外,malloc 無法配置
時會回傳 null,不會影響到正常使用下的程式功能
正確例子:
int *array = (int*) malloc( 10000000*sizeof(int) );
說明:由於此時 stack 上只需配置一個 int* 的空間,可避免 stack overflow
更多說明請參考精華區 z-10-13
11. 使用浮點數千萬要注意精確度所造成的誤差問題
根據 IEEE 754 的規範,又電腦中是用有限的二進位儲存數字,因此常有可
能因為精確度而造成誤差,例如加減乘除,等號大小判斷,分配律等數學上
常用到的操作,很有可能因此而出錯(不成立)
更詳細的說明可以參考精華區 z-8-11
或參考冼鏡光老師所發表的一文 "使用浮點數最最基本的觀念"
http://blog.dcview.com/article.php?a=VmgBZFE5AzI%3D
12. 不要猜想二維陣列可以用 pointer to pointer 來傳遞
(感謝 loveme00835 legnaleurc 版友的幫忙)
首先必須有個觀念,C 語言中陣列是無法直接拿來傳遞的!
不過這時候會有人跳出來反駁:
void pass1DArray( int array[] );
int a[10];
pass1DArray( a ); /* 可以合法編譯,而且執行結果正確!! */
事實上,編譯器會這麼看待
void pass1DArray( int *array );
int a[10];
pass1DArray( &a[0] );
我們可以順便看出來,array 變數本身可以 decay 成記憶體起頭的位置
因此我們可以 int *p = a; 這種方式,拿指標去接陣列。
也因為上述的例子,許多人以為那二維陣列是不是也可以改成 int **
錯誤例子:
void pass2DArray( int **array );
int a[5][10];
pass2DArray( a );
/* 這時候編譯器就會報錯啦 */
/* expected ‘int **’ but argument is of type ‘int (*)[10]’*/
在一維陣列中,指標的移動操作,會剛好覆蓋到陣列的範圍
例如,宣告了一個 a[10],那我可以把 a 當成指標來操作 *a 至 *(a+9)
因此我們可以得到一個概念,在操作的時候,可以 decay 成指標來使用
也就是我可以把一個陣列當成一個指標來使用 (again, 陣列!=指標)
但是多維陣列中,無法如此使用,事實上這也很直觀,試圖拿一個
pointer to pointer to int 來操作一個 int 二維陣列,這是不合理的!
^L#@N,f+1,下一頁#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
儘管我們無法將二維陣列直接 decay 成兩個指標,但是我們可以換個角度想,
二維陣列可以看成 "外層大的一維陣列,每一維內層各又包含著一維陣列"
如果想通了這一點,我們可以仿造之前的規則,
把外層大的一維陣列 decay 成指標,該指標指向內層的一維陣列
void pass2DArray( int (*array) [10] ); // array 是個指標,指向 int [10]
int a[5][10];
pass2DArray( a );
這時候就很好理解了,函數 pass2DArray 內的 array[0] 會代表什麼呢?
答案是它代表著 a[0] 外層的那一維陣列,裡面包含著內層 [0]~[9]
也因此 array[0][2] 就會對應到 a[0][2],array[4][9] 對應到 a[4][9]
結論就是,只有最外層的那一維陣列可以 decay 成指標,其他維陣列都要
明確的指出陣列大小,這樣多維陣列的傳遞就不會有問題了
也因為剛剛的例子,我們可以清楚的知道在傳遞陣列時,實際行為是在傳遞
指標,也因此如果我們想用 sizeof 來求得陣列元素個數,那是不可行的
錯誤例子:
void print1DArraySize( int* arr ) {
printf("%u", sizeof(arr)/sizeof(arr[0])); /* sizeof(arr) 只是 */
} /* 一個指標的大小 */
受此限制,我們必須手動傳入大小
void print1DArraySize( int* arr, size_t arrSize );
C++ 提供 reference 的機制,使得我們不需再這麼麻煩,
可以直接傳遞陣列的 reference 給函數,大小也可以直接求出
正確例子:
void print1DArraySize( int (&array)[10] ) { // 傳遞 reference
cout << sizeof(array) / sizeof(int); // 正確取得陣列元素個數
}
13. 函式內 new 出來的空間記得要讓主程式的指標接住
對指標不熟悉的使用者會以為以下的程式碼是符合預期的
void newArray(int* local, int size) {
local = (int*) malloc( size * sizeof(int) );
}
int main() {
int* ptr;
newArray(ptr, 10);
}
接著就會找了很久的 bug,最後仍然搞不懂為什麼 ptr 沒有指向剛剛拿到的合法空間
讓我們再回顧一次,並且用圖表示
______________
1. int* ptr; ptr -> |__未知的空間__|
______________
2. 呼叫函式 newArray ptr -> |__未知的空間__| <- local
______________
3. malloc 取得合法空間 ptr -> |__未知的空間__|
______________
|___合法空間___| <- local
______________
4. 離開函式 ptr -> |__未知的空間__|
用圖看應該一切就都明白了,我也不需冗言解釋
也許有人會想問,指標不是傳址嗎?
精確來講,指標也是傳值,只不過該值是一個位址 (ex: 0xfefefefe)
local 接到了 ptr 指向的那個位置,接著函式內 local 要到了新的位置
但是 ptr 指向的位置還是沒變的,因此離開函式後就好像事什麼都沒發生
( 嚴格說起來還發生了 memory leak )
以下是一種解決辦法
int* createNewArray(int size) {
return (int*) malloc( size * sizeof(int) );
}
int main() {
int* ptr;
ptr = createNewArray(10);
}
改成這樣亦可 ( 為何用 int** 就可以?想想他會傳什麼過去給local )
void createNewArray(int** local, int size) {
*local = (int*) malloc( size * sizeof(int) );
}
int main() {
int *ptr;
createNewArray(&ptr, 10);
}
如果是 C++,別忘了可以善用 Reference
void newArray(int*& local, int size) {
local = new int[size];
}
後記:從「古時候」流傳下來一篇文章
"The Ten Commandments for C Programmers"(Annotated Edition)
by Henry Spencer
http://www.lysator.liu.se/c/ten-commandments.html
一方面它不是針對 C 的初學者,一方面它特意模仿中古英文
聖經的用語,寫得文謅謅。所以我現在另外寫了這篇,希望
能涵蓋最重要的觀念以及初學甚至老手最易犯的錯誤。
作者:潘科元(Khoguan Phuann) (c)2005. 感謝 ptt.cc BBS 的 C_and_CPP
看板眾多網友提供寶貴意見及程式實例。
nowar100 多次加以修改整理,擴充至 13 項,並且製作成動畫版。
如發現 Bug 請推文回報,謝謝您!
--
※ 發信站: 批踢踢實業坊(ptt.cc)
推 cosmosQQ:第8戒 int i = 7; int j = ++i + i++; 蠻多公司有這考題 09/26 11:00
→ cosmosQQ:printf("i=%d, j=%d \n", i, j); => i=9, j=16 09/26 11:00
→ cosmosQQ:一般推論答案無誤, 但就與第8戒違反了@@ 該如何解.... 09/26 11:01
→ nowar100:問題不存在 不應該出現的就是不應該 就算大家約定成俗 09/26 13:06
→ nowar100:討論它也是沒什麼意義的 09/26 13:07
※ 編輯: nowar100 來自: 140.112.30.82 (10/08 13:53)
→ wawi2:第13點的最後一個例子是不是錯了? int (&array)[10]?? 06/22 00:14
2014年3月4日 星期二
C&C++: i++ ++i
By adrianshum from telnet://ptt.cc 看板 C_and_CPP
i++ 是 做完遞增後, 回傳 遞增 *前* 的值
++i 則是做完遞增後, 回傳遞增 *後* 的值
你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值超過一次以上。否則,將導致未定義的行為(undefined behavior)
錯誤例子:
int i = 7;
int j = ++i + i++;
正確例子:
int i = 7;
int j = ++i;
j += i++;
你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的,是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。
錯誤例子:
x = x++;
錯誤例子:
int arr[5];
int i = 0;
arr[i] = i++;
正確例子:
int arr[5];
int i = 0;
arr[i] = i;
i++;
錯誤例子:
int i = 10;
cout << i << "==" << i++;
正確例子:
int i = 10;
cout << i << "==";
cout << i++;
錯誤例子:
int Integer=10;
printf( "%d %d %d", Integer++, Integer++, Integer++ );
錯誤例子:
void foo(int a, int b) { ... }
int main() {
int i=0;
foo(i++, i++);
}
說明: C/C++ 並沒有強制規定參數會由哪個方向開始處理(不像Java是由左到右),因此可能會造成與預期不符的情況
i++ 是 做完遞增後, 回傳 遞增 *前* 的值
++i 則是做完遞增後, 回傳遞增 *後* 的值
你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值超過一次以上。否則,將導致未定義的行為(undefined behavior)
錯誤例子:
int i = 7;
int j = ++i + i++;
正確例子:
int i = 7;
int j = ++i;
j += i++;
你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的,是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。
錯誤例子:
x = x++;
錯誤例子:
int arr[5];
int i = 0;
arr[i] = i++;
正確例子:
int arr[5];
int i = 0;
arr[i] = i;
i++;
錯誤例子:
int i = 10;
cout << i << "==" << i++;
正確例子:
int i = 10;
cout << i << "==";
cout << i++;
錯誤例子:
int Integer=10;
printf( "%d %d %d", Integer++, Integer++, Integer++ );
錯誤例子:
void foo(int a, int b) { ... }
int main() {
int i=0;
foo(i++, i++);
}
說明: C/C++ 並沒有強制規定參數會由哪個方向開始處理(不像Java是由左到右),因此可能會造成與預期不符的情況
2012年4月27日 星期五
C&C++: 強迫float做低階運算的方法
強迫float做低階運算的方法
float a, b;
long i;
a=0.1;
b=0.2;
i=*(long*)&a^*(long*)&b;
a=*(float*)&i;
i=*(long*)&a^*(long*)&b;
b=*(float*)&i;
i=*(long*)&a^*(long*)&b;
a=*(float*)&i;
=========================================
float a=4.3,b=2.4;
*(long*)&a = *(long*)&a ^ *(long*)&b;
*(long*)&b = *(long*)&b ^ *(long*)&a;
*(long*)&a = *(long*)&a ^ *(long*)&b;
cout << a << ", " << b << endl ;
float a, b;
long i;
a=0.1;
b=0.2;
i=*(long*)&a^*(long*)&b;
a=*(float*)&i;
i=*(long*)&a^*(long*)&b;
b=*(float*)&i;
i=*(long*)&a^*(long*)&b;
a=*(float*)&i;
=========================================
float a=4.3,b=2.4;
*(long*)&a = *(long*)&a ^ *(long*)&b;
*(long*)&b = *(long*)&b ^ *(long*)&a;
*(long*)&a = *(long*)&a ^ *(long*)&b;
cout << a << ", " << b << endl ;
2012年4月22日 星期日
C&C++: Re: [問題] 請教如何只用x,y兩變數來交換彼此數值
作者 LPH66 (-858993460) 看板 C_and_CPP
標題 Re: [問題] 請教如何只用x,y兩變數來交換彼此數值
時間 Mon Apr 11 23:40:29 2011
───────────────────────────────────────
: → littleshan:不要這樣寫!這是 implementation-defined behavior 04/11 15:48
: → littleshan:就是十誡之八啦 04/11 15:49
基本上這個寫法的確是 implementation-defined behavior
不管有沒有加括號都一樣
事情是這樣的
a ^= b ^= a ^= b;
1 2 3 4 5 6 7
這樣寫的人想要做的事的順序是
值為
(7) 取 b 的值 原來的 b
(5) 取 a 的值 原來的 a
(6) 做 (5)^(7) 並把值放回 a 去 原來的 a^b
(3) 取 b 的值 原來的 b
(4) 做 (3)^(6) 並把值放回 b 去 原來的 a
(1) 取 a 的值 原來的 a^b
(2) 做 (1)^(4) 並把值放回 a 去 原來的 b
但有些 compiler (如我手上的 g++ 4.1.0 for win) 會編成這個順序
值為
(1) 取 a 的值 原來的 a
(3) 取 b 的值 原來的 b
(5) 取 a 的值 原來的 a
(7) 取 b 的值 原來的 b
(6) 做 (5)^(7) 並把值放回 a 去 原來的 a^b
(4) 做 (3)^(6) 並把值放回 b 去 原來的 a
(2) 做 (1)^(4) 並把值放回 a 去 一定是 0
結果是原來 a 的值在 b 但 a 卻變成 0 了
問題在於
雖然這個式子的確是分析成
^= (2)
/ \
a ^= (4)
(1) / \
b ^= (6)
(3) / \
a b
(5) (7)
這個樣子 但在計算 a ^= b 時 a b 取值的順序卻是 implementation-defined
也就是在上面那棵語法樹的後序走訪中先走左子樹還是先走右子樹
如果先走左子樹 就是 1 3 5 7 6 4 2 的順序
先走右子樹 就是 7 5 6 3 4 1 2 的順序
而這兩個順序 上面我也寫出來了
後者是順利交換沒錯 前者卻是把 a 搬到 b 後把 a 炸成 0 !
所以結論是千萬不要用這個寫法
---
其實簡單一句話就是十三誡之八裡說的
你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值
超過一次以上。否則,將導致未定義的行為(undefined behavior)
如果真要用 xor 來交換 乖乖寫三條
x ^= y; y ^= x; x ^= y;
就什麼事都沒有....
當然最好的寫法還是 int temp = x; x = y; y = temp; 就是了
--
'You've sort of made up for it tonight,' said Harry. 'Getting the
sword. Finishing the Horcrux. Saving my life.'
'That makes me sound a lot cooler then I was,' Ron mumbled.
'Stuff like that always sounds cooler then it really was,' said
Harry. 'I've been trying to tell you that for years.'
-- Harry Potter and the Deathly Hollows, P.308
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.112.30.142
推 attomahawk:推薦這棵 語法樹! 04/12 00:02
推 glennchen:好強大 04/12 00:07
推 loveme00835:不管多炫, 在可讀性上永遠是個失敗品, 是禁忌 04/12 00:09
推 james732:原來如此! 04/12 00:12
推 VictorTom:推語法樹:) 04/12 00:16
→ bibo9901:借問一下, 那寫成 a+=b; b=a-b; a-=b; 可以嗎 04/12 00:39
推 purpose:有緩衝區溢位的風險,如 a = INT_MAX; b = 1; 04/12 00:41
→ purpose:沒有緩衝區,打太快了 04/12 00:41
推 littleshan:overflow 是 undefined behavior 04/12 00:43
推 xatier:語法樹! 推 04/12 07:36
推 wanwan2:讚!! 04/12 08:40
推 genghiskii:3F正解 04/12 10:08
推 j094097:大推 04/12 21:23
推 yauhh:都忘了這件事,果然imperative就怕太依賴state 04/12 21:40
推 h520:用樹講解好清楚啊!! 大推 04/14 13:42
2012年3月13日 星期二
C&C++: [問題] 新手發問"!!"的意思
!!
節錄部份
作者 Barros (Leila) 站內 Programming
標題 [問題] 新手發問 "!!"的意思
時間 Thu Mar 8 21:11:04 2012
───────────────────────────────────────
看資料結構的書的時候,應該是用c寫的吧
發現code裡出現 !! 這個符號
煩請解惑感激不盡~
節錄部份code:
BinQueue
Merge(BinQueue H1, BinQueue H2)
{
Bintree T1, T2, Carry=Null;
int i j;
if(H1->CurrentSize +H2->CurrentSize >Capacity)
error("Merge would exceed capacity");
H1->CurrentSize += H2->CurrentSize;
for(i=0, j=1; j<= H2->CurrentSize; i++, j*=2)
}
T1=H1->TheTrees[i];T2=H2->TheTrees[i];
switch( !!T1 + 2 * !! T2 + 4 * !! Carry)
{
case 0: /*no trees*/
case 1: /*only H1*/
break;
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 219.84.2.1
→ Barros:因為google過了也找不到 只好來這裡發問了 219.84.2.1 03/08 21:12
→ tropical72:是長得像這樣嗎?int a=5; if(!!a).. 123.195.165.40 03/08 21:13
→ tropical72:簡單的說,a是非0的話,!!a會傳回1;否則 123.195.165.40 03/08 21:14
→ tropical72:傳回0。 123.195.165.40 03/08 21:14
※ 編輯: Barros 來自: 219.84.2.1 (03/08 21:29)
→ s66449:加了兩個not 不就等同互相抵消了?? 114.27.187.150 03/08 22:13
→ s66449:有加和沒加是一樣的? 114.27.187.150 03/08 22:13
→ tropical72:不同唷,a=1,b=5而言 : 123.195.165.40 03/08 22:19
→ tropical72:a+=b; --> a+=5 ---> a=6; 123.195.165.40 03/08 22:20
→ tropical72:a+=!!b ---> a+=!(!5)-->a+=!0-->a+=1 123.195.165.40 03/08 22:20
→ tropical72:--> a=2; 123.195.165.40 03/08 22:20
→ tropical72:(T1!=0)|((T2!=0)<<1)|((C!=0)<<2) 123.195.165.40 03/08 22:26
→ Barros:了解了,謝謝 219.84.58.43 03/08 22:47
→ MOONRAKER:真是神祕。 61.231.8.206 03/09 08:46
推 yauhh:一個!將整數變成布爾值,但邏輯意思是反的, 180.206.52.95 03/09 18:42
→ yauhh:再加!就回來了. 180.206.52.95 03/09 18:43
→ yauhh:他做二件事,一是將整數對應為0,1,二是將0,1 218.160.108.73 03/09 21:56
→ yauhh:(true,false)反過來當作數字來用. 但例子中 218.160.108.73 03/09 21:57
→ yauhh:看起來作法好怪,邏輯運算當數字運算用,數字 218.160.108.73 03/09 21:57
→ yauhh:運算卻當位元運算用. 218.160.108.73 03/09 21:58
作者 LPH66 (-858993460) 看板 Programming
───────────────────────────────────────
這是懶人用switch...
!! 是做兩次 boolean not
所以非零的會變成 1 是零的就還是 0
於是 !!T1 + 2 * !! T2 + 4 * !! Carry 這個算式
將三件事 (T1 != 0, T2 != 0, Carry != 0) 編碼成一個整數
若三者都為 0 則它會算出二進位的 000 = 十進位 0
若只有 T1 非 0 則它會算出二進位的 001 = 十進位 1
若只有 T2 非 0 010 = 十進位 2
若只有 Carry 非 0 100 = 十進位 4
等等
這樣就能以 switch 一次判斷三個條件的真假
你可以注意到這個 switch 裡的 case 有註解寫說這是什麼情況
就是這麼來的
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.112.28.91
→ popcorny:我是覺得最起碼要用Macro包一下 114.32.239.120 03/11 14:20
→ popcorny:#define ISTRUE(x) (!!(x)) 114.32.239.120 03/11 14:21
→ popcorny:可讀性跟你說的其實沒有那麼衝突 114.32.239.120 03/11 14:22
作者 adrianshum (Alien) 看板 Programming
───────────────────────────────────────
不是覺得這種精簡寫法一定有錯,但同樣做法,真的要寫得那麼
非直覺嗎?為什麼不可以寫成:
int t = 0;
if (T1) t |= 1;
if (T2) t |= 2; // 或寫成 t |= (1<<1);
if (Carry) t |= 4; // t |= (1 << 2)
switch (t) {
//..
}
再不然,寫成
int t = (T1? 1 : 0) | (T2 ? 2 : 0) | (Carry ? 4 : 0);
(以上 bitwise OR 可改用 +,看你自己喜好)
這樣單從 code 表面上就看得出是根據 T1, T2, Carry 來 turn-on
各個 bit。除非真的程式慢得連這一點點 performance 都要壓搾,
不然至少對我來說,上面的寫法比 !!T1 + 2 * !! T2 + 4 * !! Carry
看起來合理得多了
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 223.19.47.36
節錄部份
作者 Barros (Leila) 站內 Programming
標題 [問題] 新手發問 "!!"的意思
時間 Thu Mar 8 21:11:04 2012
───────────────────────────────────────
看資料結構的書的時候,應該是用c寫的吧
發現code裡出現 !! 這個符號
煩請解惑感激不盡~
節錄部份code:
BinQueue
Merge(BinQueue H1, BinQueue H2)
{
Bintree T1, T2, Carry=Null;
int i j;
if(H1->CurrentSize +H2->CurrentSize >Capacity)
error("Merge would exceed capacity");
H1->CurrentSize += H2->CurrentSize;
for(i=0, j=1; j<= H2->CurrentSize; i++, j*=2)
}
T1=H1->TheTrees[i];T2=H2->TheTrees[i];
switch( !!T1 + 2 * !! T2 + 4 * !! Carry)
{
case 0: /*no trees*/
case 1: /*only H1*/
break;
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 219.84.2.1
→ Barros:因為google過了也找不到 只好來這裡發問了 219.84.2.1 03/08 21:12
→ tropical72:是長得像這樣嗎?int a=5; if(!!a).. 123.195.165.40 03/08 21:13
→ tropical72:簡單的說,a是非0的話,!!a會傳回1;否則 123.195.165.40 03/08 21:14
→ tropical72:傳回0。 123.195.165.40 03/08 21:14
※ 編輯: Barros 來自: 219.84.2.1 (03/08 21:29)
→ s66449:加了兩個not 不就等同互相抵消了?? 114.27.187.150 03/08 22:13
→ s66449:有加和沒加是一樣的? 114.27.187.150 03/08 22:13
→ tropical72:不同唷,a=1,b=5而言 : 123.195.165.40 03/08 22:19
→ tropical72:a+=b; --> a+=5 ---> a=6; 123.195.165.40 03/08 22:20
→ tropical72:a+=!!b ---> a+=!(!5)-->a+=!0-->a+=1 123.195.165.40 03/08 22:20
→ tropical72:--> a=2; 123.195.165.40 03/08 22:20
→ tropical72:(T1!=0)|((T2!=0)<<1)|((C!=0)<<2) 123.195.165.40 03/08 22:26
→ Barros:了解了,謝謝 219.84.58.43 03/08 22:47
→ MOONRAKER:真是神祕。 61.231.8.206 03/09 08:46
推 yauhh:一個!將整數變成布爾值,但邏輯意思是反的, 180.206.52.95 03/09 18:42
→ yauhh:再加!就回來了. 180.206.52.95 03/09 18:43
→ yauhh:他做二件事,一是將整數對應為0,1,二是將0,1 218.160.108.73 03/09 21:56
→ yauhh:(true,false)反過來當作數字來用. 但例子中 218.160.108.73 03/09 21:57
→ yauhh:看起來作法好怪,邏輯運算當數字運算用,數字 218.160.108.73 03/09 21:57
→ yauhh:運算卻當位元運算用. 218.160.108.73 03/09 21:58
作者 LPH66 (-858993460) 看板 Programming
───────────────────────────────────────
這是懶人用switch...
!! 是做兩次 boolean not
所以非零的會變成 1 是零的就還是 0
於是 !!T1 + 2 * !! T2 + 4 * !! Carry 這個算式
將三件事 (T1 != 0, T2 != 0, Carry != 0) 編碼成一個整數
若三者都為 0 則它會算出二進位的 000 = 十進位 0
若只有 T1 非 0 則它會算出二進位的 001 = 十進位 1
若只有 T2 非 0 010 = 十進位 2
若只有 Carry 非 0 100 = 十進位 4
等等
這樣就能以 switch 一次判斷三個條件的真假
你可以注意到這個 switch 裡的 case 有註解寫說這是什麼情況
就是這麼來的
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.112.28.91
→ popcorny:我是覺得最起碼要用Macro包一下 114.32.239.120 03/11 14:20
→ popcorny:#define ISTRUE(x) (!!(x)) 114.32.239.120 03/11 14:21
→ popcorny:可讀性跟你說的其實沒有那麼衝突 114.32.239.120 03/11 14:22
作者 adrianshum (Alien) 看板 Programming
───────────────────────────────────────
不是覺得這種精簡寫法一定有錯,但同樣做法,真的要寫得那麼
非直覺嗎?為什麼不可以寫成:
int t = 0;
if (T1) t |= 1;
if (T2) t |= 2; // 或寫成 t |= (1<<1);
if (Carry) t |= 4; // t |= (1 << 2)
switch (t) {
//..
}
再不然,寫成
int t = (T1? 1 : 0) | (T2 ? 2 : 0) | (Carry ? 4 : 0);
(以上 bitwise OR 可改用 +,看你自己喜好)
這樣單從 code 表面上就看得出是根據 T1, T2, Carry 來 turn-on
各個 bit。除非真的程式慢得連這一點點 performance 都要壓搾,
不然至少對我來說,上面的寫法比 !!T1 + 2 * !! T2 + 4 * !! Carry
看起來合理得多了
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 223.19.47.36
2012年2月26日 星期日
024 Luba Tang 淺談 GCC 編譯技術 Break Compilation Boundaries with GCC
024 Luba Tang 淺談 GCC 編譯技術 Break Compilation Boundaries with GCC
http://www.youtube.com/watch?v=o1tlfhrkrYQ&feature=player_embedded
http://www.youtube.com/watch?v=o1tlfhrkrYQ&feature=player_embedded
2011年12月20日 星期二
用gdb解決core dumped
在FreeBSD上執行程式./a.out出錯了 Segmentation fault (core dumped)
產生了副檔名為core的檔案: a.out.core
別急著在程式碼中插入print再compile
先試試這個:
gdb a.out a.out.core
詳細用法:
進階gdb
multi-thread與multi-process除錯
http://www.study-area.org/cyril/opentools/opentools/x1265.html
基本gdb
http://www.study-area.org/cyril/opentools/opentools/x1253.html
產生了副檔名為core的檔案: a.out.core
別急著在程式碼中插入print再compile
先試試這個:
gdb a.out a.out.core
詳細用法:
進階gdb
multi-thread與multi-process除錯
http://www.study-area.org/cyril/opentools/opentools/x1265.html
基本gdb
http://www.study-area.org/cyril/opentools/opentools/x1253.html
2011年12月11日 星期日
C++: static_cast, const_cast, reinterpret_cast, dynamic_cast
作者 cppOrz (cppOrz) 看板 C_and_CPP
標題 Re: [問題] static.const.reinterpret_cast和c-sty …
時間 Sun Mar 12 23:26:23 2006
───────────────────────────────────────
Sorry, 有很多地方錯誤,不得不指出來。
熱心回答問題是好的,但是最好對內容有一定的把握,以免誤導。
這一篇介紹 C++ 的四個關鍵字: static_cast, const_cast, reinterpret_cast,
以及 dynamic_cast 的意義用法。
學習任何編程語言,只單純學習語法是不夠的,要適當的運用某種語言機制,最好
要先知道該機制存在的目的,和什麼狀況下會需要用到它,否則還不如不要使用。
※ 引述《godfat (godfat 真常)》之銘言:
: 靜態轉型,簡單地說就是強制轉型,
: 只是不合理的轉型 compiler 會告訴你不能這樣轉
: 例如兩個毫無關聯的物件
: class A{};
: class B{};
: A a;
: static_cast<B>(a); // error
static_cast 用於「相關型別」之間的轉換,其目的是讓編譯器知道,
程序員是「有意識」的進行轉換動作,並非不小心打錯。例如:
float f = 123.456f;
int i = f; // 把 float 轉為 int,編譯器一般會給警告
int v = static_cast<int>(f); // 用 static_cast 通知編譯器,設計意途確實如此
此外 static_cast 也可用於繼承體系內的 downcast,例如:
struct B { void f(); };
struct D : B { void g(); } d;
B &b = d;
...
b.f(); // ok
b.g(); // 錯誤,b 是 reference to B 物件
static_cast<D&>(b).g(); // ok, 因為程序員知道 b 確實是參照某個 D 物件
: : const_cast
: 單純去掉物件的常數性 和/或 volatile 性
這裏是對的。
: int const i = 10;
: i = 5; // error
: const_cast<int&>(i) = 5; // ok
: (這邊我不太確定是不是這樣寫,很少用)
: (總之觀念是這樣)
舉例有誤。
const_cast 只是把常數性去掉,目的是方便設計,但是企圖通過 const_cast
修改一個 const 物件,其結果未定義(視實作環境是否對該 const 物件採取
保護措施)。
至於 const_cast 使用的時機,通常是為了在不修改既有模組(例如沒有 source
code 的程式庫)的情況下,解決兩個模組之間 const-correctness 不相容的問題
(通常是不能改的那個模組,其設計不夠周延)。例如:
struct C { void f(); }; // 假設 C 是一個既存的模組(不能修改)
void foo(C const &c) // foo 是自行設計的模組,c 物件只作為輸入,不會被更動
{
...
c.f(); // 由於 c 是 referece to const C 物件,但 C::f 並未被設計為
// const,因此這樣寫是不成立的。(編譯器會給錯誤或至少警告)
const_cast<C&>(c).f(); // ok, 用 const_cast 去掉 c 的常數性
}
: : reinterpret_cast
: 強迫 compiler 把輸入型別視為欲轉換的型別
: 這個不太好解釋…總之就是暴力轉型就對了 XD
: 直接把該記憶體位置的資料視為欲轉換的型別來看待
reinterpret_cast 幾乎等於暴力轉型(指 C-style 轉型),但不能去掉物件
的常數性(也就是還不夠暴力)。它用來處理任意型別之間的轉換,通常是為
了爭取運算或儲存空間的效率時,所採取的低階操作。例如:
int IP_Addr = 0;
char *p = reinterpret_cast<char*>(&IP_Addr); // 轉型為 char *,以便於
// 以 Byte 為單位來處理
p[0] = 192;
p[1] = 168;
p[2] = 0;
p[3] = 1;
傳統的 C-style 轉型相當於 static_cast, const_cast, reinterpret_cast
三合一(也就是暴力加三級)。
: 你漏了 dynamic_cast<>
: 這跟 static_cast<> 有些類似
dynamic_cast 和 static_cast 無關。
: dynamic_cast<> 只能對於具有多形性型別轉型,
: 也就是他至少得要有一個 virtual function
這裏是對的。dynamic_cast 是 RTTI 的一部份,它用來檢測一個多型基底
類別的 pointer 或 reference 所參照物件的實際型別。例如:
struct B1 { virtual void f() = 0; };
struct D1 { void f(); };
struct X : D1
{
void f();
void g();
};
void foo(B1 *b1) // foo 模組並不知道 b1 所參照物件的實際型別
{
if (dynamic_cast<D1*>(b1)) // 如果 b1 參照的是 D1 物件
{
b1->f();
}
else if (D1 *d1 = dynamic_cast<X*>(b1)) // 如果 b1 參照的是 X 物件
{
d1->g();
}
else
{
...
}
}
此例中,dynamic_cast 的用法也是所謂的 downcast,但和 static_cast
不同的是,前者用於偵測未知多型物件的實際型別,而後者用於對已知物件
(可以是多型物件或普通物件)的轉型。
可以看出,B1, D1, X 的繼承體系設計並不是很理想,因為在這種情況下,應該
直接利用 virtual function 的動態多型機制,而非依賴 dynamic_cast 偵測物
件的型別。例如:
struct B2
{
virtual void f() = 0;
virtual void g() = 0;
};
struct D2 : B2
{
void f();
void g();
} d2;
struct Y : D2
{
void f();
void g();
} y;
void foo()
{
B2 *b2 = &d2; // b2 參照 D2 物件
b2->g(); // 實際上會執行 D2::g,不必依賴 dynamic_cast
b2 = &y; // 換一下,改參照 Y 物件
b2->g(); // 實際上會執行 Y::g
}
顯然,B2, D2, Y 的設計比較合理。事實上,如果所有的模組都是自行設計,
dynamic_cast 是多餘的。但它之所以存在,其目的就是為了解決「既存模組
」不能修改的問題。(最常見的例子,就是缺乏源代碼的程式庫)
前面的例子中,如果 B1, D1 兩模組不能修改,為了新擴充 X 模組,又要和
舊模組相容,dynamic_cast 在此情況下就能派上用場(反過來說,如果沒有
dynamic_cast,問題就會變得很麻煩,而且各種替代方案都不太安全);但
如果完全是可自行控制的設計,就應該儘量遵循 B2, D2, Y 的方式來組織。
以上是 dynamic_cast 的功能中,downcast 的部份。另外,由於 C++ 支援
多重繼承的機制,dynamic_cast 亦可用於 crosscast(橫向轉型),例如:
struct Z : B1, B2 // 多重繼承
{
void f();
void g();
} z;
void foo()
{
B1 *b1 = &z;
B2 *b2 = dynamic_cast<B2*>(b1); // crosscast
}
這個例子可以用來說明,dynamic_cast 實際上的動作是「型別檢測」,而非
僅僅是「轉型」,後者只是它輸出的結果。此例中,b1 表面上是 (B1 *),但
它實際上參照的是 Z 物件,因此 dynamic_cast 偵測的結果,就是它可以順
利轉為 (B2 *) 。
: struct Base{virtual ~Base(){}};
: struct Derived: public Base{};
: Base pb = new Derived;
: Derived pd = dynamic_cast<Derived*>(pb);
: 如果 pb 真的是指向 Derived, 則 dynamic_cast<> 傳回 pb 的地址
: 否的話,pd 為 NULL
: boost 有提供 polymorphic_cast<>, 用處同 dynamic_cast<>
: 差別在於錯誤時不是傳回 NULL, 而是丟出 std::bad_cast
: polymorphic_downcast<> 則是專門用來在已知必然成功的 downcast
: 這種時候內建的 static_cast<> 和 dynamic_cast<> 其實都可以用,
: 只是 static_cast<> 沒有錯誤檢查,dynamic_cast<> 效率太差
: polymorphic_downcast<> 則使用 assert(); 來檢查是否成功
: http://www.boost.org/libs/conversion/cast.htm
: : c-style cast
: 等同於上面全部...
: 在 C++ 中替他們分類,避免造成混淆
: 不過對初學者來說的話這麼多才是混淆吧,我猜 :p
(下略)
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 59.120.214.120
推 godfat:感謝指導,雖然我覺得跟我講的沒啥衝突 XD 大概是太隨便了 03/13 01:44
※ 編輯: cppOrz 來自: 59.120.214.120 (03/13 04:50)
※ crazying:轉錄至看板 NTUGIEE_EDA 03/13 11:58
推 abovelight:推一個~了解更深入了 03/14 18:16
標題 Re: [問題] static.const.reinterpret_cast和c-sty …
時間 Sun Mar 12 23:26:23 2006
───────────────────────────────────────
Sorry, 有很多地方錯誤,不得不指出來。
熱心回答問題是好的,但是最好對內容有一定的把握,以免誤導。
這一篇介紹 C++ 的四個關鍵字: static_cast, const_cast, reinterpret_cast,
以及 dynamic_cast 的意義用法。
學習任何編程語言,只單純學習語法是不夠的,要適當的運用某種語言機制,最好
要先知道該機制存在的目的,和什麼狀況下會需要用到它,否則還不如不要使用。
※ 引述《godfat (godfat 真常)》之銘言:
: 靜態轉型,簡單地說就是強制轉型,
: 只是不合理的轉型 compiler 會告訴你不能這樣轉
: 例如兩個毫無關聯的物件
: class A{};
: class B{};
: A a;
: static_cast<B>(a); // error
static_cast 用於「相關型別」之間的轉換,其目的是讓編譯器知道,
程序員是「有意識」的進行轉換動作,並非不小心打錯。例如:
float f = 123.456f;
int i = f; // 把 float 轉為 int,編譯器一般會給警告
int v = static_cast<int>(f); // 用 static_cast 通知編譯器,設計意途確實如此
此外 static_cast 也可用於繼承體系內的 downcast,例如:
struct B { void f(); };
struct D : B { void g(); } d;
B &b = d;
...
b.f(); // ok
b.g(); // 錯誤,b 是 reference to B 物件
static_cast<D&>(b).g(); // ok, 因為程序員知道 b 確實是參照某個 D 物件
: : const_cast
: 單純去掉物件的常數性 和/或 volatile 性
這裏是對的。
: int const i = 10;
: i = 5; // error
: const_cast<int&>(i) = 5; // ok
: (這邊我不太確定是不是這樣寫,很少用)
: (總之觀念是這樣)
舉例有誤。
const_cast 只是把常數性去掉,目的是方便設計,但是企圖通過 const_cast
修改一個 const 物件,其結果未定義(視實作環境是否對該 const 物件採取
保護措施)。
至於 const_cast 使用的時機,通常是為了在不修改既有模組(例如沒有 source
code 的程式庫)的情況下,解決兩個模組之間 const-correctness 不相容的問題
(通常是不能改的那個模組,其設計不夠周延)。例如:
struct C { void f(); }; // 假設 C 是一個既存的模組(不能修改)
void foo(C const &c) // foo 是自行設計的模組,c 物件只作為輸入,不會被更動
{
...
c.f(); // 由於 c 是 referece to const C 物件,但 C::f 並未被設計為
// const,因此這樣寫是不成立的。(編譯器會給錯誤或至少警告)
const_cast<C&>(c).f(); // ok, 用 const_cast 去掉 c 的常數性
}
: : reinterpret_cast
: 強迫 compiler 把輸入型別視為欲轉換的型別
: 這個不太好解釋…總之就是暴力轉型就對了 XD
: 直接把該記憶體位置的資料視為欲轉換的型別來看待
reinterpret_cast 幾乎等於暴力轉型(指 C-style 轉型),但不能去掉物件
的常數性(也就是還不夠暴力)。它用來處理任意型別之間的轉換,通常是為
了爭取運算或儲存空間的效率時,所採取的低階操作。例如:
int IP_Addr = 0;
char *p = reinterpret_cast<char*>(&IP_Addr); // 轉型為 char *,以便於
// 以 Byte 為單位來處理
p[0] = 192;
p[1] = 168;
p[2] = 0;
p[3] = 1;
傳統的 C-style 轉型相當於 static_cast, const_cast, reinterpret_cast
三合一(也就是暴力加三級)。
: 你漏了 dynamic_cast<>
: 這跟 static_cast<> 有些類似
dynamic_cast 和 static_cast 無關。
: dynamic_cast<> 只能對於具有多形性型別轉型,
: 也就是他至少得要有一個 virtual function
這裏是對的。dynamic_cast 是 RTTI 的一部份,它用來檢測一個多型基底
類別的 pointer 或 reference 所參照物件的實際型別。例如:
struct B1 { virtual void f() = 0; };
struct D1 { void f(); };
struct X : D1
{
void f();
void g();
};
void foo(B1 *b1) // foo 模組並不知道 b1 所參照物件的實際型別
{
if (dynamic_cast<D1*>(b1)) // 如果 b1 參照的是 D1 物件
{
b1->f();
}
else if (D1 *d1 = dynamic_cast<X*>(b1)) // 如果 b1 參照的是 X 物件
{
d1->g();
}
else
{
...
}
}
此例中,dynamic_cast 的用法也是所謂的 downcast,但和 static_cast
不同的是,前者用於偵測未知多型物件的實際型別,而後者用於對已知物件
(可以是多型物件或普通物件)的轉型。
可以看出,B1, D1, X 的繼承體系設計並不是很理想,因為在這種情況下,應該
直接利用 virtual function 的動態多型機制,而非依賴 dynamic_cast 偵測物
件的型別。例如:
struct B2
{
virtual void f() = 0;
virtual void g() = 0;
};
struct D2 : B2
{
void f();
void g();
} d2;
struct Y : D2
{
void f();
void g();
} y;
void foo()
{
B2 *b2 = &d2; // b2 參照 D2 物件
b2->g(); // 實際上會執行 D2::g,不必依賴 dynamic_cast
b2 = &y; // 換一下,改參照 Y 物件
b2->g(); // 實際上會執行 Y::g
}
顯然,B2, D2, Y 的設計比較合理。事實上,如果所有的模組都是自行設計,
dynamic_cast 是多餘的。但它之所以存在,其目的就是為了解決「既存模組
」不能修改的問題。(最常見的例子,就是缺乏源代碼的程式庫)
前面的例子中,如果 B1, D1 兩模組不能修改,為了新擴充 X 模組,又要和
舊模組相容,dynamic_cast 在此情況下就能派上用場(反過來說,如果沒有
dynamic_cast,問題就會變得很麻煩,而且各種替代方案都不太安全);但
如果完全是可自行控制的設計,就應該儘量遵循 B2, D2, Y 的方式來組織。
以上是 dynamic_cast 的功能中,downcast 的部份。另外,由於 C++ 支援
多重繼承的機制,dynamic_cast 亦可用於 crosscast(橫向轉型),例如:
struct Z : B1, B2 // 多重繼承
{
void f();
void g();
} z;
void foo()
{
B1 *b1 = &z;
B2 *b2 = dynamic_cast<B2*>(b1); // crosscast
}
這個例子可以用來說明,dynamic_cast 實際上的動作是「型別檢測」,而非
僅僅是「轉型」,後者只是它輸出的結果。此例中,b1 表面上是 (B1 *),但
它實際上參照的是 Z 物件,因此 dynamic_cast 偵測的結果,就是它可以順
利轉為 (B2 *) 。
: struct Base{virtual ~Base(){}};
: struct Derived: public Base{};
: Base pb = new Derived;
: Derived pd = dynamic_cast<Derived*>(pb);
: 如果 pb 真的是指向 Derived, 則 dynamic_cast<> 傳回 pb 的地址
: 否的話,pd 為 NULL
: boost 有提供 polymorphic_cast<>, 用處同 dynamic_cast<>
: 差別在於錯誤時不是傳回 NULL, 而是丟出 std::bad_cast
: polymorphic_downcast<> 則是專門用來在已知必然成功的 downcast
: 這種時候內建的 static_cast<> 和 dynamic_cast<> 其實都可以用,
: 只是 static_cast<> 沒有錯誤檢查,dynamic_cast<> 效率太差
: polymorphic_downcast<> 則使用 assert(); 來檢查是否成功
: http://www.boost.org/libs/conversion/cast.htm
: : c-style cast
: 等同於上面全部...
: 在 C++ 中替他們分類,避免造成混淆
: 不過對初學者來說的話這麼多才是混淆吧,我猜 :p
(下略)
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 59.120.214.120
推 godfat:感謝指導,雖然我覺得跟我講的沒啥衝突 XD 大概是太隨便了 03/13 01:44
※ 編輯: cppOrz 來自: 59.120.214.120 (03/13 04:50)
※ crazying:轉錄至看板 NTUGIEE_EDA 03/13 11:58
推 abovelight:推一個~了解更深入了 03/14 18:16
2011年11月8日 星期二
strtol
http://www.cplusplus.com/reference/clibrary/cstdlib/strtol/
Example
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
訂閱:
文章 (Atom)