目錄
六、C語言位運算(按位與運算、或運算、異或運算、左移運算、右移運算)
一、C語言結構體指針
1.1 定義:
struct stu *pstu = &stu1;
1.2 獲取結構體成員:
通過結構體指針可以獲取結構體成員,一般形式爲:
(*pointer).memberName
或者:
pointer->memberName
粗糙代碼借用:
#include <stdio.h>
struct stu{
char *name; //姓名
int num; //學號
int age; //年齡
char group; //所在小組
float score; //成績
}stus[] = {
{"Zhou ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"Liu fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
}, *ps;
int main(){
//求數組長度
int len = sizeof(stus) / sizeof(struct stu);
printf("Name\t\tNum\tAge\tGroup\tScore\t\n");
for(ps=stus; ps<stus+len; ps++){
printf("%s\t%d\t%d\t%c\t%.1f\n", ps->name, ps->num, ps->age, ps->group, ps->score);
}
return 0;
}
1.3 結構體指針作爲函數參數
結構體就和int數據類型一樣,沒什麼特別的。
二、C語言枚舉類型enum用法詳解
#include <stdio.h>
#define Mon 1
#define Tues 2
#define Wed 3
#define Thurs 4
#define Fri 5
#define Sat 6
#define Sun 7
int main(){
int day;
scanf("%d", &day);
switch(day){
case Mon: puts("Monday"); break;
case Tues: puts("Tuesday"); break;
case Wed: puts("Wednesday"); break;
case Thurs: puts("Thursday"); break;
case Fri: puts("Friday"); break;
case Sat: puts("Saturday"); break;
case Sun: puts("Sunday"); break;
default: puts("Error!");
}
return 0;
}
運行結果:
5↙
Friday
出一個星期有幾天:
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
枚舉值默認從 0 開始,往後逐個加 1(遞增);也就是說,week 中的 Mon、Tues ...... Sun 對應的值分別爲 0、1 ...... 6。
我們也可以給每個名字都指定一個值:
enum week{ Mon = 1, Tues = 2, Wed = 3, Thurs = 4, Fri = 5, Sat = 6, Sun = 7 };
更爲簡單的方法是隻給第一個名字指定值:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
這樣枚舉值就從 1 開始遞增,跟上面的寫法是等效的。
定義枚舉變量
枚舉是一種類型,通過它可以定義枚舉變量:
enum week a, b, c;
也可以在定義枚舉類型的同時定義變量:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a, b, c;
有了枚舉變量,就可以把列表中的值賦給它:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
enum week a = Mon, b = Wed, c = Sat;
#include <stdio.h>
int main(){
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day;
scanf("%d", &day);
switch(day){
case Mon: puts("Monday"); break;
case Tues: puts("Tuesday"); break;
case Wed: puts("Wednesday"); break;
case Thurs: puts("Thursday"); break;
case Fri: puts("Friday"); break;
case Sat: puts("Saturday"); break;
case Sun: puts("Sunday"); break;
default: puts("Error!");
}
return 0;
}
需要注意的兩點是:
1) 枚舉列表中的 Mon、Tues、Wed 這些標識符的作用範圍是全局的(嚴格來說是 main() 函數內部),不能再定義與它們名字相同的變量。
2) Mon、Tues、Wed 等都是常量,不能對它們賦值,只能將它們的值賦給其他的變量。
枚舉類型變量需要存放的是一個整數,我猜測它的長度和 int 應該相同,下面來驗證一下:
#include <stdio.h>
int main(){
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day = Mon;
printf("%d, %d, %d, %d, %d\n", sizeof(enum week), sizeof(day), sizeof(Mon), sizeof(Wed), sizeof(int) );
return 0;
}
運行結果:
4, 4, 4, 4, 4
三、C語言共用體類型union用法詳解
定義:
union 共用體名{
成員列表
};
共用體也是一種自定義類型,可以通過它來創建變量,例如:
union data{
int n;
char ch;
double f;
};
union data a, b, c;
共用體 data 中,成員 f 佔用的內存最多,爲 8 個字節,所以 data 類型的變量(也就是 a、b、c)也佔用 8 個字節的內存。
請看下面的演示:
#include <stdio.h>
union data{
int n;
char ch;
short m;
};
int main(){
union data a;
printf("%d, %d\n", sizeof(a), sizeof(union data) );
a.n = 0x40;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.ch = '9';
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.m = 0x2059;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.n = 0x3E25AD54;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
return 0;
}
運行結果:
4, 4
40, @, 40
39, 9, 39
2059, Y, 2059
3E25AD54, T, AD54
要想理解上面的輸出結果,弄清成員之間究竟是如何相互影響的,就得了解各個成員在內存中的分佈。以上面的 data 爲例,各個成員在內存中的分佈如下:
上圖是在絕大多數 PC 機上的內存分佈情況,要確定是大端還是小端。
接下來有個例子看一下union的應用。
Name |
Num |
Sex |
Profession |
Score / Course |
---|---|---|---|---|
HanXiaoXiao |
501 |
f |
s |
89.5 |
YanWeiMin |
1011 |
m |
t |
math |
LiuZhenTao |
109 |
f |
t |
English |
ZhaoFeiYan |
982 |
m |
s |
95.0 |
#define TOTAL 4 //人員總數
struct{
char name[20];
int num;
char sex;
char profession;
union{
float score;
char course[20];
} sc;
} bodys[TOTAL];
四、大端小端以及判別方式
定義:
大端和小端是指數據在內存中的存儲模式,它由 CPU 決定:
1) 大端模式(Big-endian)是指將數據的低位(比如 1234 中的 34 就是低位)放在內存的高地址上,而數據的高位(比如 1234 中的 12 就是高位)放在內存的低地址上。這種存儲模式有點兒類似於把數據當作字符串順序處理,地址由小到大增加,而數據從高位往低位
2) 小端模式(Little-endian)是指將數據的低位放在內存的低地址上,而數據的高位放在內存的高地址上。這種存儲模式將地址的高低和數據的大小結合起來,高地址存放數值較大的部分,低地址存放數值較小的部分,這和我們的思維習慣是一致,比較容易理解。
爲什麼會有大小端之分?
計算機中的數據是以字節(Byte)爲單位存儲的,每個字節都有不同的地址。現代 CPU 的位數(可以理解爲一次能處理的數據的位數)都超過了 8 位(一個字節),PC機、服務器的 CPU 基本都是 64 位的,嵌入式系統或單片機系統仍然在使用 32 位和 16 位的 CPU。
對於一次能處理多個字節的CPU,必然存在着如何安排多個字節的問題,也就是大端和小端模式。以 int 類型的 0x12345678 爲例,它佔用 4 個字節,如果是小端模式(Little-endian),那麼在內存中的分佈情況爲(假設從地址 0x 4000 開始存放):
內存地址 | 0x4000 | 0x4001 | 0x4002 | 0x4003 |
存放內容 | 0x78 | 0x56 | 0x34 | 0x12 |
如果是大端模式(Big-endian),那麼分佈情況正好相反:
內存地址 | 0x4000 | 0x4001 | 0x4002 | 0x4003 |
存放內容 | 0x12 | 0x34 | 0x56 | 0x78 |
個人感覺,小端模式的存儲類似於棧,棧頂是高地址和高位。大端模式的棧頂是高地質和低位。
佔用內存都是從低地址開始的,這就可以解釋共用體的上面的佔用了。
代碼驗證如下:
藉助共用體,我們可以檢測 CPU 是大端模式還是小端模式,請看代碼:
#include <stdio.h>
int main(){
union{
int n;
char ch;
} data;
data.n = 0x00000001; //也可以直接寫作 data.n = 1;
if(data.ch == 1){
printf("Little-endian\n");
}else{
printf("Big-endian\n");
}
return 0;
}
在PC機上的運行結果:
Little-endian
我們的 PC 機上使用的是 X86 結構的 CPU,它是小端模式;51 單片機是大端模式;很多 ARM、DSP 也是小端模式(部分 ARM 處理器還可以由硬件來選擇是大端模式還是小端模式)。
五、C語言位域(位段)詳解
在結構體定義時,我們可以指定某個成員變量所佔用的二進制位數(Bit),這就是位域。請看下面的例子:
struct bs{
unsigned m;
unsigned n: 4;
unsigned char ch: 6;
};
:
後面的數字用來限定成員變量佔用的位數。成員 m 沒有限制,根據數據類型即可推算出它佔用 4 個字節(Byte)的內存。成員 n、ch 被:
後面的數字限制,不能再根據數據類型計算長度,它們分別佔用 4、6 位(Bit)的內存。
n、ch 的取值範圍非常有限,數據稍微大些就會發生溢出,請看下面的例子:
#include <stdio.h>
int main(){
struct bs{
unsigned m;
unsigned n: 4;
unsigned char ch: 6;
} a = { 0xad, 0xE, '$'};
//第一次輸出
printf("%#x, %#x, %c\n", a.m, a.n, a.ch);
//更改值後再次輸出
a.m = 0xb8901c;
a.n = 0x2d;
a.ch = 'z';
printf("%#x, %#x, %c\n", a.m, a.n, a.ch);
return 0;
}
運行結果:
0xad, 0xe, $
0xb8901c, 0xd, :
C語言標準規定,位域的寬度不能超過它所依附的數據類型的長度。通俗地講,成員變量都是有類型的,這個類型限制了成員變量的最大長度,:
後面的數字不能超過這個長度。
例如上面的 bs,n 的類型是 unsigned int,長度爲 4 個字節,共計 32 位,那麼 n 後面的數字就不能超過 32;ch 的類型是 unsigned char,長度爲 1 個字節,共計 8 位,那麼 ch 後面的數字就不能超過 8。
C語言標準還規定,只有有限的幾種數據類型可以用於位域。在 ANSI C 中,這幾種數據類型是 int、signed int 和 unsigned int(int 默認就是 signed int);到了 C99,_Bool 也被支持了。
但編譯器在具體實現時都進行了擴展,額外支持了 char、signed char、unsigned char 以及 enum 類型,所以上面的代碼雖然不符合C語言標準,但它依然能夠被編譯器支持。
5.1 位域的存儲
位域的具體存儲規則如下:
1) 當相鄰成員的類型相同時,如果它們的位寬之和小於類型的 sizeof 大小,那麼後面的成員緊鄰前一個成員存儲,直到不能容納爲止;如果它們的位寬之和大於類型的 sizeof 大小,那麼後面的成員將從新的存儲單元開始,其偏移量爲類型大小的整數倍。
以下面的位域 bs 爲例:
#include <stdio.h>
int main(){
struct bs{
unsigned m: 6;
unsigned n: 12;
unsigned p: 4;
};
printf("%d\n", sizeof(struct bs));
return 0;
}
運行結果:
4
m、n、p 的類型都是 unsigned int,sizeof 的結果爲 4 個字節(Byte),也即 32 個位(Bit)。m、n、p 的位寬之和爲 6+12+4 = 22,小於 32,所以它們會挨着存儲,中間沒有縫隙。
sizeof(struct bs) 的大小之所以爲 4,而不是 3,是因爲要將內存對齊到 4 個字節,以便提高存取效率。
如果將成員 m 的位寬改爲 22,那麼輸出結果將會是 8,因爲 22+12 = 34,大於 32,n 會從新的位置開始存儲,相對 m 的偏移量是 sizeof(unsigned int),也即 4 個字節。
如果再將成員 p 的位寬也改爲 22,那麼輸出結果將會是 12,三個成員都不會挨着存儲。
2) 當相鄰成員的類型不同時,不同的編譯器有不同的實現方案,GCC 會壓縮存儲,而 VC/VS 不會。
#include <stdio.h>
int main(){
struct bs{
unsigned m: 12;
unsigned char ch: 4;
unsigned p: 4;
};
printf("%d\n", sizeof(struct bs));
return 0;
}
在 GCC 下的運行結果爲 4,三個成員挨着存儲;在 VC/VS 下的運行結果爲 12,三個成員按照各自的類型存儲(與不指定位寬時的存儲方式相同)。
3) 如果成員之間穿插着非位域成員,那麼不會進行壓縮。例如對於下面的 bs:
struct bs{
unsigned m: 12;
unsigned ch;
unsigned p: 4;
};
在各個編譯器下 sizeof 的結果都是 12。
六、C語言位運算(按位與運算、或運算、異或運算、左移運算、右移運算)
運算符 | & | | | ^ | ~ | << | >> |
---|---|---|---|---|---|---|
說明 | 按位與 | 按位或 | 按位異或 0^1 爲1,0^0 爲0,1^1 爲0。 |
取反,~9 的結果爲 -10 |
左移。高位丟棄,低位補0。 | 右移,低位丟棄,高位補 0 或 1。如果數據的最高位是 0,那麼就補 0;如果最高位是 1,那麼就補 1。 |
&
是根據內存中的二進制位進行運算的,而不是數據的二進制形式;其他位運算符也一樣。其實就是補碼格式。
如果被丟棄的低位不包含 1,那麼右移 n 位相當於除以 2 的 n 次方(但被移除的位中經常會包含 1)。
如果數據較小,被丟棄的高位不包含 1,那麼左移 n 位相當於乘以 2 的 n 次方。
具體看鏈接:http://c.biancheng.net/view/2038.html
6.1 位運算應用
#include <stdio.h>
#include <stdlib.h>
int main(){
char plaintext = 'a'; // 明文
char secretkey = '!'; // 密鑰
char ciphertext = plaintext ^ secretkey; // 密文
char decodetext = ciphertext ^ secretkey; // 解密後的字符
char buffer[9];
printf(" char ASCII\n");
// itoa()用來將數字轉換爲字符串,可以設定轉換時的進制(基數)
// 這裏將字符對應的ascii碼轉換爲二進制
printf(" plaintext %c %7s\n", plaintext, itoa(plaintext, buffer, 2));
printf(" secretkey %c %7s\n", secretkey, itoa(secretkey, buffer, 2));
printf("ciphertext %c %7s\n", ciphertext, itoa(ciphertext, buffer, 2));
printf("decodetext %c %7s\n", decodetext, itoa(decodetext, buffer, 2));
return 0;
}
程序中的 itoa() 位於 stdlib.h 頭文件,它並不是一個標準的C函數,只有Windows下有。
itoa並不是一個標準的C函數,它是Windows特有的,如果要寫跨平臺的程序,請用sprintf。是Windows平臺下擴展的,標準庫中有sprintf,功能比這個更強,用法跟printf類似:
char str[255];
sprintf(str, "%x", 100); //將100轉爲16進製表示的字符串。
七、typedef用法
typedef oldName newName;
1.typedef int INTEGER;
2.typedef char ARRAY20[20];
表示 ARRAY20 是類型char [20]的別名。它是一個長度爲 20 的數組類型。
接着可以用 ARRAY20 定義數組:
ARRAY20 a1, a2, s1, s2;
它等價於:
char a1[20], a2[20], s1[20], s2[20];
3.typedef struct stu{
char name[20];
int age;
char sex;
} STU;
STU body1,body2;
4.typedef int (*PTR_TO_ARR)[4];
表示 PTR_TO_ARR 是類型int * [4]的別名,它是一個二維數組指針類型。接着可以使用 PTR_TO_ARR 定義二維數組指針:
PTR_TO_ARR p1, p2;
5.函數指針別名
typedef int (*PTR_TO_FUNC)(int, int);
PTR_TO_FUNC pfunc;
#include <stdio.h>
typedef char (*PTR_TO_ARR)[30];//數組指針別名
typedef int (*PTR_TO_FUNC)(int, int);//函數指針別名
int max(int a, int b){
return a>b ? a : b;
}
char str[3][30] = {
"http://c.biancheng.net",
"C語言中文網",
"C-Language"
};
int main(){
PTR_TO_ARR parr = str;
PTR_TO_FUNC pfunc = max;
int i;
printf("max: %d\n", (*pfunc)(10, 20));
for(i=0; i<3; i++){
printf("str[%d]: %s\n", i, *(parr+i));
}
return 0;
}
運行結果:
max: 20
str[0]: http://c.biancheng.net
str[1]: C語言中文網
str[2]: C-Language
還有些我覺得不需要講,自己可以判斷出來,#define宏替換純替換,typedef是數據類型別名定義。
八、const用法
const type name = value;
建議將常量名的首字母大寫,以提醒程序員這是個常量。
http://c.biancheng.net/view/2041.html
九、隨機數
http://c.biancheng.net/view/2043.html