前言
C語言博大精深,我在把譚浩強那本《c語言程序設計》看完後發現碰到開源程序仍有看不懂的東西,所以把題解與上機指導那本書拿來補充一下缺漏知識。
先放出譚浩強《C程序上機題解與上機指導》的補充知識部分的目錄
一、位運算:
說明:
(1)位運算符中除“~”以外,均爲二目(元)運算符,即要求兩側各有一個運算量。如a&b.
(2)參加位運算的對象只能是整型或字符型的數據,不能爲實型數據。下面對各種位運算分別介紹。
#include<stdio.h>
int main()
{
unsigned a = 0xffffe0ff, b = 0xffffc2ff;
//%x按16進制輸出。
printf("%x\n", a & b); //按位與
printf("%x\n", a | b); //按位或
printf("%x\n", a ^ b); //按位異或
printf("%x\n", ~a); //按位取反
printf("%x\n", a << 4); //左移4位(乘以2的4次方)
printf("%x\n", a >> 8); //右移8位(除以2的8次方)
printf("這裏一個f佔4位,即‘1111’,十進制的15\n");
return 0;
}
例:實現循環右移
#include<stdio.h>
#define WORDSIZE 32 //我現在是X86模式編譯,佔32位,4字節(32位=4字節)
int main()
{
unsigned a, b, c;
int n;
printf("請輸入a和n \n");
scanf("%x,%d", &a, &n);
b = a << (WORDSIZE - n);//書中源碼這裏是16(字節),我運行發現總是不對,發現我現在是X86模式編譯,佔32位
c = a >> n;
c = c ^ b;
printf("a:%x \nc:%x", a, c);
return 0;
}
運行結果:
以下來自:
https://zhidao.baidu.com/question/165145005.html
移位 -- 不是“位移”操作
n<<3,2進制,左移3位,就是 n=n*2*2*2, 就是n*8
0,1,2,3 -- 變 0,8,16,24
x >> 24 最高字節 移到最左, & 0xff 取出來。
x >> 16 第二 高字節 移到最左, & 0xff 取出來。
x >> 8 第3 高字節 移到最左, & 0xff 取出來。
x >> 0 不移動,& 0xff 取出來。
例如:
00111111 01010101 11111111 00000001
x >> 8 得
00000000 00111111 01010101 11111111
x >> 16 得
00000000 00000000 00111111 01010101
x >> 24 得
00000000 00000000 00000000 00111111
關於“位段”
c語言允許在一個結構體中以位爲單位來指定其成員所佔用的內存長度,
這種以位爲單位的成員成爲“位段”或“位域”(bit field)。
利用位段能用較少的位數存儲數據
使用“位段”
類型名 [成員名]:寬度;
#include<stdio.h>
struct Packed_data
{
unsigned a : 2;
unsigned b : 2;
unsigned c : 2;
unsigned d : 2;
short i;
}data1;
struct Packed_data2
{
unsigned a : 2;
unsigned b : 3;
unsigned c : 4;
short i;
}data2;
int main()
{
printf("%d\n", sizeof(data1));
printf("%d\n", sizeof(data2));
return 0;
}
實際上會遇到系統的“自動補全”(至少佔一個存儲單元,即一個機器字(64位的一個字(WORD)是8字節)),結果如上圖,data2也是8個字節。
*位段空間分配方向因機器而異。一般是從右到左進行分配(如圖)。
二、預處理:
C語言允許在源程序中加人一些“預處理指令”( preprocessing directive),以改進程序設計環境,提高編程效率。這些預處理指令是由C標準建議的,但是它不是C語言本身的組成部分,不能用C編譯系統直接對它們進行編譯(因爲編譯程序不能識別它們)。必須在對程序進行正式編譯(包括詞法和語法分析、代碼生成、優化等)之前,先對程序中這些特殊的指令進行“預處理"(preprocess,也稱"編譯預處理"或“預編譯”)。把預處理指令轉換成相應的程序段,它們和程序中的其他部分組成真正的C語言程序.對預處理指令進行的預處理工作,是由稱爲C預處理器(preprocessor)的程序負責處理的.
在預處理階段,預處理器把程序中的註釋全部刪除;對預處理指令進行處理,如把#include指令指定的頭文件(如stdio.b)的內容複製到#include指令處:對#define指令,進行指定的字符替換(如將程序中的符號常量用指定的字符串代替),同時刪去預處理指令。
經過預處理後的程序不再包括預處理指令了,最後再由編譯程序對預處理後的源程序進行實際的編譯處理,得到可供執行的目標代碼。現在使用的許多C編譯系統,把C預處理器作爲C編譯系統的一個組成部分,在進行編譯時一氣呵成。因此有的用戶誤認爲預處理指令是C語言的一部分,甚至以爲它們是C語句,這是不對的。必須正確區別預處理指令和C語句區別預處理和編譯,才能正確使用預處理指令。C語言與其他高級語言的一個重要區別是可以使用預處理指令和具有預處理的功能。
1.預處理指令:
https://www.cnblogs.com/zi-xing/p/4550246.html
- #空指令,無任何效果
- #include包含一個源代碼文件
- #define定義宏
- #undef取消已定義的宏(限制作用域)
- #if如果給定條件爲真,則編譯下面代碼
- #ifdef如果宏已經定義,則編譯下面代碼
- #ifndef如果宏沒有定義,則編譯下面代碼
- #elif如果前面的#if給定條件不爲真,當前條件爲真,則編譯下面代碼
- #endif結束一個#if……#else條件編譯塊
- #error停止編譯並顯示錯誤信息
書上說了3種情況:
- 宏定義(3,4)
“宏定義”是用指定標識符代替字符串(不分配內存,所以不要把 宏名 當做 變量名 使用);同時也是條件編譯的“條件”
例,把“3.14”宏置換爲“PI”
#define PI 3.14
詳細介紹:https://www.jianshu.com/p/2915629103e4
- 文件包含處理(2)
“文件包含”指令是很有用的,它可以節省程序設計人員的重複勞動。
例如,某單位的人員往往使用一組固定的符號常量(如g-9.11,pi-3. 1415926,e= 2.71........可.以把這些宏定義指令組成一個頭文件,然後各人都可以用# include指令將這些符號常量包含到自己所寫的源文件中,而不必自己重複定義這些符號常量,相當於工業上的標準零件,拿來就用。
#include "BiaoHaoKu"
- 條件編譯(5,6,7,8,9)
條件編譯對於提高C源程序的通用性是很有好處的。
如果一個C源程序在不同計算機系統上運行,而不同的計算機又有一-定的差異(例如,有的機器以16位(2個字節)來存放一個整數,而有的則以32位存放一個整數),這樣在不同的計算機上編譯程序時往需要對源程序作必要的修改,這就降低了程序的通用性。
例:解決int位長
#ifedf COMPUTER A
#define INTEGER 16
#cise
#define INTEGER_SIZE 32
#endif
如果在↑上面這組條件編譯指令之前曾出現以下指令行:
#defne COMPUTER_A 0
或將COMPUTERA定義爲任何字符串,甚至是
# deline COMPUTER_A
即只要COMPUTER_A已被定義過,則在程序預編譯時就會奏效——預編譯後程序中的INTEGER SIZE都用16代替否則都用32代替。
這樣,源程序可以不必做任何修改就可以用於不同類型的計算機系統。當然以上介紹的只是一種簡單的情況,讀者可以根據此思路設計出其他條件編譯。
例:部分模式時有用(在想輸出信息時輸出信息)
#ifedf DEBUG
printf("開啓debug模式。x=%d",x);
#endif
2.關於 #pragma once:
方式一:
#ifndef的方式依賴於宏名字不能衝突,這不光可以保證同一個文件不會被包含多次,也能保證內容完全相同的兩個文件不會被不小心同時包含。當然,缺點就是如果不同頭文件的宏名不小心"撞車",可能就會導致頭文件明明存在,編譯器卻硬說找不到聲明的狀況
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
... ... // 一些聲明語句
#endif
方式二:
#pragma once則由編譯器提供保證:同一個文件不會被包含多次。注意這裏所說的"同一個文件"是指物理上的一個文件,而不是指內容相同的兩個文件。帶來的好處 是,你不必再費勁想個宏名了,當然也就不會出現宏名碰撞引發的奇怪問題。對應的缺點就是如果某個頭文件有多份拷貝,本方法不能保證他們不被重複包含。當 然,相比宏名碰撞引發的"找不到聲明"的問題,重複包含更容易被發現並修正。
#pragma once
... ... // 一些聲明語句