天天看點

C語言基礎文法學習筆記——資料類型、運算符和表達式

二、資料類型、運算符和表達式

1、資料類型

C語言基礎文法學習筆記——資料類型、運算符和表達式
C語言基礎文法學習筆記——資料類型、運算符和表達式

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';
    C語言基礎文法學習筆記——資料類型、運算符和表達式
  • 字元串常量: 由雙引号引起來的一個或多個字元組成的序列,如"","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;
}

           
C語言基礎文法學習筆記——資料類型、運算符和表達式

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) 運算符:

C語言基礎文法學習筆記——資料類型、運算符和表達式
C語言基礎文法學習筆記——資料類型、運算符和表達式

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;
}