重抄舊業,C語言細節忘了不少,重學 C
重點有幾種;
一、sizeof各種東西;
二、指針;
三、strcpy、strlen、memset
四、對齊
五、宏、預編譯
六、malloc、free
七、數值表示方法、補碼、移位等操作
八、函數指針
九、變量重名、作用域及代碼鏈接
十、const
十一、大小端
十二、static、extern
十三、嵌入式高級用法
1、sizeof
int * p = NULL;
sizeof(p) = 4; //32為系統中,指針永遠是佔4個字節
2、sizeof
char str[] = "12345";
sizeof(str) = 6; //這個就是數組佔空間的大小,注意字符串結尾有個/0
3、sizeof
void f(char str1[], char str2[100])
{
printf("%d, %d/n", sizeof(str1), sizeof(str2));} //答案是4,4,這裡的str1、str2已經蛻變成指針了
//函數參數聲明稱數組 == 聲明稱指針,事實上我從來都是聲明稱指針,這樣更清晰些
return;
}
說到這裡,我們順便提一下,sizeof還可以丈量函數。你可能以前不知道,也沒用過。sizeof(函數())返回的是函數返回值的佔位大小,不是函數佔了多少個字節,也不是函數名佔了多少字節哦。
比如:
short f(int x)
{
...
}
printf("%d/n", sizeof(f(100));
答案:2
注意,sizeof裡面一定要帶上函數的參數,無參數要這樣寫sizeof(f());,不然編譯出錯。如果函數無返回值,那麼sizeof無法丈量,編譯會出錯。
4、
unsigned short usA = 10;
printf("~usA = %u/n", ~usA);
char cC = 128;
printf("cC = %d/n", cC);
答案:
~usA = 4294967285
cC = -128
5、
char A[20];
sizeof(A) = 20;
strlen(A) = 未知;//数组A未初始化,里面的值是以前程序用剩下的,直到碰到一个/0值,strlen函数才返回
6、
typedef struct tagAA
{
int b1:5;
int b2:2;
}AA;
void main(void)
{
AA aa;
char cc[100];
strcpy(cc, "0123456789abcdefghijklmnopqrstuvwxyz";
memcpy(&aa, cc, sizeof(AA));
printf("aa.b1 = %d/naa.b2 = %d/n", aa.b1, aa.b2);
}
答案:
aa.b1 = -16
aa.b2 = 1
結構中的段位分配是從地位開始的,也就是b1 = 0~4bit,b2 = 5~6bit;
該結構佔4個字節,其中3個字節是4字節對齊空出來的,前面那個字節有1位沒用;
字符0、1、2、3……在計算機中的值分別是0x30、0x31、0x32……;
aa佔4個字節,所以最後只有'0'、'1'、'2'、'3'四個字符別複製到aa中,其中只有'0'會影響b1、b2;
'0' = 0x30 = 11 0000b = 0011 0000b,其中低5位是b1的,即b1 = 1 0000b,b2 = 01b,注意b2是01b不是1b,這很重要,因為b2是int,有符號,所以01表示符號位是0,就是個正數;
這裡的b1、b2都是補碼,b2是正數,補碼 == 原碼,所以b2 = 1;
b1 = 1 0000b(補碼) = 1111 0000b(補碼,擴充高位),取反加一得到原碼就是0000 1111b(取反),0001 0000(加一) = 16,b1又是負數,所以 b1 = -16。
7、
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d, %d/n", *(a+1), *(ptr - 1));
答案:
2, 5
&a系數組a的地址, 單位是整個數組,不是其中的一個元素,所以+1就是讓指針偏移了sizeof(a)即5個字節,指到“後面一個數組”(如果有的話)的首地址去了,再把它強制轉換成int *型指針,再-1,就指到數組a的最後一個元素即a[4]了,取值自然就是5,這就是*(ptr - 1)
a是數組的首地址,也是第一個元素的地址,+1偏移到後面一個元素上去了。
聲明指向數組的指針:int (* ptr)[5];
考察的是a和&a的區別。
8、
float型變量與0比較:
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON)
浮点型变量并不精确,所以不可将float变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。如果写成
if (x == 0.0),则判为错,得0分。
9、寫一個MIN、MAX宏
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
#define MAX(A,B) ((A) >= (B) ? (A) : (B))
10、
用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
11、
如下代碼片段輸出什麼?
main()
{
char *p1=“name”;
char *p2;
p2 = (char *)malloc(20);
memset (p2, 0, 20);
while(*p2++ = *p1++);
printf(“%s/n” ,p2);
}
啥都不輸出。
看仔細了,p2的值變了,指到後面去了,後面是/0。
這段代碼很糟,既沒有檢查內存是否分配成功也沒有釋放內存。
12、
int x = 1, y = 2;
x = y++ + x++;
y = ++y + ++x;
printf("x = %d, y = %d", x, y);
答案:x = 5, y = 9
這題很無聊,如果有員工在我的項目組裡敢這樣寫,老子打死他!
只有用彙編來解釋:
x = y++ + x++;
00D333BC mov eax,dword ptr [y] ;把y放到eax裡
00D333BF add eax,dword ptr [x] ;把x加到eax裡,實現了y + x
00D333C2 mov dword ptr [x],eax ;把eax存回x,即x = y + x
00D333C5 mov ecx,dword ptr [x] ;把x放到ecx裡
00D333C8 add ecx,1 ;++x了
00D333CB mov dword ptr [x],ecx ;放回去
00D333CE mov edx,dword ptr [y] ;同理y
00D333D1 add edx,1
00D333D4 mov dword ptr [y],edx
y = ++y + ++x;
00D333D7 mov eax,dword ptr [y] ;同理上一段
00D333DA add eax,1
00D333DD mov dword ptr [y],eax
00D333E0 mov ecx,dword ptr [x]
00D333E3 add ecx,1
00D333E6 mov dword ptr [x],ecx
00D333E9 mov edx,dword ptr [y]
00D333EC add edx,dword ptr [x]
00D333EF mov dword ptr [y],edx
13、
switch (a)
{
case 1:
CString str="ABCDE"; //这句编译有错误。请问为什么?
break;
case 2:
break;
....
}
在switch里只能加{}来定义变量。
詳細解釋
http://apps.hi.baidu.com/share/detail/32190288
裡說得很好。
另外switch 后面的表达式不能跟double,float,long,String ,boolean,可以接int,short,byte,char
14、原碼、反碼和補碼
正數:原码 == 反码 == 补码
負數:原码 = 符号位1,绝对值原码
反码 = 符号位不動,其餘各位取反
补码 = 反碼 + 1
+1原码 == 反码 == 补码(8位):0000 0001b
-1原码(8位):1000 0001b
反码:1111 1110b
补码:1111 1111b
0有+0和-0之分
+0原码(8位):0000 0000b
反码、补码同。
-0原码(8位):1000 0000b
反码:1111 1111b
补码:0000 0000b
+3原码(8位):0000 0011b
-3原码(8位):1000 0011b
反碼(8位):1111 1100b
補碼(8位):1111 1101b
參見:
http://topic.csdn.net/u/20100624/16/8a53bca7-2fe3-49be-a7ab-240b308d6d9c.html
已知某數補碼,求其原碼。
先要判斷該補碼表示的是正數還是負數。最高位 == 0為正數,反之為負數。
補碼:0000 1011b
最高位 == 0,所以是正數的補碼
補碼 == 反碼 == 原碼 == 1011b == 11
補碼:1111 1011b
最高位 == 1,所以是負數的補碼
除符號位外減一得到反碼:1111 1011b - 1 = 1111 1010b
除符號位外取反得到原碼:1111 1010b -> 1000 0101b == -5
15、用宏定义写出swap(x, y)
#define swap(x, y)/
x = x + y;/
y = x - y;/
x = x - y;
16、
unsigned short A = 10;
printf("~A = %u/n", ~A);
char c = 128;
printf("c = %d/n", c);
答案:
~A = 4294967285
c = -128
【這裡我解釋地很清楚,為的是知其所以然】
數字也好、字符也好、指針也好,在計算機裡都是一個數,看你把這個數解讀成什麼,這就是類型轉換的本質。比如你要把一個數+48儲存到內存某個地方,計算機就這麼做了,那個內存單元變成48,回頭你要用的時候又要把它解讀成一個字符,OK,沒問題,計算機就把48變成對應的字符,或者你要求計算機把它解讀成別的什麼東西,都可以。事實上計算機都可以不知道當初是以什麼方式存進去的,一個數還是一個字符?它都不關心,反正那個地方就是48。這是本質,理解了這一點再來搞這些奇奇怪怪的考試題就不難了。
計算機是以補碼的方式存儲的。以字長32位為例,你要存個int = +48,它就把它化成補碼[+48]補碼 = 原碼 = 0000 0000, 0000 0000, 0000 0000, 0011 0000b,這一長串二進制數就存在了內存的某4字節單元中了。計算機玩去了,不管它是什麼。當你要用的時候,你對計算機說:“到那個地方去,把那一串數字給我解讀成一個int”,由於是int,帶符號的,於是計算機跑到那裡去一看,哦,符號位(最高位)= 0,是個正數,證書的原碼就是補碼,所以這個就是原碼,把它化成10進制數就是+48了。如果你說“把它給我解讀成一個字符”,那麼計算機先把它解讀成一個char,這很複雜。int佔4個字節,char佔一個字節,轉化有兩種方式:char取int的高一字節或取其低一字節,計算機約定這種“從大往小”的轉換截取低字節,本例中取了0011 0000b。計算機得先把這個傳化成一個char,char是有符號的,按照剛才int轉換成數字的方式這也是個正數,原碼 = 補碼 = 0011 0000b = 48,然後計算機再查ASCII碼表,看看48對應哪個字符,一看,哦原來是字符'0'啊,於是乎就把字符'0'給了你。
我們再來看看這題,把unsigned short +10化成補碼存進去,存了0000 0000, 0000 1010b,用(printf)的時候把它原樣取出來取個反,即變成1111 1111, 1111 0101b,再把這傢伙按照無符號int解釋,無符號勘定是個正數,好傢伙,這傢伙可不小,於是乎算是出來它 = 4294967285。
char c = 128;
printf("c = %d/n", c);
這一段,c是char類型,char表示範圍是。。。-128~127,呼呼越界了哈,VC會出警告。但是不管怎麼樣,你非要這麼做,計算機也不攔著你,它會先把+128化成二進制數,然後認為“c的值”就是1000 0000b,這個c的值還不是補碼,於是乎它得看看c到底是個什麼東西,一看,c是char,哦,那最高位是符號位,計算機這麼想。於是乎計算機心說“這是個負數,老規矩。。。”,就把1000 0000b按負數變成補碼1000 0000b存了起來。記住,這時變量c的內存裡就是牢牢地存著1000 0000b,你說他是正數也好負數也罷,它就是1000 0000b這麼個東西。下一步,printf的時候,注意看,是%d,要求把它解讀成int型!CPU就想了:娘西皮,ntnnd一個字節的要老子解讀成4字節的int,還是有符號的。。。又得擴充了。。。”。“根據行規”(不是潛規則哈),是按高位擴充,也就是1000 0000b -> 1111 1111, 1111 1111, 1111 1111 1000 0000b,黃底部分的都是擴充出來的。然後再按int解讀,補碼變原碼,顯然是個負數,原碼是0000 0000, 0000 0000, 0000 0000, 1000 0000b = 128,加上符號就是-128。
這裡考察的是溢出處理的問題,可以記住一個規律就是“一個有符號的數,最大值+1就等於它的最小值了”,比如上題中的+127加一就成-128了。
部分參考http://blog.163.com/kmustchenb@126/blog/static/11090576520109910498798/。
我們在擴展一下,如果是
char c = 128;
printf("c = %c/n", c);
會是什麼結果呢?
答案:c = €
c的從年初範圍雖然不能超過+127,但是計算機還是將+128“強行”存入了c的內存,打印的時候%c是將該內存中的數字化成unsigned char,再查ASCII表。那個c用解釋成unsigned char自然是+128了,查表128對應“€”。
看看下面聲明:
char cX = '€';
unsigned char ucX = '€';
int iX = '€';
unsigned int uiX = '€';
它們都“強行”或非強行地將128存入了對應的內存。
printf("%c/n", cX);
printf("%c/n", ucX);
printf("%c/n", iX);
printf("%c/n", uiX);
printf("%d/n", cX);
printf("%d/n", ucX);
printf("%d/n", iX);
printf("%d/n", uiX);
printf("%u/n", cX);
printf("%u/n", ucX);
printf("%u/n", iX);
printf("%u/n", uiX);
哈哈,估計會考倒一堆人。
第一組,打印出來全是€。因為第一組按%c解釋,%c只看最低字節,如果只有一個字節,就按剛才說的化成unsigned char,前兩個都是+128,再查表。如果不止一個字節,像int之流,就截取最低一個字節,再化成unsigned char,這些int、unsigned int不管高字節擴充成什麼,第一字節都是128,所以截來截去、化來化去都是128,都是€。
第二組,都按%d打印。有正負之分,而且後面兩個(int、unsigned int的)會看全4個字節。結果是
-128
128
-128
-128
我們看一下這些變量在內存裡都是啥樣的(還沒化成補碼):
cX = 0x80 //0x80 == 128
ucX = 0x80
iX = 0xFFFFFF80 //按高位擴充
uiX = 0xFFFFFF80 //按高位擴充
由於賦的值是+128,所以補碼 == 原碼,化成補碼(與上面的原碼相同):
cX = 0x80
ucX = 0x80
iX = 0xFFFFFF80
uiX = 0xFFFFFF80
頭兩個無變化。
打印結果,第一個前面已經講過了。第二個unsigned char,按%d肯定是128。因為人家本來就存的下128。第三個和第四個都擴充了字節,按高位擴充,前面講過。
iX = 0xFFFFFF80[補碼] = 1111 1111, 1111 1111, 1111 1111, 1000 0000b,按%d解釋,為一個負數,還原成原碼,
除符號位-1 = 1111 1111, 1111 1111, 1111 1111, 0111 1111b,
除符號位外取反 = 1000 0000, 0000 0000, 0000 0000, 1000 0000b[原碼] = -128。
printf %d uiX的情況與iX相同。
第三組結果:
4294967168
128
4294967168
4294967168
知道了四個變量在內存中的補碼,結果是什麼就很容易了。%u是按4字節無符號數打印的,所以前二個要擴充。(還沒寫完。。。。)
cX = 0xFFFFFF80
ucX = 0x0000080
iX = 0xFFFFFF80
uiX = 0xFFFFFF80
時隔多年居然在csdn寫了這麼多,真累。。。
16、extern "C"用法
使C++能夠使用C編譯的函數。C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。
__cplusplus是C++中的自定义宏,定义了这个宏的话表示这是一段cpp的代码。
main.cpp
#ifdef __cplusplus
extern "C"
{
#endif
#include "cExample.h" //包含的C語言頭文件
#ifdef __cplusplus
}
#endif
詳細解釋參考
http://baike.baidu.com/view/2814224.htm#sub2814224
17、防止頭文件被重複包含
方法一:在頭文件中測試並定義一個宏
head1.h
#ifndef _HEAD1_H_
#define _HEAD1_H_
//文件內容
...
#endif
方法二:VC下載頭文件開始處加上預處理指令#pragma once
推薦使用第一種方法,因為通用性強。
18、VC下對齊控制指令
#pragma pack(2) //強制1字節對齊
....
#pragma pack() //還原,一般是4字節對齊
19、
輸出:
sizeof(char)=1
sizeof(short)=2
sizeof(int)=4
sizeof(long)=4
sizeof(float)=4
sizeof(double)=8
sizeof(double long)=12
sizeof(long double)=12
sizeof(temp3)=4
sizeof(p_char)=4
sizeof(p_short)=4
sizeof(p_int)=4
sizeof(p_long)=4
sizeof(p_float)=4
sizeof(p_double)=4
sizeof(p_double_long)=4
sizeof(p_long_double)=4
sizeof(*p_char)=1
sizeof(*p_short)=2
sizeof(*p_int)=4
sizeof(*p_long)=4
sizeof(*p_float)=4
sizeof(*p_double)=8
sizeof(*p_double_long)=12
sizeof(*p_long_double)=12
sizeof(array)=16
sizeof(*array)=1
sizeof("123")=4
sizeof(str)=11
sizeof(*str)=1
strlen(str)=10
char1=0
short1=-212
int1=1981285032
long1=1980671042
float1=0.000000
double1=0.000000
double_long1=0.000000
long_double1=-1.#QNAN0
c_array=2293408
temp1=-55623329
temp2=0
20、
改錯
答案:
這段代碼問題不少。
- PSLL_NODE * ppstTable = NULL;
- if(OK != CreateHashTable(ppstTable))
ppstTable是個二級指針,不管它是幾級的吧,要想修改一個變量的值,就得傳入該變量的地址,這里傳給CreatHashTable的可不是ppstTable的地址。
- //待插入的元素
- PSLL_NODE pstNode = (SLL_NODE)malloc(sizeof(SLL_NODE));
應該改成:
- //待插入的元素
- PSLL_NODE pstNode = (PSLL_NODE)malloc(sizeof(SLL_NODE));
考察指針的加減運算。
該題構建了一個哈希表,表中的每一項並不直接存儲數據,而是一個指針,指針指向一個單鏈表節點,這個節點的iData域存儲訊息。
哈希表可以用數組做,比如struct Node * Hash[100],這個哈希表有100個表項,每個表項是一個指向數據結構Node的指針。
這個數組聲明在函數里就是個臨時變量,函數返回後就失效了,做成全局變量倒是可以,但全局變量並不推薦,因為它永遠佔著那塊內存,尤其是如果這裡的表項不是100而是1000,那“永遠”佔的內存就太大了。
所以這裡用了一種方法:malloc一塊內存,再把它初始化成一個指向Node的指針數組。“數組”的首地址是ppstTable。
如果你是老手,你必定也經常這麼用,這段解釋可以跳過。
這段程序的錯誤出在Insert函數。該函數的思路是用一個指針尋址那個指針數組,找到相應的表項,操作之。想法是好的,但寫得有問題。
if( NULL == *(pstTable + ucOffset))寫錯了。
這裡尋址指針數組用的是pstTable,這是個指向SLL_NODE的結構指針,這個指針偏移一個單位可是一個SLL_NODE,8個字節。可是被尋址的指針數組一個單位是4字節(要記住32位系統中一個指針永遠是4字節),對像都搞錯了。這樣寫也是編不過的。因為*(pstTable + 偏移量)得到的是個SLL_NODE結構,結構不能比較大小,更不能和一個數這裡是NULL比。
這樣寫,if(NULL == pstTable + ucOffset),也不對。這句倒是可以編過,但是意思都搞錯了,況且pstTable不會 == NULL,再加偏移量就肯定不會 == NULL啦。
正確的改法:把指向結構的指針換成指向結構的指針的指針,指針的指針,指向的是一個指針,偏移單位是4,取值取出來的是指向結構的指針,是指針,可以跟NULL比。
不斷增加中。。。
21、聲明一個函數FunctionA,
傳入參數是int,
返回值是一個函數指針,該指針指向一個這樣類型的函數:char * FunctionB(short BP)
char *(*FunctionA(int AP))(short BP);