111.C語言_數據
第二章 數據node
- 2.1 數據類型
-
- 2.1.1 數據類型決定
- 1. 數據佔內存字節數
- 2. 數據取值範圍
- 3. 其上可進行的操做
- 2.2基本數據類型
- 2.2.1分類 基本類型
-
- 類型 符號 關鍵字 字節 16位/32位/64位 位數 32位 數的表示範圍 32位
- 整型
- 有
- [signed] int 2/4/4 4*8=32 -2^31 ~ 2^31-1
- [signed] short [int] 2/2/2 2*8=16 -2^15 ~ 2^15-1
- [signed] long [int] 4/4/4 32 -2^31 ~ 2^31-1
- [signed] long long [int] 8/8/8 64 -2^63 ~ 2^63-1
- 無
- unsigned short [int] 2/2/2 16 0 ~ 2^16-1
- unsigned int 2/4/4 32 0 ~ 2^32-1
- unsigned long [int] 4/4/8 32 0 ~ 2^32-1
- unsigned long long [int] 8/8/8 64 0 ~ 2^64-1
- 實型
- 有 float 4/4/4 32 0&土1.2e土38
- 有 double/longdouble有8有16 8/8/8 64 0&土1.2e土308
- 字符(變量)
- 有 [signed] char 1/1/1 8 -128~127 -2^7 ~ 2^7-1
- 無 unsigned char 1/1/1 8 0~255 0~ 2^8-1
- 字符(常量) /4/ 字節
- 指針類型
- 指針 無 char*/int*(指針變量) 2/4/8 32
- (1)指針大小
- 指針的大小是由內存尋址空間決定的,即地址總線決定。
- char*(即指針變量): 4個字節(32位的尋址空間是2^32,即32個bit,也就是4個字節。同理64位編譯器)
- 通常32位機尋址空間4G,因此指針佔4字節;
- 通常16位的單片機尋址空間是64k,因此指針佔2字節。
- (2)字符型符號
- C標準規定爲 Implementation Defined(由實做環境決定)
- •arm-linux-gcc規定 char 爲 unsigned char
- •vc 編譯器、x86上的 gcc 規定 char 爲 signed char
- 缺省狀況下,編譯器默認數據爲signed類型,可是char類型除外。
- 爲了代碼移植,必定不要用 char,用 signed char
- (3)鐵則
- int,long int,short int的寬度均可能隨編譯器而異。
- 但有幾條鐵定的原則(ANSI/ISO制訂的):
- •sizeof(shortint)<=sizeof(int)
- •sizeof(int)<=sizeof(longint)
- •shortint至少應爲16位(2字節)
- •longint至少應爲32位。
-
- 2.2.2 常量
-
- 2.2.2.1 分類
- 符號常量:
- 用標識符表明常量
- 定義格式: #define 符號常量 常量
- 直接常量:
- 整型常量
- 實型常量
- 字符常量
- 字符串常量
- 符號常量
- 2.2.2.2 整型常量
- (1)整型常量有3種形式:十進制整型常量、八進制整型常量和十六進制整型常量。
- (注意:c語言中沒有直接表示二進制的整型常量,在c語言源程序中不會出現二進制。)
- 整型常量後能夠用u或U明確說明爲無符號整型;用l或L明確說明爲長整型數
- 二進制:
- 全部數字由0,1構成,逢二進一,二進制數中不會出現2.。
- 例:110101
- 八進制:
- 以數字0(注意不是以字母O,o)開頭,全部數字由0~7構成,逢八進一,八進制數中不會出現8。
- 八進制整型常量:051 ,-026 ,0773 等
- 十進制:
- 全部數字由0~9構成,逢十進一,十進制數中不會出現10。
- 十進制整型常量:123 , 0 ,-24 , 85L(長整型常量)等
- 十六進制:以0x或者0X(數字0加字母x)開頭,
- 全部數字由0~9,A~F(或者a~f)構成,逢十六進一
- (其中A、B、C、D、E、F分別表明十、十一、十二、1三、1四、15)
- 十六進制整型常量:0x55 , 0x1101 , 0x , 0x5AC0 , -0xFF。
- 2.2.2.3 實型常量
- 實型常量有兩種表示形式:小數形式和指數形式。
- 編碼形式存儲。
- 小數形式:5.4 0.074 -23.0
- 指數形式:5.4e04.3e-3 -3.3e4 -3.3E4 e可大小寫
- (1)小數部分爲0的實型常量,能夠寫爲453.0 或453。
- (2)用小數表示時,小數點的兩邊必須有數,不能寫成「 .453「和「453.「,而應該寫成「0.453「和「453.0「。
- (3)用指數寫法時,e前必須有數字,e後面的指數必須爲整數
- (注意:整數階碼能夠是正數,負數,也能夠是八進制數、十六進制數,但必須爲整數)。
- 2.2.2.4 字符常量
- 字符常量的標誌是一對單引號‘ ’,c語言中的字符常量有兩類:
- (1) 由一對單引號括起來的一個字符,如‘a ’, ‘r’,‘#’。
- 注意: ′a′ 和 ′A′ 是兩個不一樣的字符常量。
- '1' 是字符佔一個字節,"1"是字符串佔兩個字節(含有一個結束符號)。
- '0' 的ASCII數值表示爲48,'a' 的ASCII數值是97,'A'的ASCII數值是65。
- 通常考試表示單個字符錯誤的形式:'65' "1"
- 字符是能夠進行算術運算的,記住:'0'-0=48
- 大寫字母和小寫字母轉換的方法:'A'+32='a' 相互之間相差32。
- (2)由一對單引號括起來,以反斜槓\開頭,後跟若干數字或者字母,好比‘\n’,其中「\「是轉義的意思,後面跟不一樣的字符表示不一樣的意思,這類字符常量叫轉義字符。具體如圖所示 。
- ‘A’ = ‘\101’ = ‘\x41’ = 65
- 在程序中 int a = 0x6d,是把一個十六進制的數給變量a 注意這裏的0x必須存在。
- 在程序中 int a = 06d, 是一個八進制的形式。
- 在轉義字符中
- ’\x6d’ 纔是合法的,0不能寫,而且x是小寫。
- ‘\141’ 是合法的, 0是不能寫的。
- ‘\108’是非法的,由於不能夠出現8。
- 2.2.2.5 字符串常量
- C語言中,以雙引號括起來的,由若干個字符組成的序列即爲字符串常量。
- 字符串的概念
- 簡單而言,字符串是若干有效字符的序列,可包含轉義字符、ASCⅡ碼錶中的字符;
- 形式爲: 用雙引號括起來的字符序列;
- 例:"I am a student." , "Hello "
- "a[5]="; "%f\n"。
- 字符串的結束標誌:‘\0’。
- 2.2.2.6 符號常量
- 符號常量是由宏定義「#define「定義的常量,在C程序中可用標識符表明一個常量。
- 例:計算圓的面積的c程序。
- #include<stdio.h>
- #define PI 3.14159
- main()
- {
- float r,s;
- r=12.5;
- s=PI*r*r;
- printf("s= %f",s);
- }
- 說明:
- #define 是宏定義,此程序中全部出現PI的地方都表明3.14159,同時PI稱爲符號常量。習慣上咱們用大寫字母來表示符號常量,小寫字母表示變量,這樣比較容易區別。
- define f(x)(x*x) 和 define f(x) x*x 之間的差異。必定要好好的注意這寫容易錯的地方,替換的時候有括號和沒有括號是很大的區別。
- 2.2.3 變量
- 定義
- 變量就是其值能夠改變的量。變量要有變量名,在內存中佔據必定的存儲單元,存儲單元裏存放的是該變量的值。不一樣類型的變量其存儲單元的大小不一樣,變量在使用前必須定義。
- 2.2.3.1 整型變量
- 整型變量分爲4種:
- 基本型(int)、
- 短整型(short int 或short)、
- 長整型(long int 或 long)
- 無符號型(unsigned int ,unsigned short,unsigned long)。
- 不一樣的編譯系統對上述四種整型數據所佔用的位數和數值範圍有不一樣的規定。
- 說明:
- 單詞signed來講明「有符號」(即有正負數之分),不寫signed也隱含說明爲有符號,unsigned用來講明「無符號」(只表示正數)。
- 2.2.3.2 實型變量
- C語言中,實型變量分爲單精度類型( float )和雙精度類型( double )兩種。如:
- 單精度實數提供7位有效數字,雙精度實數提供15~16位有效數字。
- 實型常量不分float型和double型,一個實型常量能夠賦給一個float 型或double型變量,但變量根據其類型截取實型常量中相應的有效數字。
- 注意:實型變量只能存放實型值,不能用整型變量存放實型值,也不能用實型變量存放整型值。
- 2.2.3.3 字符變量
- 字符變量用來存放字符常量,定義形式:
- 其中關鍵字char定義字符型數據類型,佔用一個字節的存儲單元。
- 例:char cr1,cr2;
- cr1= ‘A’ , cr2=‘B’ ;
- 將一個字符賦給一個字符變量時,並非將該字符自己存儲到內存中,而是將該字符對應的ASCII碼存儲到內存單元中。
- 例如,字符 ′A′ 的ASCII碼爲65,在內存中的存放形式以下:01000001
- 因爲在內存中字符以ASCII碼存放,它的存儲形式和整數的存儲形式相似,因此C語言中字符型數據與整型數據之間能夠通用,一個字符能用字符的形式輸出,也能用整數的形式輸出,字符數據也能進行算術運算,此時至關於對它們的ASCII碼進行運算。
- 2.2.4.4 類型的自動轉換和強制轉換
- 當同一表達式中各數據的類型不一樣時,編譯程序會自動把它們轉變成同一類型後再進行計算。轉換優先級爲:
- 即下邊級別「低「的類型向上邊轉換。具體地說,若在表達式中優先級最高的數據是double型,則此表達式中的其餘數據均被轉換成double型,且計算結果也是double型;若在表達式中優先級最高的數據是float型,則此表達式中的其餘數據均被轉換成float型,且計算結果也是float型。
- 在作賦值運算時,若賦值號左右兩邊的類型不一樣,則賦值號右邊的類型向左邊的類型轉換;當右邊的類型高於左邊的類型時,則在轉換時對右邊的數據進行截取。
- 除自動轉換外,還有強制轉換,表示形式是:
- 通常形式:(類型說明符)(表達式)
- 功能:把表達式的運算結果強制轉換成類型說明符所表示的類型
- 例:(int)(a+b)
- 必定是 (int)a 不是 int(a),注意類型上必定有括號的。
- 注意(int)(a+b) 和(int)a+b 的區別。 前是把a+b轉型,後是把a轉型再加b。
- 例1表達式(int)((double)(5/2)+2.5)的值是4。
- (int)((double)(5/2)+2.5)
- →(int)((double)2)+2.5)
- →(int)(2.000000+2.5)
- →(int)(4.500000)
- →4。
- 例2:如下程序運行後的輸出結果是(3) 。
- main()
- { int a;
- a=(int)((double)(3/2)+0.5+(int)1.99*2);
- printf("%d\n",a);
- }
- (3/2)=1,
- (double)(3/2)+0.5=1.5,
- (int)1.99*2=2,(double)(3/2)+0.5+(int)1.99*2=3.5,故a=3。
- 三種取整丟小數的狀況:
- 1、int a=1.6;
- 2、(int)a;
- 3、1/2; 3/2; //除法
- 2.2.4.5 全局變量和局部變量
- 在C語言中,用戶命名的標識符都有一個有效的做用域。
- 所謂標識符的「做用域」就是指程序中的某一部分,在這部分中,該標識符是有定義的,能夠被C編譯和鏈接程序所識別。
- 咱們知道每一個變量都有本身的做用域,在一個函數內定義的變量,不能在其餘函數中引用。顯然,變量的做用域與其定義語句在程序中出現的位置有直接的關係,據此變量能夠劃分爲局部變量和全局變量。
- 注意「定義」和 「說明」兩個詞的區別。「定義」是指給變量分配肯定的存儲單元;「說明」只是說明變量的性質。
- (一)局部變量
- 在一個函數內部定義的變量,它們只在本函數範圍內有效,即只有本函數才能使用它們,其餘函數不能使用這些變量,咱們將這些變量稱爲「局部變量」。不一樣函數中可使用相同名字的局部變量,它們表明不一樣的對象,在內存中佔不一樣的單元,互不干擾。
- (二)全局變量
- 在函數以外定義的變量稱爲外部變量,外部變量是全局變量。全局變量能夠爲本文件中其餘函數所共用,它的有效範圍從定義變量開始到本文件結束。
- 若是在同一個源文件中,外部變量與局部變量同名,則在局部變量的做用範圍內,外部變量被「屏蔽」,即它不起做用。
- 變量做用域和生存期
- 生存期:何時這個變量開始出現了,到何時消亡了
- 做用域: 在(代碼的) 什麼範圍內能夠訪問這個變量(這個變量能夠起做用)
- 對於本地變量,這兩個問題的答案是統一的:大括號內一塊
- 2.2.4.6 變量的存儲類別
- 變量值存在的時間(即生存期)
- 靜態存儲方式和動態存儲方式。
- 所謂靜態存儲方式是指在程序運行期間分配固定的存儲空間的方式,而動態存儲方式是在程序運行期間根據須要動態分配存儲空間的方式
- 在內存中供用戶使用的空間能夠分爲程序區、靜態存儲區和動態存儲區3個部分。數據分別被存放在靜態存儲區和動態存儲區中。靜態存儲區中存放的是全局變量,在程序開始執行時就給全局變量分配存儲區。程序執行過程當中它們佔據固定的存儲單元,程序執行完畢這些存儲單元就被釋放。
- 每個變量和函數所具備的屬性是:數據的存儲類別和數據類型(在前面已經介紹過)。所謂的存儲類別指的是數據在內存中存儲的方法,其可分爲兩類:靜態存儲類和動態存儲類。具體包括自動(auto)、靜態(static)、寄存器(register)和外部(extern),共4種。
- (一)auto變量
- 當在函數內部或複合語句內定義變量時,若是沒有指定存儲類別,或使用了auto說明符,系統就認爲所定義的變量具備自動類別。如:
- float a;等價於auto float a;
- auto變量的存儲單元被分配在內存的動態存儲區,每當進入函數體(或複合語句)時,系統自動爲auto變量分配存儲單元,退出時自動釋放這些存儲單元另作他用。所以,這類局部變量的做用域是從定義的位置起,到函數體(或複合語句)結束止。
- 全部自動類局部變量的存儲單元都是在進入這些局部變量所在的函數體(或複合語句)時生成,退出其所在的函數體(或複合語句)時消失(變爲無定義)。這就是自動類局部變量的「生存期「。當再次進入函數體(或複合語句)時,系統將爲它們另行分配存儲單元,所以變量的值不可能被保留。隨着函數的頻繁調用,動態存儲區內爲某個變量分配的存儲單元位置會隨程序的運行而改變。
- (二)register變量
- 寄存器變量也是自動類變量。它與auto變量的區別僅在於:用register說明變量是建議編譯程序將變量的值保留在CPU的寄存器中,而不是像通常變量那樣佔用內存單元。程序運行時,訪問寄存器內的值要比訪問內存中的值快得多。所以,當程序對運行速度有較高要求時,把那些頻繁引用的少數變量,指定爲register變量,有助於提升程序運行的效率。
- 說明:
- (1)CPU中寄存器的數目是有限的,所以只能說明少許的寄存器變量。在同一個函數中,容許說明爲寄存器變量的數目不只取決於CPU的類型,也與所用的C編譯程序有關。當沒有足夠的寄存器來存放指定的變量,或編譯程序認爲指定的變量不適合放在寄存器中時,將自動按auto變量來處理。所以,register說明只是對編譯程序的一種建議,而不是強制性的。
- (2)因爲register變量的值存放在寄存器內而不是存放在內存中,因此register變量沒有地址,也就不能對它實行求地址運算。
- (3)register變量的說明應儘可能靠近其使用的地方,用完以後儘快釋放,以便提升寄存器的利用效率。
- (三)靜態存儲類別的局部變量
- 當函數體(或複合語句)內部用static來講明一個變量時,能夠稱該變量爲靜態局部變量。它與auto變量、register變量的本質區別是:
- (1)在整個程序運行期間,靜態局部變量在內存中的靜態存儲區中佔據着永久性的存儲單元。即便退出函數後,下次再進入該函數時,靜態局部變量仍使用原來的存儲單元。因爲不釋放這些存儲單元,這些存儲單元中的值得以保留,於是能夠繼續使用存儲單元中原來的值。由此可知,靜態局部變量的生存期將一直延長到程序運行結束。
- (2)靜態局部變量的初值是在編譯時賦予的,在程序執行期間再也不賦以初值。對未賦值的局部變量,C語言編譯程序自動給它賦初值爲0。
- (四)用static聲明外部變量
- 有時在程序設計中但願某些外部變量只限於本文件使用,而不能被其餘文件引用,這時能夠在定義外部變量時加一個static聲明。
- 並非對外部變量加上static纔是靜態存儲而不加static的是動態存儲。兩種形式的外部變量都是靜態存儲方式,只是做用範圍不一樣而已。
- (五)用extern聲明外部變量
- ① 在一個文件內聲明外部變量
- 當全局變量定義在後,引用它的函數在前時,應該在引用它的函數中用extern對此全局變量進行說明,以便通知編譯程序,該變量是一個已在外部定義了的全局變量,已經分配了存儲單元,不須要爲它另開闢存儲單元。這時其做用域從extern說明處起,延伸到該函數末尾。
- 全局變量的說明與全局變量的定義不一樣。變量的定義(開闢存儲單元)只能出現一次,在定義全局變量時,不可以使用extern說明符;而對全局變量的說明,則能夠屢次出如今須要的地方,這時必須用extern進行說明。
- ② 在多文件的程序中聲明外部變量
- 當一個程序由多個單獨編譯的源文件組成,而且在每一個文件中均須要引用同一個全局變量時,若在每一個文件中都定義了所需的同名全局變量,則在「鏈接」時將會產生「重複定義」的錯誤。在這種狀況下,單獨編譯每一個文件時並沒有異常,編譯程序將按定義分別給它們開闢存儲空間,而當進行鏈接時,就會顯示出錯信息。解決的方法是:在其中一個文件中定義全部全局變量,而在其餘用到這些全局變量的文件中用extern對這些變量進行說明。
- 2.3構造類型
- 2.3.1 數組
- 2.3.1.1 一維數組的定義和引用
- (一)數組的概念
- 數組是由屬於同一個數據類型的有序數據集構成的。數組中的每個數據稱爲「元素「。能夠用一個統一的數組名和下標來惟一地標識數組中的元素。
- (二)一維數組的定義
- 一維數組的定義方式爲:
- 類型說明符 數組名[常量表達式];
- 如:
- c是數組名,此數組共有20個元素,而且每一個元素的類型都爲字符型。
- (三)一維數組元素的引用
- 數組元素的表示形式爲:
- 數組名[下標];
- 引用數組元素時,數組的下標能夠是整型常量,也能夠是整型表達式。
- 和變量同樣,數組必須先定義後使用。數組元素只能逐個引用而不能把數組當作一個總體一次引用。
- (四)一維數組的初始化
- 當數組定義後,系統會爲該數組在內存中開闢一串連續的存儲單元,但這些存儲單元中並無肯定的值。能夠在定義數組時爲所包含的數組元素賦初值,如:
- int a[6]={ 0,1,2,3,4,5 };
- 所賦初值放在一對花括號中,數值類型必須與所說明類型一致。所賦初值之間用逗號隔開,系統將按這些數值的排列順序,從a[0]元素開始依次給a數組中的元素賦初值。以上語句將a[0]賦值0,a[1]賦值1, …… ,a[5]賦值5。在指定初值時,第一個初值一定賦給下標爲0的元素。也就是說數組元素的下標是從0開始的。同時,不可能跳過前面的元素給後面的元素賦初值,可是容許爲前面元素賦值爲0。當所賦初值個數少於所定義數組的元素個數時,將自動給後面的其餘元素補以初值0;當所賦初值個數多於所定義數組的元素個數時,也就是說超出了數組已經定義的範圍,在編譯時系統將給出出錯信息。
- C語言規定能夠經過賦初值來定義數組的大小,這時一對方括號中能夠不指定數組大小。
- 2.3.1.2 一維數組與函數
- 1、 一維數組元素做爲實參
- 不管是一維數組元素或者二維數組元素,和普通變量的使用沒有任何區別,他們之間僅僅是變量名不一樣,一維數組元素做爲實參傳遞給形參,形參數據改變不會影響實參變化。
- 2、一維數組元素地址做爲實參
- 一維數組元素的地址做爲實參,對應的形參必須是與實參基類型相同的指針變量。此時能夠經過被調用函數改變調用函數中的數據。
- 例 有如下程序段
- #include<stdio.h>
- voidfun(int x,int *p)
- {
- x*=2;
- p[0]=p[-1]+p[1];
- }
- main()
- {
- int a[10]={1,2,3,4,5,6,7,8,9,10};
- fun(a[2],&a[6]);
- printf("%d %d\n",a[2],a[6]);
- }
- 程序運行後的輸出結果是 3 14
- 本題主函數中有函數調用 fun (a[2],&a[6]);
- 第一個傳遞的實參是a[2]的值3,對原數不影響
- 第二個傳遞的實參是&a[6] (a[6]的地址),指針變量p存儲a[6]的地址,根據指針變量加下標表示數據的方法,p[0]存儲的是a[6]的值,p[-1]存儲的是a[5]的值6,p[1]存儲的是a[7]的值8。通過p[0]=p[-1]+p[1]計算;值爲14。
- 3、一維數組名做爲實參
- 一維數組名爲地址常量,表示數組的首地址,若是一維數組名做爲實參,對應的形參應該是一個指針變量,此指針變量的基本類型必須與數組的類型一致。
- 一般被調用函數的首部能夠有如下3中方式:
- (1)fun(int *a)
- (2)fun(int a[N])
- (3)fun(int a[ ])
- 例 有如下程序段
- #include<stdio.h>
- int fun(int *x,int n)
- {
- int i , sum = 0 ;
- for (i=0;i<n;i++)
- sum=sum+x[i];
- return sum;
- }
- main()
- {
- int a[]={1,2,3,4,5 },s=0;
- s=fun (a,5);
- printf("%d \n",s);
- }
- 程序運行後的輸出結果是15
- 本題main函數中定義了一維數組a,含有5個int類型的數組元素,因此地址常量a的基本類型爲int類型,對應的形參x的基本類型也是int類型,能夠進行參數的傳遞,傳遞後依然用指針變量加下標的方式表示數據。
- 2.3.1.3 二維數組的定義和引用
- (一)二維數組的定義
- 在C語言中,二維數組中元素排列的順序是:按行存放,即在內存中先順序存放第一行的元素,再存放第二行的元素。所以,二維數組元素的存儲與一維數組元素存儲相相似,老是佔用一塊連續的內存單元。
- 二維數組的通常形式爲:
- 類型說明符 數組名[常量表達式][常量表達式];
- 如:int c[3][4]; 定義c爲3×4 (3行4列)的數組。
- 注意:不能寫成c[3,4]。C語言對二維數組採用這樣的定義方式:咱們能夠把二維數組當作是一種特殊的一維數組。
- 例如,能夠把c當作是一個一維數組,它有3個元素c[0]、c[1]、c[2],每一個元素又是一個包含4個元素的一維數組。能夠把c[0]、c[1]、c[2]看作是3個一維數組的名字。
- (二)二維數組的引用
- 二維數組的表示形式爲:
- 數組名[下標][下標]
- 數組的下標能夠是整型表達式,如 c[3-1][3×2-2];
- 數組元素能夠出如今表達式中,也能夠被賦值。
- 定義數組時用的c[3][4]和引用元素時的c[3][4]的區別:前者用來定義數組的維數和各維的大小,共有3行4列;後者中的3和4是下標值,c[3][4]表明該數組中的一個元素。若是a[3][4]是二維數組中最後一個元素,那麼該數組共有4行5列。
- (三)二維數組的初始化
- 能夠在定義二維數組的同時給二維數組的各元素賦初值。
- 如:
- float m[2][2]={{1.5,3.2},{0.8}};
- 所有初值放在一對花括號中,每一行的初值又分別括在一對花括號中,之間用逗號隔開。當某行一對花括號內的初值個數少於該行中元素的個數時,系統將自動地給後面的元素補初值0。一樣,不能跳過每行前面的元素而給後面的元素賦初值。
- (四)經過賦初值定義二維數組的大小
- 對於一維數組,能夠在數組定義語句中省略方括號中的常量表達式,經過所賦初值的個數來肯定數組的大小;對於二維數組,只能夠省略第一個方括號中的常量表達式,而不能省略第二個方括號中的常量表達式。
- 如:
- int a[][3]={{1,2,3},{4,5},{6},{8}};
- a數組的第一維方括號中的常量表達式省略,在所賦初值中,含有4個花括號,則第一維的大小由花括號的個數來決定。所以,該數組實際上是與a[4][3]等價的。當用如下形式賦初值時:
- 第一維的大小按如下規則決定:
- (1)當初值的個數能被第二維的常量表達式的值除盡時,所得商數就是第一維的大小。
- (2)當初值的個數不能被第二維的常量表達式的值除盡時,則:
- 所以,按此規則,以上c數組第一維的大小應該是2,也就是說語句等同於
- int c[2][3]={{1,2,3},{4,5}};。
- 2.3.1.4 二維數組與函數
- 二維數組名做爲實參
- 二維數組元素做爲實參,與普通變量做爲實參沒有任何區別,二維數組名爲行指針常量,若是二維數組名做爲實參,對應的形參必須是一個行指針變量。
- 例如:有如下定義和函數調用語句:
- #define M 5
- #define N 3
- main()
- {
- int s[M][N];
- …….
- fun(s);
- …….
- }
- 此時,實參爲二維數組名(即行指針),則fun函數的首部能夠是如下3中形式之一:
- (1)fun ( int (*a)[N])
- (2) fun ( int a[ ][N])
- (3) fun ( int a[M][N])
- 注意:行下標能夠省略,列下標不能夠省略。不管哪一種方式,系統都把a看做一個行指針變量。
- 2、指針數組名做爲實參
- 指針數組名師指向指針的指針常量,所以當指針數組名做爲實參時,對應的形參應該爲一個指向指針的指針變量。
- 例如:有如下定義和函數調用語句:
- #define M 5
- #define N 3
- main()
- {
- int s[M][N],*p[M];
- …….
- for(i=0;i<M;i++) p[i]=s[i];
- fun(p);
- …….
- }
- 此時,實參爲一維數組名(即行指針),則fun函數的首部能夠是如下3中形式之一:
- (2)fun ( int *a[M])
- (2) fun ( int *a[ ])
- (3) fun ( int **a)
- 2.3.1.5 字符數組
- 注:C語言無字符串類型,字符串是存放在字符數組中的。
- (一)字符數組的定義
- 字符數組就是數組中的每一個元素都是字符,定義方法同普通數組的定義相同,即逐個對數組元素賦值。如:
- char c[11];
- c爲該數組名,該數組共有11個元素,而且每一個元素都爲字符型。
- (二)字符數組的初始化及引用
- 對字符數組初始化,可逐個元素地賦值,即把字符逐個賦給數組元素。如:
- char a[9]={ ′T′, ′h′, ′a′, ′n′, ′k′, ′′, ′y′, ′o′,′u′};
- 若是花括號中提供的初值個數(即字符個數)大於數組長度,則按語法錯誤處理。
- 若是初值個數小於數組長度,則將這些字符賦給數組中前面那些元素,其他的元素自動定爲空字符(′\0′)。如:
- char c[6]={′G′,′o′,′o′,′d′};
- 字符數組的引用形式與其餘數組的引用形式相同,採用下標引用,
- 即 數組名[下標]。
- 例如:
- #include<stdio.h>
- main()
- { char c[9]={'T','h','a','n','k',',','y','o','u'};
- int i;
- for(i=0; i<9; i++)
- printf(「%c「,c[i]);
- }
- 輸出的結果爲Thank,you。
- (三)字符串和字符串結束標誌
- C語言中,將字符串做爲字符數組來處理。爲了測定字符串的實際長度,C語言規定了一個字符串結束標誌,以字符'\0'表明。就是說,在遇到字符'\0'時,表示字符串結束,由它前面的字符組成字符串。
- 系統對字符串常量也自動加一個'\0'做爲結束符。
- 例如:
- char c[]=「c program「;
- 數組c共有9個字符,但在內存中佔10個字節,最後一個字節'\0'是由系統自動加上的。有告終束標誌'\0'後,在程序中每每依靠檢測 '\0'的位置來斷定字符串是否結束,而不是根據數組的長度來決定字符串長度。
- 說明:'\0'表明ASCII碼爲0的字符,是一個「空操做符「,它什麼也不幹。在輸出時也不輸出'\0',它只是一個結束的標誌。
- (四)字符數組的初始化
- 方法:將字符常量以逗號分隔寫在花括號中
- ①在定義字符數組時進行初始化
- charch[7]={‘s’,’t’,’u’,’d’,’e’,’n’,’t’};
- ②在對所有元素指定初值時,可省寫數組長度。
- char ch[]={‘s’,’t’,’u’,’d’,’e’,’n’,’t’};
- ③若是花括弧內提供的初值個數大於數組長度?
- (五)用字符串來直接初始化字符數組
- 可直接把字符串寫在花括號中來初始化字符數組.
- 系統將雙引號括起來的字符依次賦給字符數組的各個元素, 並自動在末尾補上字符串結束標誌字符'\0'。
- 幾點說明:
- (1)字符串結束標誌'\0'僅用於判斷字符串是否結束,輸出字符串時不會輸出。
- (2)在對有肯定大小的字符數組用字符串初始化時,數組長度應大於字符串長度。如: char s[7]={"student"};是錯誤的.
- (3)在初始化一個一維字符數組時,能夠省略花括號。
- 如: char s[8]="student";
- ( 4 )不能直接將字符串賦值給字符數組名。下面的操做是錯誤的。
- 如: s=」student」;
- 2.3.1.6 注意
- 兩種重要的數組長度
- char a[]={‘a’,’b’,’c’}; //不安全,無 \0
- 數組長度爲3,字符串長度不定。sizeof(a)爲3。
- char a[5]={ ‘a’,’b’,’c’}
- 數組長度爲5,字符串長度3。sizeof(a)爲5。
-
- #include"stdio.h"
- #include"string.h"
- main()
- {
- char m[] = "abc";
- // char n[] = {'a','b','c','\0'}; //4 4 3 3
- char n[] = {'a','b','c'}; //4 3 3 3
- printf("%d %d\n",sizeof(m),sizeof(n));
- printf("%d %d\n",strlen(m),strlen(n));
- return 0;
- }
- 數組的重要概念
- 對a[10]這個數組的討論。
- 1、a表示數組名,是第一個元素的地址,也就是元素a[0]的地址。
- 2、a是地址常量,因此只要出現a++,或者是a=a+2賦值的都是錯誤的。
- 3、a是一維數組名,因此它是列指針,也就是說a+1是跳一列。
- 對a[3][3]的討論。
- 1、a表示數組名,是第一個元素的地址,也就是元素a[0] [0]的地址。
- 2、a是地址常量,因此只要出現a++,或者是a=a+2賦值的都是錯誤的。
- 3、a是二維數組名,因此它是行指針,也就是說a+1是跳一行。
- 4、a[0]、a[1]、a[2]也都是地址常量,不能夠對它進行賦值操做,同時它們都是列指針,a[0]+1,a[1]+1,a[2]+1都是跳一列。
- 5、注意a和a[0] 、a[1]、a[2]是不一樣的,它們的基類型是不一樣的。前者是一行元素,後三者是一列元素。
- 數組的初始化
- 一維和二維的,一維能夠不寫,二維第二個必定要寫
- int a[]={1,2} 合法。
- int a[][4]={2,3,4}合法。
- int a[4][]={2,3,4}非法。
- 二維數組中的行指針
- int a[1][2];
- 其中a如今就是一個行指針,a+1跳一行數組元素。 搭配(*p)[2]指針
- a[0],a[1]如今就是一個列指針。a[0]+1 跳一個數組元素。搭配*p[2]指針數組使用
- 脫衣服法則
- a[2] 變成 *(a+2)
- a[2][3]變成 *(a+2)[3]再能夠變成 *(*(a+2)+3)
- 這個思想很重要!
- 2.3.2結構體
- 2.3.2.1 結構體類型
- 在實際工做中,當咱們須要把一些不一樣類型,但相互之間又存在着聯繫的信息組合應用時,就要用到結構體。結構體是一種看似複雜但卻很是靈活的構造型數據類型。在一般狀況下,一個結構體類型由若干個稱爲成員(或稱爲域)的部分組成。不一樣的結構體類型可根據須要由不一樣的成員組成。但對於某個具體的結構體類型,其成員的數量必須固定,這一點與數組相同;但該結構體中各個成員的類型能夠不一樣,這是結構體與數組的重要區別。例如,咱們經常使用的「時間「能夠由如下3個部分描述:小時(hour)、分(minute)、秒(second)。它們均可以用整型數表示,能夠把這3個成員組成一個總體,並給它取名爲time,這就是一個簡單的結構體。
- 2.3.2.2 結構體聲明
- 聲明一個結構體類型的通常形式爲:
- struct 結構體名
- { 成員表列 };
- struct是C語言中的關鍵字,是結構體類型的標誌。「結構體名「用作結構體類型的標誌,它又稱「結構體標記「(structure tag)。大括弧內是該結構體中的各個成員,成員表列是由若干個變量類型名及變量名組成的。這些成員共同組成一個結構體。
- 例如,上面提到的「時間「結構體類型能夠說明以下:
- structtime
- {
- inthour;
- intminute;
- intsecond;
- };
- 其中,time就是結構體名, hour、minute、second都是成員,而且各成員都應進行類型聲明,每一個成員也就是結構體中的一個域。
- 成員名命名規則與變量名相同。因此結構體類型也能夠用如下形式說明:
- struct 結構體標識名
- {
- 類型名1 結構體成員名錶1;
- 類型名2 結構體成員名錶2;
- ……
- 類型名n 結構體成員名錶n;
- };
- 說明:
- (1)「結構體標識名」和「結構體成員名錶」都必須是合法的用戶定義的標識符。
- (2)每一個「結構體成員名錶「中均可以含有多個同類型的成員名,它們之間以逗號分隔。
- (3)結構體類型說明中的「類型名1」~「類型名n」,不只能夠是簡單數據類型,也能夠是某種結構體類型。當結構體說明中又包含結構體時,稱爲結構體的嵌套。
- (4)ANSI C標準規定結構體至多容許嵌套15層,而且容許內嵌結構體成員的名字與外層成員的名字相同。
- 2.3.2.3 結構體類型變量的定義
- 前面只是指定了一個結構體類型,爲了能在程序中使用結構體類型的數據,就須要定義結構體類型的變量,並在其中存放具體的數據。能夠用以下方法定義結構體類型變量。
- (一)先聲明結構體類型再定義變量名
- 如上面已經定義了一個結構體類型struct time,能夠以下定義:
- struct time time1,time2;
- 結構體類型名 結構體變量名;
- time1和time2爲struct time類型變量,即它們都具備struct time類型的結構。
- (二)在聲明類型的同時定義變量
- 其通常形式爲:
- struct 結構體名 { 成員表列 } 變量名錶列;
- (三)直接定義結構體類型變量
- 其通常形式爲: struct { 成員表列 } 變量名錶列;
- 即不出現結構體名。
- 類型與變量是兩個不一樣的概念,使用時應注意區別。只能對變量賦值、存取或運算,而不能對一個類型進行賦值、存取或運算。能夠單獨使用結構體中的成員,它與普通變量的做用相同。
- 2.3.2.4 結構體變量引用
- 在定義告終構體變量之後,固然能夠引用這個變量。但應注意:
- (1)結構體變量不能做爲一個總體而對其進行任何操做,只能對結構體變量中的各個成員分別進行輸入和輸出等操做。結構體變量中的成員用如下方式引用:
- (2)若是結構體的某個成員自己又是一個結構體類型,則可使用若干個成員運算符一級一級地找到最低的一級成員,只能對最低一級的成員進行賦值或存取及運算。
- (3)結構體變量的初始化,是指逐個對結構體變量的各個成員進行初始化的過程。
- 2.3.2.5 結構體數組
- 和普通數組同樣,結構體數組中的每一個元素都屬於同一數據類型(結構體類型),只不過各個元素自己又都包含多個成員項。例如,一個結構體變量中存放着一組數據(如某產品的名稱、型號、尺寸、顏色等數據),如今若是有10個這樣產品的數據須要參加運算,顯然應當用到結構體數組。和定義結構體變量的方法相仿,只需說明其爲數組便可。
- 其通常形式爲:
- struct 結構體變量名 { 成員表列} 數組名[常量表達式];
- 結構體數組的初始化
- 結構體數組的初始值應順序地放在一對花括號中,因爲數組中的每個元素都是一個結構體,所以一般將其成員的值依次放在一對花括號中,以便區分各個元素。
- 2.3.2.6 指向結構體類型數據的指針
- 一個結構體變量的指針就是用來指向該結構體類型的存儲單元,並指向結構體變量所佔據的內存段的起始地址。
- (一)指向結構體變量的指針
- 「結構體變量.成員名」、「(*結構體指針變量名).成員名「和「結構體指針變量名->成員名」這3種形式是等價的,其中「->「稱爲指向運算符,它由兩部分組成:「-」減號和「>」大於號,它們之間不能有空格
- 看下面的例子:
- #include<stdio.h>
- #include<string.h>
- main()
- {
- struct objects
- {
- char name[20];
- int size;
- char color[10];
- float weight;
- float height;
- };
- struct objects obj1;
- struct objects *p;
- p=&obj1;
- strcpy(obj1.name,"pen");
- obj1.size=10;
- strcpy(obj1.color,"black");
- obj1.weight=50.5;
- obj1.height=18.5;
- printf("name: %s\nsize: %d\ncolor:%s\nweight: %f\nheight:%f\n",obj1.name,obj1.size,obj1.color,obj1.weight,obj1.height); printf("name: %s\nsize: %d\ncolor: %s\nweight:%f\nheight: %f\n",(*p).name,(*p).size,(*p).color,(*p).weight,(*p).height);
- }
- 咱們聲明瞭一個struct objects類型,而且定義了一個該類型的變量obj1,又定義了一個指向struct objects類型的數據的指針p,而且將p指向obj1,接下來是對各成員賦值。第一個printf語句用「.」的方式將obj1的成員的值輸出。第二個printf語句用(*p)將obj1的成員的值輸出,由於成員運算符「.」的優先級高於「*」運算符,因此(*p)的兩側的圓括號不能省略。以上兩個printf函數語句的輸出結果是相同的,咱們可用p->name來代替(*p).name
- (二)指向結構體數組的指針
- 結構體數組及其元素也能夠用指針變量來指向。在使用指針變量指向結構體數組時,只要把該結構體數組中的每一個元素當作普通的結構體變量使用就能夠了,例如:
- #include<stdio.h>
- #include<string.h>
- structobjects
- {
- char name[20];
- int size;
- char color[10];
- float weight;
- float height;
- };
- structobjects obj[3]= {{"pen",10,"black",50.5,18.5},
- {"notebook",20,"blue",180,19.5},
- {"bag",50,"red",2000,37.5}};
- main()
- {
- struct objects *p;
- printf("name size color weightheight\n");
- for(p=obj;p<obj+3;p++)
- printf("%10s%d%-20s%6.5f%6.5f\n",p->name,p->size,p->color,p->weight,p->height);
- }
- 這樣就能夠利用指針變量來逐個把結構體數組中的元素的各個域輸出。
- 說明:
- 若是p的初值爲obj,即指向第一個元素,則p+1就指向下一個元素。
- 例如:
- (++p)->name;先使p自加1,而後獲得它指向的元素中的name成員值。
- 而(p++)->name;先獲得p->name的值,而後使p自加1,指向obj[1];。
- p只能指向一個structobjects類型的數據,不能指向obj數組元素中的某一成員(即p的地址不是成員的地址)。例如,p=&obj[1].name;是不對的。對結構體變量中的每一個成員,均可以像普通變量同樣,對它進行同類變量所容許的任何操做。
- (三)用結構體變量和指向結構體的指針做爲函數參數
- 將一個結構體變量的值傳遞給另外一個函數,有以下方法:
- (1)結構體變量的成員做爲實參傳遞給主調函數。
- (2)能夠用結構體變量做爲一個總體實參。
- (3)C語言中,容許將結構體變量的地址做爲實參傳遞,這時,對應的形參應該是一個基類型相同的結構體類型的指針。
- 2.3.3共用體
- 共用體的類型說明和變量的定義方式與結構體的類型說明和變量定義的方式徹底相同。不一樣的是,結構體中的成員各自佔有本身的存儲空間,而共用體的變量中的全部成員佔有同一個存儲空間。能夠把一個整型變量、一個字符型變量、一個實型變量放在同一個地址開始的內存單元中。以上3個變量在內存中所佔的字節數不一樣,但都從同一個起始地址開始存放,也就是使用覆蓋技術,幾個變量相互覆蓋。
- (一)共用體類型的說明
- 共用體類型說明的通常形式爲:
- union 共用體標識名
- {
- 類型名1共用體成員名1;
- 類型名2共用體成員名2;
- .
- 類型名n共用體成員名n;
- };
- 例如:
- unionexample
- {
- int a;
- float b;
- char c;
- };
- 其中,union是關鍵字,是共用體類型的標誌,example是共用體標識名。「共用體標識名「和「共用體成員名「都是由用戶定義的合法標識符,按語法規定共用體標識名是可選項,在說明中能夠不出現。
- (二)共用體變量的定義
- 和結構體類似,共用體變量的定義也可採用3種方式,一種方法以下:
- union un
- {
- int i;
- float x;
- }s1,s2,*p;
- 說明:
- (1)共用體變量在定義的同時只能用第一個成員的類型的值進行初始化。
- (2)「共用體「與「結構體「的定義形式類似,但它們的含義是不一樣的。結構體變量所佔內存長度是各成員佔的內存長度之和,每一個成員分別佔有其本身的內存單元,而共用體變量所佔的內存長度等於變量中所佔字節最長的成員的長度。例如,上面的共用體佔4字節(由於一個實型變量佔4字節)。
- (三)共用體變量中成員的引用
- 共用體變量中每一個成員的引用方式與結構體徹底相同,可使用如下3種形式之一:
- (1)共用體變量.成員名
- (2)(*共用體指針變量名).成員名
- (3)共用體指針變量名->成員名
- 共用體中的成員變量一樣可參與其所屬類型容許的任何操做,但在訪問共用體成員時應注意:共用體變量中起做用的是最近一次存入的成員變量的值,原有成員變量的值將被覆蓋。
- 另外,ANSIC標準容許在兩個類型相同的共用體變量之間進行賦值操做。同結構體變量同樣,共用體類型的變量能夠做爲實參進行傳遞,也能夠傳遞共用體變量的地址。
- 共用體的考查:
- union TT
- { int a;
- char ch[2];}
- 考點一:sizeof (structTT) = 2;
- 考點二:TT t1 ; t1=0x1234;
- 那麼 ch[0]=0x 34; ch[1]=0x12
- 2.4指針類型
- 2.4.1 關於地址和指針
- 在內存區中每個字節都有一個編號,這個編號就是「地址「,它至關於每一個變量的房間號。變量的數據就存放在地址所標識的內存單元中,變量中的數據其實就至關於倉庫中各個房間存放的貨物。若是內存中沒有對字節進行編號,系統將沒法對內存進行管理。內存的存儲空間是連續的,所以內存中的地址號也是連續的,而且用二進制數表示,爲了直觀起見,在這裏咱們將用二進制數進行描述。
- 通常微機使用的C系統爲整型變量分配4個字節,爲實型變量分配4個字節,爲字符型變量分配1個字節,爲雙精度類型變量分配8個字節。當某一變量被定義後,其內存中的地址也就肯定了。
- 在通常狀況下,咱們在程序中只需定義變量並指出變量名,無須去知道每一個變量在內存中的具體地址,由C編譯系統來完成每一個變量與其具體地址發生聯繫的操做。在程序中咱們對變量進行存取操做,實際上也就是對某個變量的地址存儲單元進行操做。這種直接按變量的地址存取變量的方式稱爲「直接存取「方式。
- 在C語言中,還能夠用另外一種稱爲「間接存取「的方式來完成對變量進行存取的操做,即將變量的地址存放在另外一種類型的變量中,從而經過這種新的變量類型來獲得變量的值。按C語言規定,能夠在程序中定義整型變量、實型變量、字符變量等,也能夠定義這樣一種特殊的變量,它是專門用來存放地址的。
- 因爲經過地址能找到所需的變量單元,咱們就能夠說:地址「指向「該變量單元。 所謂「指向「就是經過地址來體現。
- 在C語言中,將地址形象地稱爲「指針「,意思是經過它能找到以它爲地址的內存單元,這裏包含有一個方向指向的意思。一個變量的地址稱爲變量的「指針「。一個專門用來存放另外一個變量的地址的變量(即指針),則稱它爲「指針變量「。變量的指針就是變量的地址。存放變量地址的變量是指針變量。即在C語言中,容許用一個變量來存放指針,這種變量稱爲指針變量。所以,一個指針變量的值就是某個變量的地址或稱爲某變量的指針。
- 爲了表示指針變量和它所指向的變量之間的關係,在程序中用「*」符號表示「指向」,例如,i_p表明指針變量,而*i_p是i_p所指向的變量。所以,下面兩個語句做用相同:i=3; 語句是把3賦值給變量單元i。*i_p=3; 語句是把3賦值給i_p指向的變量單元。二者都能正確存儲數據。
- 2.4.2 變量的指針和指向變量的指針變量
- (一)指針變量的定義
- 定義指針變量的通常形式以下:
- 類型名 *指針變量名1,*指針變量名2,… ;
- 如:int *p,*t;
- 以上定義語句中,p和t都是合法用戶標識符,在每一個變量前的星號(*)是一個類型說明符,用來標識該變量是指針變量。
- 變量前的星號不可省略,若省略了星號說明符,就變成了把p和t定義爲整型變量(int是類型名)。在這裏,說明了p和t是兩個指向整型(int 類型)變量的指針,也就是說變量p和t中只能存放int類型變量的地址,這時咱們稱int是指針變量p和t的「基類型「。基類型用來指定該指針變量能夠指向的變量的類型。
- 例如: int *p1;
- 表示p1是一個指針變量,它的值是某個整型變量的地址。或者說p1指向一個整型變量。至於p1究竟指向哪個整型變量,應由向p1賦予的地址來決定。
- 再如:
- int*p2; /*p2是指向整型變量的指針變量*/
- float*p3; /*p3是指向浮點變量的指針變量*/
- char*p4; /*p4是指向字符變量的指針變量*/
- 應該注意的是,一個指針變量只能指向同類型的變量,如P3 只能指向浮點變量,不能時而指向一個浮點變量,時而又指向一個字符變量。
- (二)指針變量的引用
- 指針變量中只能存放地址(指針),將一個整型變量(或任何其餘非地址類型的數據)賦給一個指針變量是不容許的。
- 如:
- int *p; /*定義一個指向整型變量的指針*/
- p=300; /*300爲整數*/
- 是不合法的賦值。
- 與指針相關的兩個運算符是「&「(取地址運算)和「*「(指針運算符)。
- 1) &:取地址運算符。
- 2) *:指針運算符(或稱「間接訪問」 運算符)。
- C語言中提供了地址運算符&來表示變量的地址。
- 注意
-
- #include"stdio.h"
- main1() //錯誤:運行時出錯,P沒有指向明確的單元,p是隨機數,很是危險,萬一是重要地址
- {
- int *p; //解決方法 int k, *p = &k; inta, *p; p = &a;是對的
- *p = 5;
- printf("%d\n", *p);
- }
- //*p=NULL是將p指向的內存賦值爲NUll,而p自己不會變
- //p=NULL是改變了p自己的值,將它指向的地址改成NULL,
- //因此在使用地址指針的時候出錯的時候,都是將指針變量賦值爲NULL的
-
- swap(int*p1, int *p2) //錯誤,運行報錯,指針變量在使用前必須賦值
- {
- int*p; // 和int *p; *p = 5;一個錯誤 ,p指向不明確 ,即不知道5存在哪
- // int k; p = &k; //改正
- *p =*p1;
- *p1= *p2;
- *p2= *p;
- }
- main2()
- {
- int a, b;
- int *ip1, *ip2;
- scanf("%d%d",&a, &b);
- ip1 = &a; ip2 = &b;
- if(a < b)
- swap(ip1,ip2);
- printf("%d %d\n", a, b);
- }
-
- main() //錯的,指針類型不一樣,編譯報錯 [Error] cannot convert 'float*' to 'int*' in assignment
- {
- int*p;
- floatc;
- p =&c;
- }
-
- /*
- int a[5] = {1, 2, 3, 4, 5}, i; //a地址常量,數組首地址 a[i]電腦存儲方式 *(a + i)
- int *p = a;
- 下標法 指針法
- 第1個元素:
- 地址: a p a p
- &a[0] &p[0]
- 內容: a[0] p[0] *a *p
- 第i個元素:
- 地址: &a[i] &p[i] a +i p + i
- 內容: a[i] p[i] *a *(p + i)
- */
-
- inta[10];
- printf("%p\n",&a); //0xbff8dd44
- printf("%p\n",a); //0xbff8dd44
- printf("%p\n",&a[0]); //0xbff8dd44
- printf("%p\n",&a[1]); //0xbff8dd48
- (三)指針變量做爲函數參數
- 前面的章節中介紹過,函數參數能夠是整型、實型、字符型等數據,指針類型數據一樣也能夠做爲函數參數來進行傳遞。它的做用是將一個變量的地址傳送到另外一個函數中,參與該函數的運算。
- 若是想經過函數調用從而獲得n個要改變的值,能夠採用以下方法:
- (1)在主調函數中設n個變量,分別用n個指針變量指向它們。
- (2)而後將各個指針變量做爲實參,即將這n個變量的地址傳給所調用的函數的形參。
- (3)經過形參指針變量的改變,從而改變這n個變量的值。
- (4)主調函數中就可使用這些改變了值的變量。
- 形參指針變量的值的改變不能使實參指針變量的值發生改變。
- 2.4.3 數組與指針
- 一個數組包含若干個元素(變量),在定義時被分配了一段連續的內存單元。所以,能夠用一個指針變量來指向數組的首地址,經過該首地址就能夠依次找到其餘數組元素,一樣指針變量也能夠指向數組中的某一個元素。所謂數組的指針是指數組的起始地址,數組元素的指針是各個數組元素的地址。
- (一)指向數組元素的指針
- C語言規定數組名錶明數組的首地址,也就是數組中第0號元素的地址。有以下語句:
- int c[10]={0};
- int *p;
- p=c;
- 則語句p=c;與語句p=&c[0];是等價的。
- 數組c不表明整個數組。上述「p=c;「的做用是把數組c的首地址賦值給指針變量p,而不是把數組c中各元素的值賦給p。
- 定義指向數組元素的指針變量的方法,與定義指向變量的指針變量相同。例如:
- int c[10],*p;
- p=&c[5];
- 指針變量p指向了數組c中下標爲5的那個元素,即p用來保存c[5]的地址。
- (二)經過指針引用數組元素
- 按C語言的規定:若是指針變量p已指向數組中的一個元素,則p+1指向同一數組中的下一個元素(而不是將p的值簡單加1),這裏的加1是指增長一個長度單位(與數組基類型所佔存儲單元相同)。例如,數組元素是浮點型,每一個元素佔4個字節,則p+1意味着使p的值(是一個地址)加4個字節,以使它指向下一個元素。
- 將++和--運算符用於指針變量是十分有效的,可使用指針變量自動向前或向後移動,指向下一個或上一個數組元素。不過要當心利用,不然會致使內存錯誤。
- (三)用數組名做爲函數參數
- 數組名能夠用作函數的形參和實參。當數組名做爲參數被傳遞時,若形參數組中各元素髮生了變化,則原實參數組各元素的值也隨之變化。由於數組名做爲實參時,在調用函數時是把數組的首地址傳送給形參,所以實參數組與形參數組共佔一段內存單元。而若是用數組元素做爲實參的狀況就與用變量做爲實參時同樣,是「值傳遞「方式,單向傳遞,即便形參數組元素值發生了變化,原實參的數組元素值也不會受影響。
- (四)指向多維數組的指針和指針變量
- 有以下定義:
- c是該二維數組的數組名,它表明了整個數組的首地址,也就是第0行的首地址,c+1表明第1行的首地址,即c+1是c[1]的地址。c[0]、c[1]、c[2]既然是一維數組名,而C語言又規定了數組名錶明數組的首地址,所以c[0]表明第0行的一維數組中第0列元素的地址,即&c[0][0]。一樣地,c[1]的值是&c[1][0]。
- 第0行第1列元素的地址怎麼表示呢?能夠用c[0]+1來表示。此時「c[0]+1「中的1表明1個列元素的字節數,即兩個字節。如c[0]的值是2020,則c[0]+1的值是2022。
- 根據上面的介紹,c[i]和*(c+i)等價,所以,c[0]+1和*(c+0)+1的值都是&c[0][1],都是用來表示第0行第1列的元素的地址。c[i]從形式上看是數組c中第i個元素。若是c是二維數組名,則c[i]表明一維數組名,c[i]自己並不佔實際的內存單元,它也不存放數組中各個元素的值,它只是一個地址。c、c+i、c[i]、*(c+i)+j、c[i]+j都是地址,*(c[i]+j)、*(*(c+i)+j)是元素的值。
- 2.4.5 字符串與指針
- (一)字符串的表示形式
- (1)用字符數組存放一個字符串,而後輸出該字符串。
- 例如:
- char str[]=「I am a student??「;
- printf(「%s\n「,str);
- (2)用字符指針指向一個字符串。
- 能夠不定義數組,而定義一個字符指針,用字符指針指向字符串中的字符。
- 例如:
- char*str=「I am a student??「;
- /*定義str爲指針變量,並指向字符串的首地址*/
- printf(「%s\n「,str);
- 在這裏沒有使用字符數組,而是在程序中定義了一個字符指針變量str,並使該指針變量指向一個字符串的首地址。C語言對字符串常量是按字符數組進行處理的,在內存中開闢了一個字符數組來存放字符串常量。程序在定義字符指針變量str時,把字符串的首地址賦給str。str只能指向一個字符變量或其餘字符類型數據,不能同時指向多個字符數據,更不能理解爲把字符串中的所有字符存放到str中(指針變量只能存放地址)。在輸出時,利用字符型指針str的移動來控制輸出,直到遇到字符串結束標誌'\0'爲止。
- 經過字符數組名或字符指針變量能夠一次性輸出的只有字符數組(即字符串),而對一個數值型的數組,是不能企圖用數組名輸出它的所有元素的,只能藉助於循環逐個輸出元素。
- 顯然,用%s能夠控制對一個字符串進行總體的輸入輸出。對字符串中字符的存取,與操做其餘數組的方法相同,既能夠用下標方法,又能夠用指針方法。
- (二)字符串指針做函數參數
- 將一個字符串從一個函數傳遞到另外一個函數,能夠用地址傳遞的辦法,即用字符數組名做爲參數或用指向字符串的指針變量做爲參數,進行傳遞。
- 字符串指針變量做爲函數實參,形參能夠是字符指針變量,一樣也能夠是字符數組名。當字符數組名做爲函數實參時,形參能夠是字符數組名,一樣也能夠是字符指針變量。
- (三)字符指針變量和字符數組的區別
- 雖然字符數組和字符指針變量都能實現對字符的存儲和運算,但它們二者之間有以下區別:
- (1)字符數組是由若干個元素組成的,每一個元素中存放一個字符,而字符指針變量中存放的是地址(字符串的首地址),毫不是將字符串的內容存放到字符指針變量中。
- (2)賦值方式。
- 只能對字符數組各個元素賦值,不能用如下方法對字符數組賦值:
- char str[20];
- str=「Iam happy「;
- 而對字符指針變量,能夠採用如下方法賦值:
- char *s;
- s=「I amhappy??「;
- (3)字符數組能夠在定義時對其總體賦初值(即初始化),但在賦值語句中不能完成總體賦值。下面的作法是不容許的:
- char s[30];
- s[]=「I am so happy「;
- 而字符指針變量既能夠在定義時賦初值,也能夠出如今賦值語句中,相對來講要比字符數組使用起來靈活一點。
- (4)若是定義了一個字符數組,在編譯時,系統會爲它分配一段連續的內存單元,它的地址是肯定的。而當定義了一個字符指針變量後,就要即時給該指針變量分配內存單元,該指針變量中能夠存放一個地址值,也就是說,該指針變量能夠指向一個字符型數據,但若是未對它賦以一個地址值,則它並未具體指向一個肯定的字符數據。如:
- char s[10];
- scanf(「%s「,s);
- 是能夠的。但用下面的方法是極其危險的:
- 由於編譯時雖然給指針變量s分配了內存單元,s的地址(即&s)已經指定了,但s的值並未指定。在s單元中是一個不可預料的值,在執行scanf()函數時要求將一個字符串輸入到s所指向的一段內存單元中,即以s的值(地址)開始的一段內存單元。而s的值現在倒是不可預料的,它可能指向內存中空白的存儲區(未用的用戶存儲區),這樣當然能夠,但它也有可能指向內存中已存放指令或數據的有用內存段。這就會破壞程序,甚至破壞系統,形成嚴重的後果。應當這樣:
- char*a,s[10];
- a=s;
- scanf(「%s「,a);
- 先使a有肯定值,也就是使a指向一個數組的首地址,而後輸入一個字符串,把它存放在以該地址開始的若干單元中。
- (5)在程序中指針變量的值能夠改變。例如:
- char*s=「china」;
- s=s+2;
- 指針變量s的值能夠改變,當要輸出字符串時,從s當前所指向的單元開始輸出各個字符(本題中從字符i開始輸出),直到遇到'\0'爲止。而數組名雖然表明了地址,但它的值是一個固定的值,是不能改變的。下面是錯的:
- char str[]={「china」};
- str=str+2;
- 2.4.6 指向函數的指針
- 用函數指針變量調用函數
- 咱們已經知道,能夠用指針變量指向整型變量、字符型變量、字符串、數組,一樣指針變量也能夠指向一個函數。編譯時,一個函數將被分配給一個入口地址,這個入口地址就稱爲該函數的指針。所以,能夠經過使用一個指向函數的指針變量調用此函數。
- 說明:
- (1)指向函數的指針變量的通常定義形式爲:
- 數據類型 (*指針變量名)( );
- 如int(*s)();,「數據類型」指該函數返回值的類型。
- (2)(*s)()表示定義了一個指向函數的指針變量,但目前它不是固定指向哪個函數,而只是表示定義了這樣一個類型的變量,它的做用是專門用來存放函數的入口地址。在程序中實現把某一個函數的地址賦給它,它就指向那一個函數,這樣它的值也就肯定了。在一個程序中,一個指針變量以前後指向不一樣的函數,也就是說指向函數的指針變量和普通指針變量同樣,能夠屢次使用。
- (3)在給函數指針變量賦值時,只需給出函數名而沒必要給出參數。如:
- s=fun; /* fun爲已有定義的有參函數*/
- 由於是將函數入口地址賦給s,不涉及到參數的問題,不能寫成:
- s=fun(a,b);
- (4)用函數指針變量調用函數時,只需將(*s)代替函數名便可(s爲已經定義過的指向函數的指針變量名),在(*s)以後的括號中根據須要寫上實參。
- (5)對指向函數的指針變量,有些運算,如++s、--s、s+3等都是沒有意義的。
- 函數指針的用法(*f)()記住一個例子:
- intadd(int x, int y)
- {....}
- main()
- {int (*f)();
- f=add;
- }
- 賦值以後:合法的調用形式爲
- 1、add(2,3);
- 2、f(2,3);
- 3、(*f)(2,3)
- 2.4.7 返回指針的函數
- 返回指針值的函數
- 一個函數的返回值能夠是一個整型值、字符型值、實型值等,一樣地,函數的返回值也能夠是指針類型的數據,即地址。這種返回指針值的函數,通常定義形式爲:
- 類型名 *函數名(參數表);
- 例如:
- int*fun(int a,int b);
- fun是函數名,調用它能夠獲得一個指向整型數據的指針(地址)。a、b是兩個整形變量,是函數fun()的形參。注意:*fun在兩側沒有括弧,在fun的兩側分別爲*運算符和()運算符。而()優先級高於*,所以fun先與()結合,顯然這是函數形式。這個函數前面有一個*,表示此函數是指針型函數。
- 2.4.8 指針數組和指向指針的指針
- (一)指針數組的概念
- 若在一個數組中,其元素均爲指針類型數據,這樣的數組稱爲指針數組,也就是說,指針數組中的每個元素都至關於一個指針變量。一維指針數組的定義形式爲:
- 類型名 *數組名[數組長度];
- 例如:
- int *a[10];
- 因爲[]運算符的優先級比*運算符的優先級高,所以a先與[10]結合,造成a[10]形式,而後與前面的「*「結合,「*「表示此數組是指針類型的,每一個數組元素均可指向一個整型變量。
- 指針數組的一個重要用途是能夠用來指向若干個字符串。例如,在對庫房物品進行管理時,想把物品名稱放在一個數組中,而後對這些物品進行統計和查詢,咱們能夠分別定義一些字符串來存放各物品名稱,而後利用指針數組中的元素分別指向各字符串。若是還想對字符串排序,沒必要改動字符串的位置,只需改動指針數組中各元素的指向(即改變各元素的值,這些值是各字符串的首地址)。這樣,各字符串的長度能夠不一樣,並且移動指針變量的值(地址)要比移動字符串所花的時間少。
- (二)指向指針的指針
- 指向指針數據的指針變量,簡稱爲指向指針的指針,一般稱爲二級指針。定義一個指向指針數據的指針變量的形式:
- 類型名 **a;
- a前面有兩個「*「號,*a是指針變量的定義形式,如今它前面又有一個「*「號,表示指針變量是指向某種類型的指針變量的。
- 2.4.9 注意
- 二級指針和行指針區別
- #####################################################################################################################################################################################
- 指針變量的本質是用來放地址,而通常的變量是放數值的
- int *p 中 *p和p的差異:
- *p能夠當作變量來用;*的做用是取後面地址p裏面的數值
- p是看成地址來使用。
- *p++ 和(*p)++的之間的差異:
- *p++是 地址會變化。
- (*p)++ 是數值會要變化。
- 三名主義:
- 數組名:表示第一個元素的地址。數組名不能夠自加,他是地址常量名。
- 函數名:表示該函數的入口地址。
- 字符串常量名:表示第一個字符的地址。
- 指針變量是存放地址的。而且指向哪一個就等價哪一個,全部出現*p的地方均可以用它等價的代替。
- 例如:int a=2,*p=&a;
- *p=*p+2;
- (因爲*p指向變量a,因此指向哪一個就等價哪一個,這裏*p等價於a,能夠至關因而a=a+2)
- 指針變量兩種初始化
- 方法一:int a=2,*p=&a;(定義的同時初始化)
- 方法二:int a=2,*p; p=&a; (定義以後初始化)
- 2.4.10 指針應用--鏈表
- (一)鏈表的概念
- 鏈表是一種常見的重要的數據結構,它是動態地進行存儲單元分配的一種結構。
- 鏈表中的各元素在內存中不必定是連續存放的。要找鏈表中某一元素,必須先找到上一個元素,根據該元素提供的下一元素的地址才能找到下一個元素。因此,若是沒有頭指針(head),則整個鏈表都沒法訪問。另一點,這種鏈表的數據結構,必須利用指針變量才能實現。即一個節點中應包含一個指針變量,用它存放下一節點的地址。固然也能夠不經過指針變量,用其餘方式也能夠構建簡單鏈表,請參考有關數據結構的教材。
- 下面經過一個例子來講明如何創建和輸出一個簡單鏈表。
- #include<stdio.h>
- #include<string.h>
- structnode
- {
- int data;
- struct node *next;
- };
- typedefstruct node NODETYPE;
- main()
- {
- NODETYPE s1,s2,s3,*begin,*p;
- s1.data=100;/*給變量中的data域賦值*/
- s2.data=200;
- s3.data=300;
- begin=&s1;
- s1.next=&s2;/*使s1的域next指向s2*/
- s2.next=&s3;
- s3.next='\0';
- p=begin;/*移動p,使之依次指向s一、s二、s3,輸出它們data域中的值*/
- while(p)
- {
- printf("%d",p->data);
- p=p->next; /* p順序後移 */
- }
- printf("\n");
- }
- main()函數中定義的變量s一、s二、s3都是結構體變量,它們都含有data和next兩個成員。變量begin和p是指向NODETYPE結構體類型的指針變量,它們與結構體變量s一、s二、s3中的成員變量next類型相同。執行賦值語句後,begin中存放s1變量的地址,變量s1的成員s1->next中存放變量s2的地址……最後一個變量s3的成員s3->next置成'\0'(NULL),從而把同一類型的結構體變量s一、s二、s3「連接」到一塊兒,造成「鏈表」。
- 在此例中,連接到一塊兒的每一個節點(結構體變量s一、s二、s3)都是經過定義,由系統在內存中開闢了固定的存儲單元(不必定連續)。在程序執行的過程當中,不可能人爲地再產生新的存儲單元,也不可能人爲地使已開闢的存儲單元消失。從這一角度出發,可稱這種鏈表爲「靜態鏈表「。在實際中,使用更普遍的是一種「動態鏈表」。
- (二)創建動態鏈表(主要針對單向鏈表)
- 創建單向鏈表的主要步驟以下:
- (1)讀取數據。
- (2)生成新節點。
- (3)將數據存入節點的成員變量中。
- (4)將新節點插入到鏈表中,重複上述操做直至輸入結束。
- 編寫函數creatlist(),創建帶有頭節點的單向鏈表。節點數據域中的數值從鍵盤輸入,以-1做爲輸入結束標誌。鏈表的頭節點的地址由函數值返回。
- 咱們在函數中定義了一個名爲begin的指針變量,用於存放頭節點的地址,另外還定義了兩個工做指針:current和end。其中指針current用來指向新生成的節點,指針end老是指向鏈表當前的尾節點。每當把current所指的新開闢的節點鏈接到表尾後,end便移向這一新的表尾節點。這時又能夠用current去指向下一個新開闢的節點。鏈表最後一個節點的指針域中置'\0'(NULL值)做爲單向鏈表的結束標誌。
- 鏈表建成後,頭節點的地址由creatlist()返回,賦給main()函數中的指針變量head。函數以下:
- #include<stdio.h>
- #include<string.h>
- #include<stdlib.h>
- structnode
- {
- int data;
- structnode *next;
- };
- typedefstruct node NODETYPE;
- NODETYPE*creatlist()
- {
- int i;
- NODETYPE *begin,*end,*current;
- begin=(NODETYPE*)malloc(sizeof(NODETYPE));/*生成頭節點*/
- end=begin;
- scanf("%d",&i);/*輸入數據*/
- while(i!=-1)/*未讀到數據結束標誌時進入循環*/
- {
- current=(NODETYPE*)malloc(sizeof(NODETYPE));/*生成一個新節點*/
- current->data=i;/*讀入的數據存入新節點的data域*/
- end->next=current;/*新節點連到表尾*/
- end=current;/*end指向當前表尾*/
- scanf("%d",&i);/*讀入數據*/
- }
- end->next='\0';/*置鏈表結束標誌*/
- return begin;/*返回表頭指針*/
- }
- main()
- {
- NODETYPE *head;
- head=creatlist();/*調用鏈表創建函數,獲得頭節點的地址*/
- }
- 以上creatlist()函數中,當一開始輸入-1時,並不進入while循環,而直接執行循環以後的end->next='\0';語句,這時創建的是一個「空鏈表「。因而可知,可用條件begin->next=='\0'來判斷鏈表是否爲空。
- (三)順序訪問鏈表中各節點的數據域
- 所謂「訪問「,能夠理解爲取各節點的數據域中的值進行各類運算、修改各節點的數據域中的值等一系列的操做。
- 輸出單向鏈表各節點數據域中內容的算法比較簡單,只需利用一個工做指針(p),從頭至尾依次指向鏈表中的每一個節點,當指針指向某個節點時,就輸出該節點數據域中的內容,直到遇到鏈表結束標誌爲止。若是是空鏈表,就只輸出提示信息並返回調用函數。
- 函數以下:
- voidprintlist(NODETYPE *head)
- {
- NODETYPE*p;
- p=head->next;/*指向頭節點後的第一個節點*/
- if(p=='\0')/*鏈表爲空時*/
- printf(「Linklistis null\n」);
- else/*鏈表不爲空時*/
- {
- printf(「head」);
- do
- {
- printf(「->%d」,p->data);/*輸出當前節點數據域中的值*/
- p=p->next;/*move指向下一個節點*/
- }while(p!=′\0′);/*未到鏈表尾,繼續循環下去*/
- }
- printf(「->end\n」);
- }
- (四)在鏈表中插入節點
- 在單向鏈表中插入節點,首先要肯定插入的位置插入節點在指針p所指的節點以前稱爲「前插「,插入節點在指針p所指的節點以後稱爲「後插「。「前插「操做中各指針的指向
- 當進行前插操做時,須要3個工做指針:s指向新開闢的節點,用p指向插入的位置,q指向要插入的前趨節點。
- (五)刪除鏈表中的節點
- 爲了刪除單向鏈表中的某個節點,首先要找到待刪除的節點的前趨節點(即當前要刪除節點的前面一個節點),而後將此前趨節點的指針域去指向待刪除節點的後續節點(即當前要刪除節點的下一個節點),最後釋放被刪除節點所佔的存儲空間便可。
- 2.5用typedef說明一種新類型名
- C語言能夠用typedef說明一種新類型名,說明新類型名的語句通常形式爲:
- typedef 類型名 標識符;
- 其中,「類型名「必定是在此語句以前已有定義的類型標識符。「標識符「是一個用戶定義標識符,用來標識新的類型名。typedef語句的做用僅僅是用「標識符「來表明已存在的「類型名「,並無產生新的數據類型,所以,原有的類型名依然有效。
- 聲明一個新的類型名的具體步驟以下:
- (1)先按定義變量的方法寫出定義的主體(如float a;)。
- (2)將變量名換成新類型名(如將a換成FLO)。
- (3)在最左面加上關鍵字typedef(如 typedef float FLO;)。
- (4)而後能夠用新類型名去定義其餘的變量(如FLO b;)。
- 2.6 sizeof()計算實際所佔字節數
- 概念
- sizeof是C語言的一種單目操做符,如C語言的其餘操做符++、--等。它並非函數。sizeof操做符以字節形式給出了其操做數的存儲大小。操做數能夠是一個表達式或括在括號內的類型名。操做數的存儲大小由操做數的類型決定。做用就是返回一個對象或者類型所佔的內存字節數。
- 2.6.1語法
- sizeof有三種語法形式,以下:
- 1) sizeof( object ); // sizeof( 對象 );
- 2) sizeof( type_name ); // sizeof( 類型 );
- 3) sizeof object; // sizeof 對象;
- 舉例
- sizeof(int); //64位 4
- int a=3; sizeof(a); //64位 4 建議使用此方法
- sizeof(3); //64位 4
- sizeof 3 //64位4 是對的,不建議使用
- sizeof int //Error
- 2.6.2數組
- 數組的sizeof值等於數組所佔用的內存字節數,如:
- char a1[] = "abc"; sizeof( a1 ); // 結果爲4,字符末尾還存在一個NULL終止符
- int a2[3]; sizeof( a2 ); // 結果爲3*4=12(依賴於int)
- sizeof不是求數組元素的個數,求數組元素的個數,一般有下面兩種寫法:
- int c1 = sizeof( a1 ) / sizeof( char ); // 總長度/單個元素的長度
- int c2 = sizeof( a1 ) / sizeof( a1[0] ); // 總長度/第一個元素的長度
- 舉例
- char array[8] = 「China」; sizeof(array) //64位 8 數組實際長度
- sizeof(「hi」); //64位 3
- char a[5]="CHINA"; printf("%d",sizeof(a)); //64位 5
- char a[5]="CHINAA"; printf("%d", sizeof(a)); //64位 警告 結果5
- int a[3]; sizeof(a); //64位 3*4=12 數組實際長度,整形一個元素4個字節
- 2.6.3字符
- sizeof(char) //64位 1 s 字符型變量是1字節
- sizeof(‘s’) //64位 4 ‘s’轉換爲一個數,數佔四個字節
- sizeof(‘\101’) //64位 4 ‘\101’爲一個數,數佔四個字節
- ‘A’ = ‘\101’ = ‘\x41’ = 65
- char c; sizeof(c) //64位 1
- C和C++比較
- C語言環境下:
- char a = 'a' ;
- sizeof(char) = 1 ;
- sizeof(a) = 1 ;
- sizeof('a') =4 ;
- C++語言環境下:
- char a = 'a';
- sizeof(char) = 1;
- sizeof(a) = 1 ;
- sizeof('a') =1;
- 字符型變量是1字節這個沒錯,奇怪就奇怪在C語言認爲'a'是4字節,而C++語言認爲'a'是1字節。
- 緣由以下:
- C99標準的規定,'a'叫作整型字符常量(integer character constant),被當作是int型,因此在32位機器上佔4字節。
- ISO C++標準規定,'a'叫作字符字面量(character literal),被當作是char型,因此佔1字節
- 字符是指計算機中使用的字母、數字、字和符號。1個漢字字符存儲須要2個字節,1個英文字符存儲須要1個字節。ASCII是一個字節,Unicode是兩個字節。Java的字符是Unicode的,因此是兩個字節。
- 2.6.4結構體
- 1.對齊
- structS1
- {
- char c;
- int i;
- };
- sizeof(s1)爲8,字節對齊,有助於加快計算機的取數速度,不然就得多花指令週期了。爲此,編譯器默認會對結構體進行處理(實際上其它地方的數據變量也是如此),讓寬度爲2的基本數據類型(short等)都位於能被2整除的地址上,讓寬度爲4的基本數據類型(int等)都位於能被4整除的地址上,以此類推。這樣,兩個數中間就可能須要加入填充字節,因此整個結構體的sizeof值就增加了。
- 2.字節對齊的細節和編譯器實現相關,但通常而言,知足三個準則:
- 1) 結構體變量的首地址可以被其最寬基本類型成員的大小所整除;
- 2) 結構體每一個成員相對於結構體首地址的偏移量(offset)都是成員大小的整數倍,若有須要編譯器會在成員之間加上填充字節(internaladding);
- 3) 結構體的總大小爲結構體最寬基本類型成員大小的整數倍,若有須要編譯器會在最末一個成員以後加上填充字節(trailing padding)。
- 舉例
- struct S3 整除4 一共3*4=12 測試###############################################################################################################################################################################
- {
- char c1; 整除1
- S1 s; 整除4
- structS1
- {
- char c;
- int i;
- };
- char c2 整除1
- };
- S1的最寬簡單成員的類型爲int,S3在考慮最寬簡單類型成員時是將S1「打散」看的,因此S3的最寬簡單類型爲int,這樣,經過S3定義的變量,其存儲空間首地址須要被4整除,整個sizeof(S3)的值也應該被4整除。
- 3.「空結構體」(不含數據成員)的大小不爲0,而是1。試想一個「不佔空間」的變量如何被取地址、兩個不一樣的「空結構體」變量又如何得以區分呢因而,「空結構體」變量也得被存儲,這樣編譯器也就只能爲其分配一個字節的空間用於佔位了。以下:
- structS5 { };
- sizeof(S5 ); // 結果爲1
- 2.6.5聯合體
- 結構體在內存組織上是順序式的,聯合體則是重疊式,各成員共享一段內存,因此整個聯合體的sizeof也就是每一個成員sizeof的最大值。結構體的成員也能夠是複合類型,這裏,複合類型成員是被做爲總體考慮的。
- 因此,下面例子中,U的sizeof值等於sizeof(s)。
- structS1
- {
- char f1;
- int f2;
- char*f3;
- };
- union U
- {
- int i;
- char c;
- S1 s;
- };
- 2.6.6表達式和調用函數
- sizeof能夠對一個表達式求值,編譯器根據表達式的最終結果類型來肯定大小,通常不會對錶達式進行計算
- 1.# include <stdio.h>
- int main()
- {
- int i;
- i = 10;
- printf("i : %d\n",i);
- printf("sizeof(i++) is: %d\n",sizeof(++i));
- printf("i : %d\n",i);
- return 0;
- }
- 結果:i : 10
- sizeof(i++) is: 4i : 10
- 2. chara="255";printf("%d",sizeof(a++));
- /*打印的值是1,這一步並不對錶達式a++進行計算,因此char a="255"*/
- 3. sizeof( 2 + 3.14 );
- // 3.14的類型爲double,2也會被提高成double類型,因此等價於 sizeof( double );可是不計算,至關於比較裏面最大類型##########################測試#################sizeof(i=2+3.14)###########################################################################
- sizeof也能夠對一個函數調用求值,其結果是函數返回類型的大小,函數並不會被調用,咱們來看一個完整的例子:
- charfoo()
- {
- printf("foo()has been called.\n");
- return'a';
- }
- intmain()
- {
- size_tsz = sizeof( foo() );
- /*foo() 的返回值類型爲char,因此sz = sizeof(char ),foo()並不會被調用*/
- printf("sizeof(foo() ) = %d\n", sz);
- }
- 2.6.7 不可用
- C99標準規定,函數、不能肯定類型的表達式以及位域(bit-field)成員不能被計算sizeof值,即sizeof操做符不能用於函數類型,不徹底類型或位字段。不徹底類型指具備未知存儲大小的數據類型,如未知存儲大小的數組類型、未知內容的結構或聯合類型、void類型等。下面這些寫法都是錯誤的:
- 1. sizeof(foo ); // error
- 2. void foo2() { } sizeof(foo2() ); // error
- 3. struct S
- {
- unsignedint f1 : 1; // error 屬於位字段
- unsignedint f2 : 5;
- unsignedint f3 : 12;
- };
- sizeof(S.f1 ); // error
- 2.6.8 sizeof的常量性
- sizeof的計算髮生在編譯時刻,因此它能夠被看成常量表達式使用,如:
- charary[ sizeof( int ) * 10 ]; // ok
- 最新的C99標準規定sizeof也能夠在運行時刻進行計算,以下面的程序在Dev-C++中能夠正確執行:
- int n;
- n = 10;// n動態賦值
- charary[n]; // C99也支持數組的動態定義
- printf("%d\n",sizeof(ary)); // ok. 輸出10
- 但在沒有徹底實現C99標準的編譯器中就行不通了,上面的代碼在VC6中就通不過編譯。因此咱們最好仍是認爲sizeof是在編譯期執行的,這樣不會帶來錯誤,讓程序的可移植性強些。

歡迎關注本站公眾號,獲取更多信息