二、資料類型、運算符和表達式
1、資料類型
1) 不同資料類型所占位元組數
- 1.1) 同一種資料類型在不同平台下所占記憶體大小亦不相同;
- 1.2) 占用記憶體:short <= int <= long; int ~= float; double = 2 * float;
2) 存儲差別
2.1) 進制:
- 二進制(B11111110)、八進制(0376)、十進制(254)、十六進制(0xFE);
- (254)10 = (11111110)2; (011 111 110)2 = (376)8; (1111 1110)2 = (FE)16;
- 小技巧:8421法;二進制轉八進制三位一組,二進制轉十六進制四位一組;
- C語言不識别二進制,不能給一個變量指派二進制數值;
2.2) 存儲形式:
- 有符号signed/無符号unsigned;
- 以補碼形式存儲;正數的補碼為其二進制本身,負數的補碼為其絕對值的二進制形式取反加一;
- 實型:以float為例:符号位(31) + 指數部分(23-30) + 精度部分(0-22);形如0.314 * 10^1;
- 字元型:标準C對于char型是否有符号是未定義的;ASCII表: 0表示字元NULL,48表示字元0,65表示字元A,97表示字元a;
3) 不同資料類型之間的轉換
- 實型轉整型不會四舍五入,而是直接丢棄小數部分;
- 隐式轉換:不同資料類型之間做運算,預設向記憶體占用大的類型靠攏;
- 顯式轉換:強制類型轉換
4) 特殊性
4.1) 布爾型bool:預設假為0值,非0即為真;
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
bool a = true;
bool b = false;
printf("a = %d, b = %d\n", a, b); // a = 1, b = 0
return 0;
}
4.2) 浮點數無法精确表示,無法與另一個數之間判斷是否相等;
#include <stdio.h>
#include <math.h>
int IsPositive(float f)
{
if (f > 0)
return 1;
// else if (f == 0)
else if (fabs(f - 0) < 1e-6) // 解決方案
return 0;
else
return -1;
}
int main(void)
{
printf("%d, %d, %d\n", IsPositive(-3.14), IsPositive(0), IsPositive(100));
return 0;
}
4.3) char型是否有符号:标準C的char型可以帶符号也可以不帶符号,由具體的編譯器、處理器或由它們兩者共同決定;
4.4) 不同形式的零值: 0 '0' "0" '\0' NULL
4.5) 資料類型與後續代碼中所使用的輸入輸出要相比對,防止自相沖突;
2、變量與常量
1) 常量
1.1) 定義:在程式執行過程中值不會發生變化的量,如:3.14、數組名,不會出現在指派符号的左邊;
1.2) 分類:
- 整型常量: 1, 790, 76, 52
- 實型常量: 3.14, 1.99999
- 字元常量: 由單引号引起來的單個字元或轉義字元,如'a','\n','\015','\x7f';
- 字元串常量: 由雙引号引起來的一個或多個字元組成的序列,如"","a","abc\n\021\018";以'\0'結束;
'\ddd'表示1~3位八進制數ddd對應的字元,如'\141'代表字元常量 'a'
'\xhh'表示1~2位十六進制數hh對應的字元,如'\x41'代表字元常量 'A'
#include <stdio.h>
#include <string.h>
int main(void)
{
printf("%llu\n", strlen("abc\n\021\017")); // 結果是6, \017是1個字元
printf("%llu\n", strlen("abc\n\021\018")); // 結果是7, \018是2個字元
printf("%llu\n", strlen("c:\test\32\test.c")); // 結果是13,\32是1個字元
printf("%llu\n", strlen("c:\test\382\test.c")); // 結果是15, \382是3個字元
printf("%llu\n", strlen("c:\test\x61\test.c")); // 結果是13, \x61是1個字元
return 0;
}
- 辨別常量: #define定義的常量;處理在程式的預處理階段,占用運作時間,不占編譯時間;特點是一改全改,缺點是不檢查文法,隻是單純的宏體與宏名之間的替換;
#include <stdio.h>
#define PI 3.14
int main(void)
{
PI + 3
PI - 4
PI * 10
PI / 12
return 0;
}
/*
gcc -E 4.c
......
int main(void)
{
3.14 + 3
3.14 - 4
3.14 * 10
3.14 / 12
return 0;
}
*/
#include <stdio.h>
#define PI 3.14
#define ADD (2+3)
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MAX_P(a,b) ({typeof(a) x=(a),y=(b); (x)>(y)?(x):(y);})
int max(int a, int b)
{
return a > b ? a : b;
}
int main(void)
{
int r = 1, a = 5, b = 3;
printf("area = %.2lf\n", PI * r * r); // area = 3.14
printf("%d\n", ADD * ADD); // 25
printf("MAX = %d\n", MAX(a, b)); // MAX = 5
/* 使用宏出現異常的情況 */
printf("a = %d, b = %d\n", a, b); // a = 5, b = 3
// printf("%d\n", ((a++)>(b++)?(a++):(b++)));
printf("%d\n", MAX(a++, b++)); // 6
printf("a = %d, b = %d\n", a, b); // a = 7, b = 4
/* 使用函數解決 */
printf("a = %d, b = %d\n", a, b); // a = 7, b = 4
printf("%d\n", max(a++, b++)); // 7
printf("a = %d, b = %d\n", a, b); // a = 8, b = 5
/* 使用宏解決 */
printf("a = %d, b = %d\n", a, b); // a = 8, b = 5
// printf("%d\n", ({typeof(a++) x=(a++),y=(b++); (x)>(y)?(x):(y);}));
printf("%d\n", MAX_P(a++, b++)); // 8
printf("a = %d, b = %d\n", a, b); // a = 9, b = 6
return 0;
}
2) 變量
2.1) 定義:用來儲存一些特定内容,在程式執行過程中值随時會發生變化的量;
2.2) 定義變量: [存儲類型] 資料類型 辨別符 = 值;
2.3) 辨別符:由字母、數字、下劃線組成且不能以數字開頭的一個表示序列;盡量做到見名知意;
2.4) 資料類型:基本資料類型 + 構造類型;
2.5) 值:注意值與資料類型比對;
2.6) 存儲類型:auto static register extern(說明型);
- auto: 預設類型,自動配置設定空間,自動回收空間;
- register: 寄存器類型,建議型(建議編譯器);隻能定義局部變量,不能定義全局變量;大小有限制,隻能定義32位大小的資料類型(對于32位作業系統),如double就不可以;寄存器沒有位址,是以一個寄存器類型的變量無法列印出位址檢視或使用;
- static: 靜态型,自動初始化為0值或空值,并且其變量的值有繼承性;修飾全局變量,防止與其他源碼檔案中的同名全局變量沖突;修飾函數時,強調函數不對外擴充;
#include <stdio.h>
void func(void)
{
int x = 0;
static int y;
x++;
y++;
printf("x = %d, &x = %p, y = %d, &y = %p\n", x, &x, y, &y);
}
int main(void)
{
auto int i;
int j;
static int s;
printf("i = %d\n", i); // i = 随機值
printf("j = %d\n", j); // j = 随機值
printf("s = %d\n", s); // s = 0
func(); // x = 1, &x = 随機值, y = 1, &y = 随機值
func(); // x = 1, &x = 不同的随機值, y = 2, &y = 相同的随機值
func(); // x = 1, &x = 不同的随機值, y = 3, &y = 相同的随機值
return 0;
}
// ./minproj/proj.h
#ifndef PROJ_H__
#define PROJ_H__
static void func(void); // static修飾函數,防止函數對外擴充
void call_func(void);
#endif
// ./minproj/proj.c
#include <stdio.h>
#include "proj.h"
static int i = 20;
static void func(void)
{
printf("[%s] i = %d\n", __FUNCTION__, i);
}
void call_func(void)
{
func();
}
// ./minproj/main.c
#include <stdio.h>
#include "proj.h"
static int i = 10; // static修飾全局變量
int main(void)
{
printf("[%s] i = %d\n", __FUNCTION__, i); // [main] i = 10
// func(); // undefined reference to `func'
call_func(); // [func] i = 20
return 0;
}
- extern: 不屬于定義型,屬于說明型關鍵字,意味着不能改變被說明的變量的值或類型;
// ./miniproj_extern/proj.h
#ifndef PROJ_H__
#define PROJ_H__
void func(void);
#endif
// ./miniproj_extern/proj.c
#include <stdio.h>
#include "proj.h"
// extern int i = 100; // warning: 'i' initialized and declared 'extern'
extern int i;
// extern float i; // 随機值
void func(void)
{
printf("[%s] i = %d\n", __FUNCTION__, i);
}
// ./miniproj_extern/main.c
#include <stdio.h>
#include "proj.h"
int i = 10;
int main(void)
{
printf("[%s] i = %d\n", __FUNCTION__, i); // [main] i = 10
func(); // [func] i = 10
return 0;
}
2.7) 變量的生命周期和作用範圍
全局變量和局部變量
局部變量和局部變量
#include <stdio.h>
int i = 100; // 全局變量
void func(int i) // 調用函數時配置設定的形參i是值傳遞
{
printf("i = %d, &i = %p\n", i, &i);
}
int main(void)
{
int i = 5; // 局部變量
{
int i = 2; // 局部變量
printf("i = %d, &i = %p\n", i, &i); // i = 2, &i = 000000000061FE18
func(i); // i = 2, &i = 000000000061FDF0
}
func(i); // i = 5, &i = 000000000061FDF0
return 0;
}
3、運算符和表達式
1) 表達式和語句的差別: 表達式(i = 1),語句(i = 1;)
2) 運算符:
2.1) 每個運算符所需要參與運算的操作數個數
單目運算符:
- 算術運算符:+表示正數;-表示負數;++表示自增;--表示自減;
- 邏輯運算符:!邏輯非;
- 求位元組數:sizeof;
- 強制類型轉換:(類型);
- 下标運算符:[];
- 指針運算符:*解引用;&取位址;
- 位運算符:~按位取反;
雙目運算符:
- 算術運算符:+加法;-減法;*乘法;/除法;%取餘;
- 關系運算符:<小于;<=小于等于;==等于;>大于;>=大于等于;!=不等于;
- 邏輯運算符:&&邏輯與;||邏輯或;
- 指派運算符:=指派;+= -= *= /= %= &= |= ^= ~= <<= >>=;
#include <stdio.h>
int main(void)
{
int a = 2;
a -= a *= a += 3; // 結合性從右向左運算
printf("a = %d\n", a); // a = 0
return 0;
}
- 逗号運算符:exp1, exp2;逗号運算符確定操作數被順序地處理,先計算左邊的操作數,再計算右邊的操作數,右操作數的類型和值作為整個表達式的結果;
- 分量運算符:.;->;
- 位運算符:<<;>>;|按位或;&按位與;^按位異或;
三目運算符:
- 條件運算符:exp1 ? exp2 : exp3;
2.2) 結合性
- /除法運算被除數不能為零;
- %取餘要求兩個操作數必須是整型;
- 自增與自減運算規則:運算符在前,先進行計算,再取變量值使用;變量在前,先取變量值使用,再進行計算;
#include <stdio.h>
int main(void)
{
int i = 1, j = 10, v;
v = i++ + ++j;
printf("i = %d\n", i); // i = 2
printf("j = %d\n", j); // j = 11
printf("v = %d\n", v); // v = 12
v = --i + j++;
printf("i = %d\n", i); // i = 1
printf("j = %d\n", j); // j = 12
printf("v = %d\n", v); // v = 12
return 0;
}
- =指派運算符,==關系運算符相等;
- 邏輯運算符(&&、||)的短路特性;
#include <stdio.h>
int main(void)
{
int a = 15, b = 20, c = 100, d = 200;
int m = 1, n = 1;
int j = 1, k = 1;
(m = a > b) && (n = c > d);
printf("m = %d, n = %d\n", m, n); // m = 0, n = 1
(j = a > b) || (k = c > d);
printf("j = %d, k = %d\n", j, k); // j = 0, k = 0
return 0;
}
- 将操作數中第n位置為1,其他位不變:num = num | 1 << n;
- 将操作數中第n位置為0,其他位不變:num = num & ~(1 << n);
- 測試第n位:if (num & 1 << n)
#include <stdio.h>
int main(void)
{
int a = 16; // B10000
a = a | 1 << 2; // 将第二位置為1,其他位不變
printf("%d\n", a); // 20 B10100
a = a & ~(1 << 2); // 将第二位置為0,其他位不變
printf("%d\n", a); // 16
printf("%d, %d\n", a & 1 << 4, a & 1 << 3); // 16, 0
return 0;
}