C++中的C

来源:https://www.cnblogs.com/fireway/archive/2019/08/19/11375008.html
-Advertisement-
Play Games

前言 因為C++是以C為基礎的,所以要用C++編程就必須熟悉C的語法。 C語言的學習可以學習K & R C的《C程式設計語言》 創建函數 Q: 函數原型? A: 標準C/C++有一個特征叫函數原型(function prototyping)。調用函數時,編譯器使用原型確保正確傳遞參數並且正確處理返回 ...


 

前言

因為C++是以C為基礎的,所以要用C++編程就必須熟悉C的語法。

C語言的學習可以學習K & R C的《C程式設計語言》

創建函數

Q: 函數原型?

A: 標準C/C++有一個特征叫函數原型(function prototyping)。調用函數時,編譯器使用原型確保正確傳遞參數並且正確處理返回值,如果調用函數時程式員出錯,編譯器就會捕獲這個錯誤。

A: 下麵是一個聲明函數原型的例子:

int translate(float x, float y, float z);

在函數原型中聲明變數時,對於同樣形式的變數,不能寫成translate(float x, y, z)這種形式,而必須指明每一個參數的類型。在函數聲明中,下麵的形式是可以接受的:

int translate(float, float, float);

因為在調用函數時,編譯器只會檢查類型,所以使用標識符只是為了使別人閱讀代碼時更加清晰。

Q: 空參和可變參數列表?

A: 如果有一個空的參數列表,可以在C++中聲明這個函數為func(),它告訴編譯器,這裡有0個參數。應該意識到這隻意味著C++中是空參數列表,在C中它意味著不確定的參數數目,這是C語言中的漏洞,因為在這種情況下不能進行類型檢查。

A: 在C/C++中,聲明func(void)都意味著空的參數列表。

A: 可變的參數列表(variable argument list)用省略號(...)表示。

A: 如果因為某種原因不想使用函數原型的錯誤檢查功能,可以對固定參數表的函數使用可變參數列表,正因為如此,應該限制對C使用可變參數列表並且在C++中也避免使用。我們將會看到,C++中有更好的選擇。

Q: 函數的返回值?

A: C++函數原型必須指明函數的返回值類型,在C中,如果省略返回值,表示預設為整型。

A: 為了表明沒有返回值可以使用void關鍵字,如果這時試圖從函數返回一個值會產生錯誤。

A: 下麵有一些完整的函數原型:

int f1(void);   // Returns an int, takes no arguments
int f2();       // Like f1() in C++ but not in Standard C!
float f3(float, int, char, double);  // Returns a float
void f4(void);  // Takes no arguments, returns nothing

A: 在一個函數定義中可以有多個return語句。

示例:return.cpp

A: 註意函數聲明不是必須的,因為函數在main()使用它之前定義,所以編譯器從函數定義中知道它。

Q: 通過庫管理器創建自己的庫?

A: 我們可以將自己的函數收集到一個庫中。在Linux系統中,庫的擴展名叫.so或.a

A: 大多數編程包帶有一個庫管理器來管理對象模塊組,每個庫管理器有它自己的命令,但有這樣一個共同的想法:如果想創建一個庫,那麼就建立一個頭文件,它包含庫中的所有函數原型。把這個頭文件放置在預處理器搜索路徑中的某處,或者在當前目錄中(以便能被#include "myheader.h"中發現),或者在包含路徑中(以便能被#include<頭文件>發現)。

A: 把建成的庫和其他庫放置在同一個位置以便鏈接器能發現它,當使用自己的庫時,必須向命令行添加一些東西,讓鏈接器知道為你調用的函數查找庫。

執行控制語句

本節覆蓋了C++中的執行控制語句,在學習C/C++代碼之前,必須熟悉這些語句。

C++使用C的所有執行控制語句,這些語句包括if-else、while、do-while、for、swtich選擇語句。C++也允許使用聲名狼藉的goto語句,應該儘量避免使用goto語句。

Q: 真和假?

A: 表達式產生布爾值true或false,這隻是C++中的關鍵字,在C中如果一個表達式等於非零則表示true。

Q: if-else語句?

A: if-else語句有兩種形式:用else或不用else。
第一種形式:

if (表達式) 
{
    語句; 
}

第二種形式

if (表達式) 
{
    語句;
} 
else    
{
    語句;
}

A: 示例:ifthen.cpp

Q: while語句?

A: while、do-while和for語句是迴圈控制語句。一個語句重覆執行直到控製表達式的計值為假。

A: while迴圈的形式:

while(表達式) 
{
    語句;
}

迴圈一開始就對錶達式進行計算,併在每次重覆執行語句之前再次計算。

A: 示例:guess.cpp

Q: do-while語句?

A: do-while的形式是:

do 
{
    語句;
} while(表達式);         

A: do-while語句與while語句的區別在於,即使表達式第一次計值為假,前面的語句也會至少執行一次。在一般的while語句中,如果條件第一次為假,語句一次也不會執行。

A: 示例:guess2.cpp,如果使用do-while,變數guess不需要初始化為0值,因為在它被檢測之前就已被cin語句初始化了。

A: 因為某種原因,大多數程式員更多喜歡值使用while語句而避免使用do-while語句。

Q: for語句?

A: 在第一次迴圈前,for迴圈執行初始化,然後它執行條件測試,併在每一次迴圈結束時執行某種形式的“步進(stepping)”。

A: for迴圈的形式:

for (initialization; conditional; step) {
    語句;           
}

A: 表達式中的initialization、conditional或step都可能為空。

A: 一旦進入for迴圈,initialization代碼就執行,在每次迴圈之前,conditional被測試,如果它的計值一開始為false,for語句就不會執行,每一次迴圈結束時,執行step。

A: for迴圈通常用於“計數”任務,示例:charlist.cpp,運行結果link

Q: 關鍵字break和continue?

A: break語句退出迴圈,不再執行迴圈中的剩餘語句;continue語句停止執行當前的迴圈,返回到迴圈的起始處開始新的一輪迴圈。

A: 一個非常簡單的菜單系統,示例:menu.cpp

A: 如果用戶在主菜單選擇'q',則用個關鍵字break退出,選擇其他,程式則繼續運行。

A: 在每一個子菜單選擇後,關鍵字continue用於跳轉到while迴圈的起始處。

A: while(true)語句等價於“永遠執行這個迴圈”,當用戶按'q'時,break語句使程式跳出這個迴圈語句。

Q: switch語句

A: switch語句根據一個整型表達式的值從幾段代碼中選擇執行。它的形式如下:

switch(selector) 
{
    case integral-value1 : statement; break;
    case integral-value2 : statement; break;
    case integral-value3 : statement; break;
    case integral-value4 : statement; break;
    case integral-value5 : statement; break;
    (...)
    default: statement;
}

A: 選擇器(selector)是一個產生整數值的表達式,switch語句把選擇器(selector)的結果和每一個整數值(integral-value)比較。如果發現匹配,就執行對應的語句,如果都不匹配,則執行default語句。

A: switch語句是一種清晰的實現多路選擇的方式,即對不同的執行路徑進行選擇,但它需要一個能在編譯時期求得整數值的選擇器。

A: 例如,如果想使用一個字元串類型的對象作為一個選擇器,在switch語句中它是不能用的,對於字元串類型的選擇器,必須使用一系列的if語句並比較在條件中的字元串。

A: 上面的菜單程式提供了一個特別好的switch語句,示例:menu.cpp

Q: 使用和濫用goto?

A: 因為關鍵字goto存在於C中,所以C++中也支持它。使用goto經常被貶斥為一種糟糕的編碼方式,大多數情況也確實如此。

A: 示例:gotokeyword.cpp,運行結果link

A: 一個可供選擇的方法是設置一個布爾值,在外層for迴圈對它進行測試,然後利用break從內層for迴圈跳出。然而,如果我們有幾層for語句或while迴圈,可能會出現困難。

Q: 遞歸?

A: 遞歸也是一種控制流程的一種非常有用的編程技巧,憑藉遞歸我們可以在一個函數內部調用該函數。

A: 如果一直調用下去,會導致記憶體用完,所以一定要有一種確定“達到底點”的條件,這個條件就叫做基值條件。

A: 示例:cats_in_hats.cpp,運行結果link

A: 求解某些具有隨意性的複雜問題經常使用遞歸,因為這時解的具體“大小”不受限制,函數可以一直遞歸調用,直到問題解決。

A: 更多參考:Java數據結構和演算法 - 遞歸

運算符簡介

我們可以把運算符看作是一種特殊的函數,C++的運算符重載正是以這種方式對待運算符的。

一個運算符帶一個或更多的參數並產生一個新值,運算符參數和普通的函數調用參數相比形式上不同,但是作用是一樣的。

Q: 優先順序?

A: 使用括弧使優先順序更加清晰

Q: 自增和自減?

A: 可能C語言的設計者認為如果程式員的眼睛不必瀏覽大範圍的印刷區域,那樣理解一段巧妙的代碼可能是比較容易的。其中一個較好的簡潔示例是自增和自減運算符,經常使用它們去改變迴圈變數以控制迴圈執行的次數。

A: 如果A是一個整數,首碼++A則先執行運算,再產生結果值;尾碼A++,則產生當前值,再執行運算。

A: 示例:autoIncrement.cpp,運行結果link

A: 題外話,C++隱含的意思就是“在C上更進一步”。

數據類型簡介

數據類型(data type)定義使用存儲空間(記憶體)的方式,通過定義數據類型,告訴編譯器怎樣創建一片特定存儲空間,以及怎樣操縱這邊存儲空間。

數據類型可以是內部的或抽象的。

內建數據類型(built-in data type )是編譯器本來就能理解的數據類型,直接與編譯器關聯。C和C++中的內建數據類型幾乎是一樣的。

相反,用戶定義的數據類型是我們和別的程式員創建的類型,作為一個類。它們一般被稱為抽象數據類型。編譯器啟動時,通過讀包含類聲明的頭文件認識怎麼處理抽象數據類型。

Q: 基本內建類型?

A: 標準C的內建類型規範不說明每一個內建類型必須有多少位,規範只規定內建類型必須能存儲的最大值和最小值。

A: 系統頭文件limits.h和float.h定義了不同的數據類型可能存儲的最大值和最小值。

A: C/C++中有4個基本的內建數據類型。
char用於存儲字元,使用最小的一個位元組存儲,儘管它可能占用更大的空間。
int存儲整數值,使用最小兩個位元組的存儲空間。
float和double類型存儲浮點數,一般使用IEEE的浮點格式。
float使用單精度浮點數,double用於雙精度浮點數。

A: 示例:basic.cpp

A: 如果不初始化一個變數,標準會認為沒有定義它的內容,通常這意味著它們的內容是垃圾

A: 程式的第二部分同時定義和初始化變數,如果可能,最好在定義時提供初始值。

A: 註意6e-4中指數符號的使用,意思是“6乘以10的負4次冪”。

Q: bool類型與true/false?

A: 在bool類型成為標準C++的一部分之前,每個人都想使用不同的方法產生類似bool類型的行為,這產生了可移植性問題,可能會引入微妙的錯誤。

A: 標準C++的bool類型由兩種由內建的常量true和false表示的狀態。true轉換為整數1,false轉換為整數0。這3個名字bool、true、false都是關鍵字。

A: 因為有很多現存的代碼使用整型int表示一個標誌,所以編譯器隱式轉換int為bool,非零值為true,零值為false。理想情況下,編譯器會給我們一個警告,建議糾正這種情況。

A: 指針在必要的時候也自動轉換成bool值。

Q: 說明符?

A: 說明符(specifier)用於改變基本內建類型的含義並把它們擴展成一個更大的集合。

A: 有4個說明符:long、short、signed和unsigned。

A: 整數類型的大小等級是:short int、int、long int。一般int必須至少有short int型的大小。

A: 浮點型的大小等級是:float、double和long double。“long float”是不合法的類型,也沒有“short float”。

A: signed和unsigned修飾符告訴編譯器怎麼使用整數類型和字元的符號位。unsigned數不保存符號,因此有一個多餘的位可用,所以它能存儲比signed數大一倍的正數。signed是預設的。

A: 示例:specify.cpp,運行結果link

A: 要註意,在不同的機器/操作系統/編譯器上運行這個程式得到的結果可能不一樣。唯一一致的事情是每個不同類型都具有標準中規定的最小值和最大值。

A: 正如示例所示,當用short或long改變int時,關鍵字int是可選的。

Q: 指針簡介(一)?

A: 不管什麼時候運行一個程式,都是首先要把它從磁碟裝入電腦記憶體,因此,程式中的所有元素都駐留在記憶體的某處。

A: 記憶體一般被佈置成一系列連續的記憶體位置,我們通常把這些位置看作是8bit的一個位元組,但實際每一個空間的大小取決於具體機器的結構,一般稱為機器的字長(word size)。

A: 每個空間可按它的地址與其他空間區分。為了便於討論,我們認為所有機器都使用連續地址的位元組從0開始,一直到該電腦的記憶體的上限。

Q: 指針簡介(二)?

A: 因為程式運行時駐留記憶體中,所以程式中的每一個元素都有地址,假設我們從一個簡單的程式開始:

#include <iostream>
using namespace std;

int dog, cat, bird, fish;

void f(int pet) {
  cout << "pet id number: " << pet << endl;
}

int main() {
  int i, j, k;
}

A: 程式運行時,每一個元素在記憶體中都占有一個位置,甚至函數也占用記憶體。我們將會看到,定義什麼樣的元素和定義元素的方式通常決定元素在記憶體中放置的地方。

Q: 指針簡介(三)?

A: C/C++有一個運算符告訴我們元素的地址,這就是‘&’運算符。只要在標識符前加上‘&’,就會得到該標識符的地址。

A: 示例:yourpets.cpp,運行結果link

A: (long)是一種類型轉換(cast),意思是“不要把它看成原來的類型,而是看作是long類型”。這個類型轉換不是必須的,但是如果沒有的話,地址是以十六進位的形式列印,所以轉換為long類型會增加一些可讀性。

A: 這個程式的結果會隨電腦、操作系統和各種其他的因素的不同而變化,但我們總會看到一些有趣的現象。全局變數和局部變數存放在不同的區域,當對語言有更多的瞭解時,就會明白為什麼如此。同樣,f()出現在它自己的區域,在記憶體中代碼和數據一般是分開存放的。

A: 相繼定義的變數在記憶體中是連續存放的,它們根據各自的數據類型所要求的位元組數分隔開,在本示例中,變數cat距離變數dog是4個位元組,同理bird和cat也一樣。所以在這台機器上,一個int占4個位元組。

Q: 指針簡介(四)?

A: 那麼利用地址能幹什麼呢?能做的重要的事就是,把地址存放在別的變數中以便以後使用,C/C++有一個專門的存放地址的變數類型,這個變數就叫做指針(pointer)。

A: 定義一個指針時,必須規定它指向的變數類型,可以先給出一個類型名,然後不是立即給出變數的標識符,而是在類型和標識符之間插入一個星號*,這就是說“等一等,它是一個指針”。一個指向int的指針聲明如下:

int* pi;  // pi is points to an int variable

A: 把‘*’和類型聯繫起來似乎很明白且易讀,但是事實上可能容易產生錯誤。如:

int a, b, c;

而對於指針,可能想寫成這樣

int* pa, pb, pc;

C/C++不允許像這樣合乎情理的表達,在上面的聲明中,只有pa是一個指針,而pb和pc是一般的int,可以認為*和標識符結合的更緊密一些。因此最好是每一行定義一個指針,這樣就清晰一些:

int *pa = NULL;
int *pb = NULL;
int *pc = NULL;

A: // C++編程的一般原則是在定義的同時就進行初始化。如:

int a = 47;
int *pa = &a;

現在已經初始化a和pa,pa存放a的地址。

Q: 指針簡介(五)?

A: 一旦有一個初始化了的指針,我們能做的最基本的事就是利用指針來修改它指向的值。要通過指針訪問變數,可以使用以前定義指針使用的同樣的運算符來間接引用這個指針,如:

*pa = 100;

現在a的值時100而不是47。

Q: 按值傳遞?

A: 通常,向函數傳遞參數時,在函數內部生成該參數的一個拷貝,這稱為按值傳遞(pass-value)。

A: 示例:passbyvalue.cpp,運行結果link

A: 在函數f()中,a是一個局部變數(local variable),它只有在調用函數f()期間存在。因為它是一個函數參數,所以調用函數時通過參數傳遞來初始化a的值。在main()中參數是x,其值為47,所以當調用函數f()時,這個值被拷貝到a中。

A: 當運行這個程式時,最初,x的值時47,調用f()時,在函數調用期間為變數a分配臨時空間,拷貝x的值給a來初始化它,後面我們改變a的值並顯示它被改變,但是f()調用結束時,分配給a的臨時空間消失了,我們看到,在a和x之間的曾經發生過的唯一聯繫,是在把x的值拷貝到a的時候。

Q: 按地址傳遞?

A: 當在函數f()內部時,變數x就是外部對象(outside object),顯然,改變局部變數並不會影響外部變數,因為它們分別存在存儲空間的不同位置,但是如果我們的確向修改外部對象那又該怎麼辦呢?

A: 在某種意義上,指針就是另一個變數的別名,所以如果我們不是傳遞一個普通的值而是傳遞一個指針給函數,實際上就是傳遞外部對象的別名,使函數能修改外部對象。

A: 示例:passaddress.cpp,運行結果link,現在函數f()把指針作為參數,並且在賦值期間間接引用這個指針,這就使得外部對象x被修改了。

A: 因此,通過給函數傳遞指針可以允許函數修改外部對象,這是最基本也是最常用的用途。

Q: C++引用?

A: C++增加了另外一種給函數傳遞地址的途徑,這就是按引用傳遞(pass-by-reference),它也存在於一些其他的編程語言,並不是C++的發明。

A: 我們可以用引用傳遞參數地址,引用和指針的區別在於,帶引用的函數調用比帶指針的函數調用在語法構成上更清晰,在某種情形下,使用引用實質上的確只是語法構成上的不同。

A: 示例:passreference.cpp,運行結果link

A: 在函數f()的參數列表中,不用int*來傳遞指針,而是用int&來傳遞引用。在f()中,如果僅僅寫‘r’,會得到r引用的變數值,如果對r賦值,實際上是給r引用的變數賦值,事實上,得到r中存放的地址值的唯一是用‘&’運算符。

A: 在函數main(),我們能看到引用在調用函數f()中的重要作用,其語法形式還是f(x)。儘管這看起來像是一般的按值傳遞,但是實際上引用的作用是傳遞地址,而不是值的一個拷貝。

A: 所以我們可以看到,以引用傳遞允許一個函數去修改外部對象,就像傳遞一個指針所做的那樣,通過這個簡單的示例,我們可以認為引用僅僅是語法上的不同方法(有時稱為“語法糖syntactic sugar”)。

Q: 用指針和引用作為修飾符 ?

A: 迄今為止,我們已經看到了基本的數據類型char、int、float和double,看到了修飾符signed、unsigned、short和long,它們可以和基本的數據類型結合使用,現在我們增加了指針和引用,所以可能產生了三倍的組合。

A: 示例:alldefinition.cpp

Q: 萬能指針void*(一)?

A: 如果聲明指針是void*,它意味著任何類型的地址都可以間接引用那個指針,而如果聲明是int*,則只能對int型變數的地址間接引用那個指針。

A: 示例:voidpointer.cpp

Q: 萬能指針void*(二)?

A: 一旦我們間接引用一個void*,就會失去關於類型的信息,這意味著在使用前,必須轉換為正確的類型。

A: 示例:CastFromVoidPointer.cpp

A: 轉換(int*)pv告訴編譯器把void*當做int*處理,因此可以成功地對它間接引用。

A: 我們註意到這個語法相當難看,的確如此,但是更糟的是,void*在語言類型系統引入了一個漏洞,也就是說,它允許甚至是提倡把一種類型看作另一種類型。

A: 在上面示例中,通過把pv轉換為int*,把它看作一個整型,但是並沒有說不能把它轉換為一個char*double*,這將改變已經分配給int的存儲空間的大小,可能會引起程式崩潰。

A: 一般來說應該避免使用void指針,只有在一些少見的特殊情況才用。

A: 我們不能使用void引用。

作用域

Q: 規則?

A: 作用域規則告訴我們一個變數的有效範圍,它在哪裡創建,在哪裡銷毀(即超出了作用域)。

A: 變數的有效作用域從它的定義點開始,到和定義變數之前最鄰近的開括弧配對的第一個閉括弧,也就是說,它的作用域是由變數所在的最近一對括弧確定。

A: 示例:scope.cpp

A: 上面的示例表明什麼時候變數可見,什麼時候變數不可用,只有在變數的作用域內才能使用它。

A: 作用域可以嵌套。其內層可以訪問外層,反過來不行。

Q: 實時定義變數?

A: 定義變數時,C和C++有著顯著的區別,這兩種語言都要求變數使用前必須定義,但是C強制在作用域的開始處就定義所有的變數,以便在編譯器創建一個塊時,能夠給所有的變數分配空間。

A: 讀C代碼時,進入一個作用域,首先看到的是一個變數的定義塊,在塊的開始部分聲明所有的變數,要求程式員以一種特定的方式寫程式,因為語言的實現細節需要這樣。大多數人在寫代碼之前並不知道它們將要使用的所有變數,所以他們必須不停地跳轉回塊的開頭來插入新的變數,這是很不方便的,也很容易引起錯誤。這些變數定義對讀者來說並沒有很多含義,它們實際上只是容易引起混亂,因為它們出現的地方遠離使用它們的上下文。

A: C++(不是C)允許在作用域內的任意地方定義變數,所以可以在正好使用它之前定義。此外,可以在定義變數時對它進行初始化以防止犯某種類型的錯誤。以這種方式定義變數使得編寫代碼更容易,減少了在一個作用域內不停地來回跳轉造成的問題。

A: 同時定義並初始化一個變數是非常重要的。

A: 我們還可以在for迴圈和while迴圈的控製表達式內定義變數,在if語句的條件表達式和switch的選擇器語句內定義變數。

A: 示例:OnTheFly.cpp

A: 儘管例子表明在while語句、if語句和switch語句中也可以定義變數,但是可能因為語法受到許多限制,這種定義不如for的表達式中常用。例如,我們不能有任何插入括弧,也就是說,不可以寫出:

while((char c = cin.get()) != 'q')

附加的括弧似乎是合理的,並且能做很有用的事,但因為無法使用它們,結果就不像所希望的那樣。問題是‘!=’比‘=’的優先順序高,所以char c最終含有的值是由bool轉換為char的,當列印出來時,我們在很多終端上看到一個笑臉字元。

A: 通常,可以認為在while語句、if語句和switch語句中定義變數的能力是為了完備性(completeness),但是唯一使用這種變數定義的地方可能是for迴圈中,在這裡使用的十分頻繁。

指定存儲空間分配

創建一個變數時,我們擁有指定變數生存期的很多選擇,指定怎樣給變數分配存儲空間,以及指定編譯器怎樣處理這些變數。

Q: 全局變數?

A: 全局變數時在所有函數體的外部定義,程式的所有部分,甚至其他文件中的代碼,都可以使用。

A: 全局變數不受作用域的影響,總是可用的,也就是說,全局變數的生命周期一直到程式的結束。

A: 如果在一個文件中使用extern關鍵字來聲明另一個文件中存在的全局變數,那麼這個文件就可以使用這個數據。

A: 示例:global,運行結果link

A: 變數globe的存儲空間是由代碼global.cpp中定義創建的,在global2.cpp的代碼中可以訪問同一個變數。由於global2.cpp和global.cpp的代碼是分段編譯的,必須通過聲明:

extern int globe;

告訴編譯器變數存在哪裡。

A: 運行這個程式時,會看到函數func()的調用的確影響globe的全局實例。

Q: 局部變數(一)?

A: 局部變數出現在一個作用域,它們局限於一個函數內。

A: 局部變數經常被稱為自動變數(automatic variable),因為它們在進入作用域時自動生成,離開作用域時自動消失。

A: 關鍵字auto可以顯示地說明這個問題,但是局部變數預設認為是auto,所以沒有必要聲明為auto。

Q: 局部變數(二)?

A: 寄存器變數(register variable)是一種局部變數。

A: 關鍵字register告訴編譯器“儘可能快地訪問這個變數”,加快訪問速度取決於實現,但是,正如名字所暗示的那樣,這經常是通過在寄存器放置變數來做到的。這並不能保證將變數放置在寄存器中,甚至也不能保證提高訪問速度。這隻是對編譯器的一個暗示。

A: 使用register變數是有限制的,不可能得到或計算register變數的地址。register變數只能在一個塊中聲明,不可能有全局的或靜態的register變數。然而可以在一個函數中使用register變數作為一個形式參數。

A: 一般地,不應當推測編譯器的優化器,因為它可能比我們做得更好,因此,最好避免使用關鍵字register。

Q: Static關鍵字(一)之靜態變數?

A: 通常,函數中定義的局部變數在函數作用域結束時消失,當再次調用該函數時,會重新創建該變數的存儲空間,其值會被重新初始化。

A: 如果想使局部變數的值在程式的整個生命周期仍然存在,我們可以定義函數的局部變數為static,並給它一個初始值。

A: 初始化只在函數第一次調用時執行,函數調用之間變數的值保持不變。用這種方式,函數可以“記住”函數調用之間的一些信息片段。

A: 我們可能奇怪為什麼不使用全局變數,static變數的優點是在函數範圍之外它是不可用的,所以它不可能被輕易地改變。

A: 示例:static.cpp,運行結果link

A: 每一次在for迴圈中調用函數func()時,它都列印不同的值,如果不使用關鍵字static,列印出的值總是1。

Q: Static關鍵字(二)?

A: static的第二層意思和前面的含義相關,即“在某個作用域外不可訪問”。

A: 當用static修飾函數名和所有函數外部的變數時,它的意思是“在文件的外部不可以使用這個名字”。函數名或變數是局部於文件的,我們說它具有文件作用域(file scope)。

A: 示例:filestatic,編譯和鏈接下麵文件會引起鏈接器錯誤:

/tmp/ccGRjHFp.o:在函數‘func()’中:
filestatic2.cpp:(.text+0x6):對‘fs’未義的引用
collect2: error: ld returned 1 exit status

A: 儘管在文件filestatic2.cpp中變數fs被聲明為extern,但是鏈接器不會找到它,因為在filestatic.cpp中它被聲明為static。

A: static說明符也可以在一個類中使用,在後面介紹如何創建類時,在對此作出解釋。

Q: 外部變數

A: 前面已經簡單地描述和說明瞭extern關鍵字。它告訴編譯器存在著一個變數和函數,即使編譯器在當前編譯的文件中沒看到它,這個變數或函數可能在另一個文件中或者在當前文件的後面定義。

A: 示例:forward.cpp,運行結果link

A: 當編譯器遇到extern int i時,它直到i肯定作為全局變數存在於某處。當編譯器看到變數i的定義時,並沒看到別的聲明,所以知道它在文件的前面已經找到了同樣聲明的i。

Q: 鏈接?

A: 為了理解C/C++程式的行為,必須對鏈接(linkage)有所瞭解。在一個執行程式中,標識符代表存放著變數或被編譯過的函數體的存儲空間。鏈接用鏈接器(linker)所見的方式描述存儲空間。

A: 鏈接方式有兩種:內部鏈接(internal linkage)和外部鏈接(external linkage)。

A: 內部鏈接意味著只對正被編譯的文件創建存儲空間。用內部鏈接,別的文件可以使用相同的標識符或全局變數,鏈接器不會發現衝突,也就是為每一個標識符創建單獨的存儲空間。

A: 在C/C++中內部鏈接是由關鍵字static指定。

A: 外部鏈接意味著為所有被編譯過的文件創建一片單獨的存儲空間。一旦創建存儲空間,鏈接器必須解決所有對這片存儲空間的引用。

A: 全局變數和函數名有外部鏈接,通過用關鍵字extern聲明,可以從其他文件訪問這些變數和函數。

A: 函數之外定義的所有變數(在C++除了const)和函數定義預設為外部鏈接,可以使用關鍵字static特地強制它們具有內部鏈接,也可以在定義時使用關鍵字extern顯示指定標識符具有外部鏈接。

A: 在C中,不必用extern定義變數或函數,但是在C++中對於const有時必須使用。

A: 調用函數時,自動變數(局部變數)只是臨時存在於堆棧中,鏈接器不知道自動變數,所以這些變數沒有鏈接。

Q: 常量(一)?

A: 在舊版本(標準前)的C中,如果想建立一個常量,必須使用預處理器:

#define PI 3.14159

A: 在C和C++中都可以使用這個巨集。

A: 當使用預處理器創建常量時,我們在編譯器的範圍之外能控制這些常量。對名字PI上不進行類型檢查,也不能得到PI的地址,所以不能向PI傳遞一個指針和一個引用。

A: PI不能是用戶定義的類型變數。

A: PI的意義是從定義它的地方持續到文件結束的地方,預處理器並不識別作用域。

A: C++引入了常量來代替上面的巨集,常量就像變數一樣,只是它的值不能改變。修飾符const告訴編譯器這個名字表示常量,如果定義了某對象為常量,然後試圖修改它,編譯器就會報錯。

A: 必須用下麵的方式說明一個常量類型:

const int X = 10;

Q: 常量(二)?

A: 在標準C和C++中,可以在參數列表中使用常量,即使列表中的參數是指針或引用,也就是說,可以獲得從const的地址。const就像正常的變數一樣有作用域。

A: const是由C++採用,並加進標準C中。在C中,編譯器對待constant如同變數一樣,只不過帶有一個特殊的標記,意思是“不要改變我”。當在C中定義const時,編譯器為它創建存儲空間,所以如果在兩個不同的文件中或在頭文件中定義多個同名的const,鏈接器將生成剛發生衝突的錯誤消息。

A: 在C中使用const和在C++中使用const是完全不一樣的,簡而言之,在C++中使用的更好。

Q: 常量值?

A: 在C++中,一個const必須有初始值,在C中不是這樣的。

A: 內建類型的常量值可以表示為十進位、八進位、十六進位、浮點型或字元,不幸的是,二進位被認為是不重要的。

A: 如果沒有其他的線索,編譯器會認為常量值是十進位。

A: 常量值前帶0被認為是八進位。

A: 常量值前帶0x被認為是十六進位。

A: 浮點數可以含有小數點和指數冪(用e表示,意思是10的冪),小數點和e都可以任選。如果給一個浮點變數賦一個常量值,編譯器會取得這個常量值並把它轉換成浮點數,這個過程是隱式類型轉換(implicit type conversion),但是,使用小數點或e對於提醒讀者當前正在使用的是浮點數是一個好主意。

A: 合法的浮點常量值如:1e4、1.0001、47.0、0.0、-1.159e-77等等。我們可以對數加尾碼強加浮點數類型:f或F強加float型,L或l強加long double型,否則是double型。建議不要用小寫字母l,因為它看起來很像數字1。

A: 字元常量時用單引號括起來的字元,如'A'、'0'、' '等等。註意'0'和數值0之間存在巨大差別。

A: 用“反斜杠”表示一些特殊的字元,如:'\n'(換行)、'\t'(製表符)、'\\'(反斜杠)、'\r'(回車)、'"'(雙引號)、'''(單引號)等等,也可以用八進位表示字元常量,如'\17',或用十六進位表示字元常量,如'xff'。

Q: volatile變數?

A: 限定詞const告訴編譯器“這是不會改變的”,而限定詞volatile則告訴編譯器“不知道何時會改變”,防止編譯器依據變數的穩定性做任何優化。

A: 當讀在代碼控制之外的某個值時,例如讀一塊通信硬體中的寄存器,將使用這個關鍵字,無論何時需要volatile變數的值,都能讀到,即使在該行之前剛剛讀過。

A: “在代碼的控制之外”的某個存儲空間的一個特殊例子是在多線程程式中,如果正在觀察被另一個線程修改的特殊標識符,這個標識符應該是volatile,所以編譯器不會認為它能夠對標識符的多次讀入進行優化。

A: 註意當編譯器不進行優化是,volatile可能不起作用,但是當開始優化代碼時,如當編譯器開始尋找冗餘的讀入時,可以防止出現重大的錯誤。

A: 後面還會進一步闡述const和volatile關鍵字。

運算符及其作用

所有的運算符都會從它們的操作數中產生一個值。除了賦值、自增(自減)運算符之外,運算符所產生的值不會修改操作數。

修改操作數被稱為副作用(side effect),一般使用修改操作數的運算符就是為了產生這種副作用,但是應該記住它們所產生的值就像沒有副作用的運算符產生的值一樣都是可以使用的。

Q: 賦值運算符?

A: 賦值操作是由運算符“=”實現,這意味著“取右邊的值”並把它拷貝給左邊。

A: 右邊的值通常稱為右值(rvalue),同理也有左值(lvalue)的概念。

A: 右值可以是任意的常量、變數或能產生值的表達式,但是左值必須是一個明確命名的變數,也就是說應該有一個存儲數據的物理空間。

A: 例如,可以給一個變數賦值常量:

A = 4;

但是不能給常量賦任何值,因為它不能是左值,不能寫成如下:

4 = A;

Q: 數學運算符?

A: 基本的數學運算符: addition(+)、subtraction(-)、multiplication(*)、division(/)、multiplication(%)。

A: 整數相除會截取結果的整數部分,不捨入。

A: 浮點數不能使用取模運算符。
示例:FloatCanNotModulus.cpp,編譯結果link

A: C/C++也使用一種簡化的符號來同時執行操作和賦值,這是由一個運算符後面跟著一個賦值號來表示。例如:X += 4;

A: 示例:mathops.cpp,運行結果link

A: 註意,使用巨集PRINT()可以節省輸入和避免輸入錯誤。傳統上用大寫字母來命名預處理巨集以便突出它。後面我們很快會瞭解巨集有可能會變得很危險。

A: 跟在巨集後面的括弧中的參數會被閉括弧後面的所有代碼替代。只要在調用巨集的地方,預處理程式就刪除名字PRINT並替換代碼,所以使用巨集時編譯器不會報告任何錯誤信息,它並不對參數做任何類型檢查。

Q: 關係運算符?

A: 關係運算符在操作數之間建立一種關係。如果關係為真,則產生布爾值true;如果關係為假,則產生布爾值false。

A: 關係運算符有:<、>、<=、>=、==、!=

A: 參考:C/C++ 浮點數比較問題

Q: 邏輯運算符?

A: &&(邏輯與)、||(邏輯或)

A: 記住在C/C++中,如果語句是非零值則表示true,為零則為false

A: 示例:boolean.cpp

A: 我們可以用float或double代替int的定義,但是註意浮點數和零比較時很嚴格的,一個數和另一個數即使只有最小小數位不同仍然是“不相等”。

Q: 位運算符?

A: 因為浮點數使用一種特殊的內部格式,所以位運算符只適用於整型char、int和long。

A: 位運算符包括:&(位與運算符)、|(位或運算符)、^(位異或運算符xor)、~(非運算符,也稱補運算符)。

A: ~運算符是一個一元運算符,它只帶一個參數。

A: 位運算符可以和“=”結合來統一運算和賦值,如:&=、|=、^=都是合法運算。

A: 因為~是一元運算符,所以不能和=結合。

Q: 移位運算符(一)?

A: 左移位運算符(<<)引起運算符左邊的操作數向左移動,移動的位數由運算符後面的操作數指定。

A: 右移位運算符(>>)引起運算符左邊的操作數向右移動,移動的位數由運算符後面的操作數指定。

A: 如果移位運算符後面的值比運算符左邊的操作數的位數大,則結果是不定的。

A: 如果左邊的操作數是無符號的,右移是邏輯移位,所以最高位補零。

A: 如果左邊的操作數是有符號的,右移可能是也可能不是邏輯移位,行為是不確定的。

A: 移位可以和賦值號結合,<<=>>=,左值由左值按右值移位後的結果代替。

A: 示例:bitwise.cpp,運行結果link

A: 在main()中,變數都是unsigned的,這是因為一般來說,在使用位元組進行工作時並不希望用帶符號數。

A: 對於變數getval而言,可能要使用int來代替char,因為語句cin >> getval以另一種方式把第一個數字看成是一個字元,通過把getval賦值給a和b,該值被轉換為一個單獨的位元組。

Q: 移位運算符(二)?

A: 當移位越出數的一端時,那些位就會丟失,那些位掉進了神秘的位桶(bit bucket)里,丟棄在這個桶中的位有可能需要重用。

A: 操作位時,也可以執行旋轉(rotation),即在一端移掉的位插入到另一個端,好像它們在繞著一個迴路旋轉。儘管大多數電腦處理器提供了機器級的旋轉命令,但是在C/C++中,不直接支持旋轉。

A: 示例:

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 如果流圖片要載入失敗, 就會顯示找不到圖片的裂痕 代碼如下: 圖片目錄: 修改方法 : img標簽src為loading占點陣圖的地址,lay-src為正圖地址,圖片懶載入時會替換src<img src="loadingImg/loading.gif" lay-src="ddd.jpg"> 修改jsl ...
  • html代碼: js代碼: ...
  • layer彈出視窗在彈出時指定了area,彈出後,如果當前頁面(iframe)大小比彈出的視窗小,那麼就會出現無法操作彈出視窗的尷尬情況。如圖: 彈出視窗比當前頁面大,這時,唯有放大整個頁面才能看到完全的彈出視窗,才可以操作。 layui 為我們提供了 layer.style(); 方法來重新跳整窗 ...
  • 效果圖 ...
  • 目錄 js面向對象編程 js原型鏈 共用方法 原型繼承 class繼承 js面向對象編程 js原型鏈 共用方法 原型繼承 class繼承 js面向對象編程 js面向對象編程不同於 java 的類和對象 JavaScript 不區分類和實例的概念,而是通過原型(prototype)來實現面向對象編程。 ...
  • 一、引言 單例模式應該算是23種設計模式中比較簡單的,它屬於創建型的設計模式,關註對象的創建。 二、概念 單例模式是23個“Gang Of Four”的設計模式之一,它描述瞭如何解決重覆出現的設計問題,以設計靈活且可復用的面向對象軟體,使對象的實現、更改、測試和重用更方便。 單例模式解決了以下問題: ...
  • 面向對象有三大特性分別是繼承、封裝和多態。 (1)繼承:繼承是一種聯結類的層次模型,並且允許和鼓勵類的重用,它提供了一種明確表述共性的方法。對象的一個新類可以從現有的類中派生,這個過程稱為類繼承。新類繼承了原始類的特性,新類稱為原始類的派生類(子類),而原始類稱為新類的基類(父類)。派生類可以從它的 ...
  • 法一(本地sql查詢,註意表名啥的都用資料庫中的名稱,適用於特定資料庫的查詢) 法二(jpa已經實現的分頁介面,適用於簡單的分頁查詢) 法三(Query註解,hql語局,適用於查詢指定條件的數據) 可以自定義整個實體(Page<User>),也可以查詢某幾個欄位(Page<Object[]>),和原 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...