C語言重學

重抄舊業,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、

改錯

答案:

這段代碼問題不少。

 

  1.     PSLL_NODE * ppstTable = NULL;   
  2.     if(OK != CreateHashTable(ppstTable))  

ppstTable是個二級指針,不管它是幾級的吧,要想修改一個變量的值,就得傳入該變量的地址,這里傳給CreatHashTable的可不是ppstTable的地址。

 

  1.     //待插入的元素   
  2.     PSLL_NODE pstNode = (SLL_NODE)malloc(sizeof(SLL_NODE));

應該改成:

  1.     //待插入的元素   
  2.     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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值