C++ 程式設計/丟失的位
或為什麼定義一個operator void*()強制轉換運算子而不是一個operator bool()?
這樣做是為了避免我們錯誤地寫出諸如
int foo = std::cin;
或者,更重要的是,
int bah; std::cin << bah; // observe: << instead of >>
這樣的語句。然而,它並不完美,因為它允許其他錯誤,例如
delete std::cin;
儘管幸運的是,此類錯誤不太可能發生,因為delete在任何情況下都應該謹慎使用。
最先進的做法是,我們應該在內部定義一個私有巢狀類dummy在std::ios中,並返回 dummy 的指向成員函式的指標 - 從而允許從該指標到bool的隱式轉換,但不允許許多其他操作。這有時被稱為 "安全布林" 習語,其動機是 C++ 的bool型別具有從int到 int 的隱式轉換,這是標準化過程的結果。
該auto關鍵字以前的行為有所不同,但在 C++ 中,它允許省略變數的型別,並讓編譯器決定。這對於泛型程式設計特別有用,在泛型程式設計中,函式的返回型別可能取決於其引數的型別。因此,我們無需寫成這樣
int x = 42;
std::vector<double> numbers;
numbers.push_back(1.0);
numbers.push_back(2.0);
for(std::vector<double>::iterator i = numbers.begin();
i != numbers.end(); ++i) {
cout << *i << " ";
}
可以寫成這樣
auto x = 42; // We can use auto on base types...
std::vector<double> numbers;
numbers.push_back(1.0);
numbers.push_back(2.0);
// But auto is most useful for complicated types.
for(auto i = numbers.begin(); i != numbers.end(); ++i) {
cout << *i << " ";
}
作用域的概念很簡單,除非包含過程;然後,就很難跟蹤了
// Confusing Scope Program
#include <iostream>
using namespace std;
int i = 5; /* The first version of the variable 'i' is now in scope */
void p(){
int i = -1; /* A ''new'' variable, also called 'i' has come into existence. The 'i' declared above is now out of scope,*/
i = i + 1;
cout << i << ' '; /* so this ''local'' 'i' will print out the value 0.*/
} /* The newest variable 'i' is now out of scope. The first variable, also called 'i', is in scope again now.*/
int main(){
cout << i << ' '; /* The first variable 'i' is still in scope here so a ''5'' will be output here.*/
char ch;
int i = 6; /* A ''new'' variable, also called 'i' has come into existence. The first variable 'i' is now out of scope again,*/
i = i + 1;
p();
cout << i << endl; /* so this line will print out a ''7''.*/
return 0;
} /* End of program: all variables are, of course, now out of scope.*/
第一個變數 'i' 在兩個不同的部分被置於作用域之外。因此,重複的語句 cout << i << ' '; 每次寫入都表示不同的含義。每次提到的 'i' 都是記憶體中的不同位置。這就是引言中提到的 `上下文':語句所處的上下文或背景每次都不一樣,因此語句每次在不同的地方執行不同的操作。
關於上面的示例,有一些重要的注意事項。該程式只是一個示例,並且非常複雜,它只用於演示作用域的概念,而不是其他任何東西。雖然它說明了作用域的概念,但它並沒有有效地說明作用域的用途。
某些變數需要儲存整個程式的資訊,而其他變數是短期變數,它們只為單個小目的而短暫地存在,然後透過超出作用域而被銷燬。在以下程式中,讀取一組數字,然後呼叫一個過程,該過程計算陣列中數字的平均值。在過程中,為了遍歷陣列並依次選擇陣列中的元素,會建立一個名為 'i' 的變數,用於執行此目的。對比兩種型別的變數:陣列本身在整個程式中始終處於作用域內,而變數 'i' 僅在程式碼的一小部分中處於作用域內,以執行其自身的小任務。
// Program Average
#include <iostream>
using namespace std;
float a[10]; /* a is now in scope.*/
int length; /* length is now in scope.*/
float average(){
float result = 0.0; /* result is now in scope.*/
for(int i = 0; i < length; i++){ /* i is now in scope.*/
result += a[i];
} /* i is now out of scope.*/
return result/length;
} /* result is now out of scope.*/
int main(){
length = 0;
cout << "enter a number for each of the next 10 lines" << endl;
while( length != 10 )
{ cin >> a[length++]; }
float av = average(); /* av is now in scope.*/
cout << endl << "average: " << av << endl;
return 0;
} /* All variables now out of scope.*/
在一個過程中,可以開始一個新的作用域級別。實際上,每次寫入左花括號 `{' 時都會發生這種情況,並且在寫入與其匹配的右花括號 '}' 的地方結束。因此,可以構建任意深度的作用域層,如以下程式所示。以下程式具有四個作用域級別。最內層的作用域被認為是 包含 其周圍的作用域,因此我們談論內層作用域和外層作用域。同樣,該程式僅用於說明目的,它沒有任何實際價值。
// Complicated Scope Program
#include <iostream>
using namespace std; /* outermost level of scope starts here */
int i;
int main(){ /* next level of scope starts here */
int i;
i = 5;
{ /* next level of scope starts here */
int j,i;
j = 1;
i = 0;
{ /* innermost level of scope of this program starts here */
int k, i;
i = -1;
j = 6;
k = 2;
} /* innermost level of scope of this program ends here */
cout << j << ' ';
} /* next level of scope ends here */
cout << i << endl;
return 0;
} /* next and outermost levels of scope end here */
該程式的輸出是 6 5。為了理解原因,我們首先檢視 'i' 的更簡單情況,並瞭解為什麼為它列印了 5,然後檢視 'j' 的更復雜情況,以及為什麼為它列印了 6。
在每個新的作用域級別,都會建立一個新的變數 'i'。因此,僅第一次對 'i' 的賦值(其中 'i' 被賦值為 5)會影響這個特定的變數 'i':即被列印的變數。該變數在第一次賦值後不會改變其值,因此最終的語句列印了一個 5。對 'i' 的其他賦值與最終的列印語句無關。
相反,變數 'j' 僅建立一次,因此,即使變數是在外層作用域中宣告的,將 `j' 賦值為 6 的賦值也會改變這個唯一的現有變數 'j'。如果程式有一個在另一個作用域級別內的作用域級別,並且在該內層作用域級別沒有宣告具有相同名稱的變數,那麼計算機將 `向外檢視' 到下一個外層作用域級別。如果那裡也沒有宣告具有該名稱的變數,那麼它將繼續向外檢視,直到找到變數的宣告。(當然,如果從未找到變數的宣告,那麼編譯器將指示錯誤,說明變數未宣告,因此程式不會被編譯。)
上面我們說過,每個左花括號 `{'. 都會開始一個新的作用域級別。然而,不常用裸左花括號:通常,左花括號與if 語句或while 語句或類似語句相關聯。我們在上面的程式中添加了這些語句,以建立一個更常見的(但仍然無用)示例程式。該程式可以編譯,並且在執行時列印一個 5,但作為一個程式幾乎沒有價值。
// Complicated Scope Program, variation 1
#include <iostream>
using namespace std; /* outermost level of scope starts here */
int i;
int main(){ /* next level of scope starts here */
int i;
i = 5;
while(i != 5) { /* next level of scope starts here */
int j,i;
j = 1;
i = 0;
switch (i) { /* next level of scope starts here */
int i;
case 1:
if (i != 4) { /* innermost level of scope of this program starts here */
int k, i;
i = -1;
j = 6;
k = 2;
} /* innermost level of scope of this program ends here */
break;
case 2:
j = 5;
break;
} /* next level of scope ends here */
cout << j << ' ';
} /* next level of scope ends here */
cout << i << endl;
return 0;
} /* next and outermost levels of scope end here */
我們在switch 語句中添加了一個額外的作用域級別,因為該語句要求一個左花括號,並且它表明即使在這一點上,也會開啟一個新的作用域級別(因為我們能夠宣告另一個名為 `i' 的變數而不會出現錯誤)。如您所見,各種控制結構(即while、if 和switch 語句)都在其各自的匹配右花括號處結束,因此與作用域結束的點相同。可以公平地說,每個控制語句都在作用域的某個區域內執行。但請記住,作用域是一個關於變數名稱及其定義區域的概念,而不是關於控制結構的概念。
if 或while 語句在其後具有一個開括號並不是必需的,程式 average 上面展示了其while 語句的示例。在這種情況下,不會開始新的作用域級別。是左花括號打開了新的作用域級別,而不是if 或while 語句本身。switch 語句要求在該點處有一個左花括號,但請注意,`i' 開關變數在舊的作用域級別。新的作用域級別在左花括號之後立即出現,一如既往。
在實際應用中,for 迴圈控制結構的範圍作用方式與其他結構類似。然而,允許在 for 語句本身內宣告 for 迴圈變數,如上面program average中的第八行所示。這是一種良好的程式設計習慣,因為它使程式結構更加清晰。
在本節中的所有程式中,語句隨著作用域級別的加深而向右延伸。這被稱為縮排,是程式呈現中非常重要的一個特性。它始終明確地顯示作用域級別,並清楚地標明語句的結束位置。例如,在上面的程式中,cout << j << ' '; 位於while迴圈內,而cout << i << endl; 則位於迴圈之外。迴圈和作用域級別在同一位置結束:這兩個語句之間的右括號表示兩者都已結束。
詳細介紹 for 控制語句的作用域
[edit | edit source]本小節可能比有用更令人困惑,但為了完整性而提供;您可以隨意跳過它。
for 控制語句具有不尋常的作用域,即左圓括號也開始它自己的作用域級別。因此,以下程式是合法的
// Complicated Scope Program, variation 2
#include <iostream>
using namespace std; /* outermost level of scope starts here */
int i;
int main(){ /* next level of scope starts here */
int i;
i = 5;
for( /* next level of scope starts here */
int i = 1;
i<10 && cout << i << ' ';
++i )
{ /* next level of scope starts here */
int i = -1;
cout << i << ' ';
} /* two levels of scope end here*/
cout << i << endl;
return 0;
} /* next and outermost levels of scope end here */
它輸出
1 -1 2 -1 3 -1 4 -1 5 -1 6 -1 7 -1 8 -1 9 -1 5
這種 for 語句的特殊特性不是while 語句所共有的。嘗試在while語句內宣告變數是語法錯誤,因此while(int i < 22)i++; 會引發語法錯誤。
這種特殊的範圍級別使我們能夠在 for 迴圈本身內宣告一個 for 迴圈變數(例如上面程式中的變數 'i'),而不是必須在包含的作用域級別宣告它,從而建立一個更簡潔的程式。但這有點特殊。
上面的程式確實顯示了一個有趣的特性:為了檢查是否打開了新的作用域級別,只需嘗試在該級別再次宣告一個已存在的變數即可,就像上面示例程式中對變數 'i' 所做的那樣,它在每個可能的級別都聲明瞭一個新的變數 'i'。
上面的程式還說明了另一個非常重要的點:計算機程式設計中有一句諺語,即可以用任何語言編寫糟糕的程式碼。上面的程式在操作方面相當不清楚,並且是糟糕編碼的典型例子:它適合說明一個觀點,但不適合閱讀。所有程式碼都應儘可能清晰地編寫,使程式儘可能清晰,正如其他地方關於程式風格的討論中所述。
作用域和生命週期
[edit | edit source]變數的作用域應與其生命週期區分開來。在上面名為 'Confusing Scope Program' 的程式中,第一個變數 'i' 在一段時間內不再處於作用域內,但它仍然存在,因此它的生命週期仍在繼續,儘管它不在作用域內。在舊的程式語言中,很難構造出作用域和生命週期不同的例子——通常情況下,在這些舊語言中,兩者是相同的,因此生命週期等於作用域。不僅很難,而且在它確實發生的一般情況下也沒有那麼有用。然而,在最近建立的計算機語言(如 C++)中,使用超出作用域但仍然存活的變數的想法得到了廣泛應用,並創造了 C++ 的主要區別特徵:C++ 類。這是上面程式用類重寫的版本
// Program Average rewritten using a class
#include <iostream>
using namespace std;
class StatisticsPackage{
private:
float aa[20]; /* aa scope start*/
int length; /* length scope start*/
public:
float average(){
float result = 0.0; /* result scope start*/
for(int i = 0; i < length; ++i) /* i scope start*/
result += aa[i];
return result/length;
} /* result and i scope end*/
void get_data(){
length = 0;
while(cin >> aa[length++]);
--length;
}
}; /* aa and length scope end*/
int main(){
StatisticsPackage sp; /* aa and length lifetimes start */
sp.get_data();
float av = sp.average(); /* av scope start*/
cout << av << endl;
return 0;
} /* av scope end*/
在這個版本的程式中,變數 'length' 和 'aa' 在類 'sp' 出現後仍然存活。但是,它們的範圍已經限制了:它們是存活的,但不在作用域內,儲存了資訊,但不能在主程式中直接訪問。以這種方式將變數保持在作用域之外對除錯程式非常有用,因為它縮小了可能更改變數值的行的數量。
strcat_s
[edit | edit source]strcat_s 函式被提議用作 strcat 函式的近似替代品。strcat_s 比strcat 多一個引數,該引數指定目標字串的最大大小。strcat_s 函式將源字串的字元追加到第一個字串目標字串的末尾
| 語法 |
#include <string.h>
errno_t strcat_s(char * dest,rsize_t s1max,const char * scr);
|
例如
char str1 , str2;
printf( "Enter your first string : " );
scanf( "%s", str1 );
printf( "Enter your second string: " );
scanf( "%s", str2);
strcat_s(str1, 16, str2);
printf( " %s\n", str1 );
