第5章 指針、數組和結構

5.1 指針
指針,是一個無符號整數(unsigned int),它是一個以當前系統尋址範圍爲取值範圍的整數。
 char c = 'a';
 char *cp = &c;//cp保存着c的地址

c++

5.2 數組
數組就是相同數據類型的元素按必定順序排列的集合。
int iarr [5];

程序員

5.2.1 字符串文字量
用雙引號括起來的字符序列。例如 "hello"。
一個字符串文字量老是由一個空字符'\0'做爲結束符。
sizeof("hello") == 6
字符串文字量的類型是「適當個數的const字符數組」。
字符串文字量是靜態分配的,因此函數返回他們是安全的。


重要概念辨析:
(1)函數指針:實質是一個指針,該指針指向函數的入口地址。
void (*func)(int ,int);
(2)指針函數:本質是一個函數,函數返回類型是某一類型的指針。
void * func(int, int);
(3)數組指針:指向數組的指針,實質是一個指針,其指向的類型是數組。
int (*parr)[5];
(4)指針數組:元素爲指針的數組,本質是一個數組,其中的元素爲指針。
int * parr[5];



5.4 const
const表達「不變化的值」這樣一個概念。
const是一個C語言的關鍵字,它限定一個變量不容許被改變。使用const在必定程度上能夠提升程序的安全性和可靠性。
1什麼是const?
常類型是指使用類型修飾符const說明的類型,常類型的變量或對象的值是不能被更新的。(固然,咱們能夠偷樑換柱進行更新)
2爲何引入const?windows

const 推出的初始目的,正是爲了取代預編譯指令,消除它的缺點,同時繼承它的優勢。
3主要做用
(1)能夠定義const常量,具備不可變性。 
例如:const int Max=100; int Array[Max]; 
(2)便於進行類型檢查,使編譯器對處理內容有更多瞭解,消除了一些隱患。
例如: void f(const int i) { .........} 編譯器就會知道i是一個常量,不容許修改; 
(3)能夠避免意義模糊的數字出現,一樣能夠很方便地進行參數的調整和修改。 同宏定義同樣,能夠作到不變則已,一變都變!
如(1)中,若是想修改Max的內容,只須要:const int Max=you want;便可! 
(4)能夠保護被修飾的東西,防止意外的修改,加強程序的健壯性。 仍是上面的例子,若是在函數體內修改了i,編譯器就會報錯; 
例如: void f(const int i) { i=10;//error! } 
(5) 能夠節省空間,避免沒必要要的內存分配。 例如: 
#define PI 3.14159 //常量宏 
const double Pi=3.14159; //此時並未將Pi放入RAM中 ...... 
double i=Pi; //此時爲Pi分配內存,之後再也不分配! 
double I=PI; //編譯期間進行宏替換,分配內存 
double j=Pi; //沒有內存分配 
double J=PI; //再進行宏替換,又一次分配內存! 
const定義常量從彙編的角度來看,只是給出了對應的內存地址,而不是象#define同樣給出的是當即數,因此,const定義的常量在程序運行過程當中只有一份拷貝,而#define定義的常量在內存中有若干個拷貝。 
(6) 提升了效率。 
編譯器一般不爲普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成爲一個編譯期間的常量,沒有了存儲與讀內存的操做,使得它的效率也很高。


重要概念辨析:
(1)常量指針:只能讀取內存中內容,不可以修改內存中內容的指針。
int const * ip;
(2)指針常量:指針所指向的地址不能改變,即指針自己是一個常量,可是指針所指向的內容能夠改變。
int * const p=&a;
ps:指針常量必須在聲明的同時對其初始化,不容許先聲明一個指針常量隨後再對其賦值,這和聲明通常的常量是同樣的。
簡單理解--左定值,右定向
const在*號左邊,表示指針指向的內容不能修改(常量指針)
const在*號右邊,表示指針指向的地址不能修改(指針常量)


5.5 引用
一個引用就是某個對象的另外一個名字。
引用的主要做用是爲了描述函數的參數和返回值。
爲了確保一個引用總可以使某個對象的名字,咱們必須對引用初始化。
int i = 100;
int &r = i;


5.6 指向 void 的指針
一個指向任何對象類型的指針均可以賦值給 void*的變量,一個void*變量能夠賦值給另外一個void*變量,兩個void*變量能夠比較相等與否,一個void*變量能夠顯式的轉換爲另外一個類型。
其餘的操做都是不安全的(編譯器不知道實際被指向的是哪一種對象),所以,要使用void*變量,必須顯式的將它轉換爲某個特定類型的指針。
void* 的最主要的用途是向函數傳遞一個指針。
int a = 100;
int * pi = &a;
void * pv = pi; //ok,從int*到void*的隱式轉換
*pv; //err, void* 不能間接引用
pv++; //err, void* 不能自增(不知道被指向對象的大小)


5.7 結構
結構就是一個能夠包含任意類型元素的集合。
struct Person
{
   char name[20];
   char sex[10];
   int age;


};//!!!冒號不能掉;


結構類型對象的大小是其成員的大小之和

數組

重要概念辨析:安全

內存對齊
1、內存對齊的緣由
大部分的參考資料都是如是說的:
一、平臺緣由(移植緣由):不是全部的硬件平臺都能訪問任意地址上的任意數據的;
某些硬件平臺只能在某些地址處取某些特定類型的數據,不然拋出硬件異常。
二、性能緣由:數據結構(尤爲是棧)應該儘量地在天然邊界上對齊。
緣由在於,爲了訪問未對齊的內存,處理器須要做兩次內存訪問;而對齊的內存訪問僅須要一次訪問。
也有的朋友說,內存對齊出於對讀取的效率和數據的安全的考慮,我以爲也有必定的道理。
2、對齊規則
    每一個特定平臺上的編譯器都有本身的默認「對齊係數」(也叫對齊模數)。
好比32位windows平臺下,VC默認是按照8bytes對齊的(VC->Project->settings->c/c++->Code Generation中的truct member alignment 值默認是8),
程序員能夠經過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的「對齊係數」。
    在嵌入式環境下,對齊每每與數據類型有關,特別是C編譯器對缺省的結構成員天然對屆條件爲「N字節對齊」,N即該成員數據類型的長度。
如int型成員的天然對界條件爲4字節對齊,而double類型的結構成員的天然對界條件爲8字節對齊。
若該成員的起始偏移不位於該成員的「默認天然對界條件」上,則在前一個節面後面添加適當個數的空字節。
C編譯器缺省的結構總體的天然對界條件爲:該結構全部成員中要求的最大天然對界條件。
若結構體各成員長度之和不爲「結構總體天然對界條件的整數倍,則在最後一個成員後填充空字節。
    那麼能夠獲得以下的小結:
類型 對齊方式(變量存放的起始地址相對於結構的起始地址的偏移量)
Char    偏移量必須爲sizeof(char)即1的倍數
Short   偏移量必須爲sizeof(short)即2的倍數
int     偏移量必須爲sizeof(int)即4的倍數
float   偏移量必須爲sizeof(float)即4的倍數
double  偏移量必須爲sizeof(double)即8的倍數
   各成員變量在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊方式調整位置,空缺的字節編譯器會自動填充。
同時爲了確保結構的大小爲結構的字節邊界數(即該結構中佔用最大空間的類型所佔用的字節數)的倍數,
因此在爲最後一個成員變量申請空間後,還會根據須要自動填充空缺的字節,也就是說:結構體的總大小爲結構體最寬基本類型成員大小的整數倍,
若有須要編譯器會在最末一個成員以後加上填充字節。對於char數組,字節寬度仍然認爲爲1。
   對於下述的一個結構體,其對齊方式爲:
struct Node1{
    double m1;
    char m2;
    int m3;
};
  對於第一個變量m1,sizeof(double)=8個字節;接下來爲第二個成員m2分配空間,
這時下一個能夠分配的地址對於結構的起始地址的偏移量爲8,是sizeof(char)的倍數,
因此把m2存放在偏移量爲8的地方知足對齊方式,該成員變量佔用 sizeof(char)=1個字節;
接下來爲第三個成員m3分配空間,這時下一個能夠分配的地址對於結構的起始地址的偏移量爲9,
不是sizeof (int)=4的倍數,爲了知足對齊方式對偏移量的約束問題,自動填充3個字節(這三個字節沒有放什麼東西),
這時下一個能夠分配的地址對於結構的起始地址的偏移量爲12,恰好是sizeof(int), 
因爲8+4+4 = 16剛好是結構體中最大空間類型double(8)的倍數,因此sizeof(Node1) =16.
 
typedef struct{
    char a;
    int b;
    char c;
}Node2;
    成員a佔一個字節,因此a放在了第1位的位置;因爲第二個變量b佔4個字節,爲保證起始位置是4(sizeof(b))的倍數,
因此須要在a後面填充3個字節,也就是b放在了從第5位到第8位的位置,而後就是c放在了9的位置,此時4+4+1=9。
接下來考慮字節邊界數,
9並非最大空間類型int(4)的倍數,應該取大於9且是4的的最小整數12,因此sizeof(Node2) = 12.
typedef struct{
    char a;
    char b;
    int c;
}Node3;
   明顯地:sizeof(Node3) = 8
   對於結構體A中包含結構體B的狀況,將結構體A中的結構體成員B中的最寬的數據類型做爲該結構體成員B的數據寬度,
同時結構體成員B必須知足上述對齊的規定。
   要注意在VC中有一個對齊係數的概念,若設置了對齊係數,那麼上述描述的對齊方式,則不適合。
   例如:
1字節對齊(#pragma pack(1))
輸出結果:sizeof(struct test_t) = 8 [兩個編譯器輸出一致]
分析過程:
成員數據對齊
#pragma pack(1)
struct test_t {
    int a; 
    char b; 
    short c;
    char d; 
};
#pragma pack()
成員總大小=8;
 
2字節對齊(#pragma pack(2))
輸出結果:sizeof(struct test_t) = 10 [兩個編譯器輸出一致]
分析過程:
成員數據對齊
#pragma pack(2)
struct test_t {
    int a; 
    char b; 
    short c;
    char d; 
};
#pragma pack()
成員總大小=9;
 
4字節對齊(#pragma pack(4))
輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(4)
struct test_t { //按幾對齊, 偏移量爲後邊第一個取模爲零的。
int a; 
char b; 
short c;
char d; 
};
#pragma pack()
成員總大小=9;

數據結構

相關文章
相關標籤/搜索