最全C語言知識點——整合(持續更新)

一直以來都是零零散散的去書寫一些學習C語言的相關知識點,今天決定將所學習的已經發表和沒有發表的所有知識點進行一個整合,方便自己,也適用於更多學習C語言基礎的同學,如有紕漏,歡迎指出,有則改之無則加勉。

第壹部分:初識C語言和基礎知識

一:什麼是C語言程序設計

1. 什麼是C語言?

C語言是一門通用的計算機編程語言,廣泛的應用於底層的開發。C語言的設計目標是提供一種能以簡易的方式編譯,處理低級存儲器,產生少量的機器碼以及不需要任何運行環境支持便能夠運行的編程語言。

2. 第一個的C程序

#include <stdio.h>
int main()
{
printf("hello new world!\n");
return 0;
}

二:具體的數據類型

1. 都存在着那些類型?

char      字符數據類型
short     短整型
int       整形
long      長整型
long long 更長的整形
float     單精度浮點數
double    雙精度浮點數

存在這麼多的類型是爲了更加豐富的表達我們在生活之中所遇見的各種值。

2. 每種類型的大小是多少?

這段程序則是爲了我們更好的檢驗數據類型大小到底是多少最爲正確的一種方式了。

#include <stdio.h>
int main()
{
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
printf("%d\n", sizeof(long double));
return 0;
}

當然我們也應該明白對於不同的系統(32位,64位)的話,所對應的數據類型的大小是不同的,因此對於很多時候我們所考慮的大小,是需要結合相關的計算機系統來判定的,下面我們直接給大家上圖。
C語言中各類型數據的長度
一般來說對於這些相對的數據類型的大小也是要求我們儘可能地自己記住,避免在後期編程的時候出現問題。

3. 主要數據的範圍

    類型       無符號(unsigned)           有符號(signedchar          0~+255                    -128~+127
    
    short        0~65535                  -32768~32768
    
    int        0~4294967295            -2147483648~+2147483648(21)
    

這是所羅列出我們使用頻率儘可能會多一些的數據類型的取值範圍,也是爲了方便我們在後期更好的使用;除此之外,也是建議大家將2的(2-16)次方大概牢記一些,相信大多數朋友肯定應該是比較熟悉的 了吧。

4. 不同類型之間的轉換

4.1 兩種不同的類型轉換

當操作數的類型不同,而且不屬於基本數據類型時,經常需要將操作數轉化爲所需要的類型,這個過程即爲強制類型轉換。強制類型轉換具有兩種形式:顯式強制轉換和隱式強制類型轉換。
4.1.1顯式強制類型轉換:通過一定語句和相關的前綴,將所給出的類型強制轉換成爲我們所需要的類型。

TYPE b = (TYPE) a;

其中,TYPE爲類型描述符,如int,float等。經強制類型轉換運算符運算後,返回一個具有TYPE類型的數值,這種強制類型轉換操作並不改變操作數本身,運算後操作數本身未改變。
舉例1

int n=0xab65char a=char)n;

上述強制類型轉換的結果是將整型值0xab65的高端一個字節刪掉,將低端一個字節的內容作爲char型數值賦值給變量a,而經過類型轉換後n的值並未改變。
4.1.2隱式強制類型轉換:當算術運算符兩側的操作數類型不匹配的時候,會觸發“隱式的轉換”,先轉換成相同的類型,之後再進行計算。
C在以下四種情況下會進行隱式轉換:
1、算術運算式中,低類型能夠轉換爲高類型。
2、賦值表達式中,右邊表達式的值自動隱式轉換爲左邊變量的類型,並賦值給他。
3、函數調用中參數傳遞時,系統隱式地將實參轉換爲形參的類型後,賦給形參。
4、函數有返回值時,系統將隱式地將返回表達式類型轉換爲返回值類型,賦值給調用函數。

4.2 自動類型轉換

在C語言中,自動類型轉換遵循以下規則:
1、若參與運算量的類型不同,則先轉換成同一類型,然後進行運算。
2、轉換按數據長度增加的方向進行,以保證精度不降低。如int型和long型運算時,先把int量轉成long型後再進行運算。
a、若兩種類型的字節數不同,轉換成字節數高的類型
b、若兩種類型的字節數相同,且一種有符號,一種無符號,則轉換成無符號類型
3、所有的浮點運算都是以雙精度進行的,即使僅含float單精度量運算的表達式,也要先轉換成double型,再作運算。
4、char型和short型(在visual c++等環境下)參與運算時,必須先轉換成int型。
5、在賦值運算中,賦值號兩邊量的數據類型不同時,賦值號右邊量的類型將轉換爲左邊量的類型。如果右邊量的數據類型長度比左邊長時,將丟失一部分數據,這樣會降低精度,丟失的部分直接捨去。
一些需要注意的東西:
類型轉換的規則
類型轉換階層圖
在強制類型轉換的時候需要注意的是,如(double)a,則可能會丟失掉原有的精度。
想要實現運算之後的四捨五入計算,只需要給一個數加上0.5就可以直接完成。

5. 整形截斷和整型提升

5.1 整型截斷

把四個字節的變量賦值給一個字節的變量,會存在截斷,則對應的截取它低位上的值:

四字節變量
int 0x11111111 11111111 11111111 11111111;

截斷後

一字節變量
char 0x1111 1111;

5.2 何謂整形提升

C的整型算術運算總是至少以缺省整型類型的精度來進行的。
爲了獲得這個精度,表達式中的字符和短整型操作數在使用之前被轉換爲普通整型,這種轉換稱爲整型提升

5.2 整型提升的意義

表達式的整型運算要在CPU的相應運算器件內執行,CPU內整型運算器(ALU)的操作數的字節長度一般就是int
的字節長度,同時也是CPU的通用寄存器的長度。
因此,即使兩個char類型的相加,在CPU執行時實際上也要先轉換爲CPU內整型操作數的標準長度。
通用CPU(general-purpose CPU)是難以直接實現兩個8比特字節直接相加運算(雖然機器指令中可能有這
種字節相加指令)。所以,表達式中各種長度可能小於int長度的整型值,都必須先轉換爲int或unsigned
int,然後才能送入CPU去執行運算。
如:

char a,b,c;
...
a = b + c;

b和c的值被提升爲普通整型,然後再執行加法運算。
加法運算完成之後,結果將被截斷,然後再存儲於a中

5.3 如何進行整型提升

整形提升是按照變量的數據類型的符號位來提升的:
補充
( 因爲在cpu之中只能夠以

/負數的整形提升
char c1 = -1;
變量c1的二進制位(補碼)中只有8個比特位:
1111111
因爲 char 爲有符號的 char
所以整形提升的時候,高位補充符號位,即爲1
提升之後的結果是:
11111111111111111111111111111111


/正數的整形提升
char c2 = 1;
變量c2的二進制位(補碼)中只有8個比特位:
00000001
因爲 char 爲有符號的 char
所以整形提升的時候,高位補充符號位,即爲0
提升之後的結果是:
00000000000000000000000000000001


/無符號整形提升,高位補0

例1

int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if(a==0xb6)
printf("a");
if(b==0xb600)
printf("b");
if(c==0xb6000000)
printf("c");
return 0;
}

例子中的a,b要進行整形提升,但是c不需要整形提升 a,b整形提升之後,變成了負數,所以表達式
a= =0xb6 , b= =0xb600 的結果是假,但是c不發生整形提升,則表達式 c= =0xb6000000 的結果是真.
所程序輸出的結果是:

c

例2

int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(!c));
return 0;
}

例2中的,c只要參與表達式運算,就會發生整形提升,表達式 +c ,就會發生提升,所以 sizeof(+c) 是4個字節.
表達式 -c 也會發生整形提升,所以 sizeof(-c) 是4個字節,但是 sizeof( c ) ,就是1個字節.

!!!截斷提升相結合的例子!!!

#include <stdio.h>

int main()
{
	先進行截斷
	原碼 0x10000000 00000000 00000000 10000000
	反碼 0x11111111 11111111 11111111 01111111
	補碼 0x11111111 11111111 11111111 10000000
	a截斷後內存中所存儲的則是 1000 0000char a=-128;
    輸出的是十進制,則意味着要進行提升(需要根據類型而定)
    0x11111111 11111111 11111111 100000000
    0x11111111 11111111 11111111 011111111
    0x10000000 00000000 00000000 100000000
	printf("%d\n",a);
	return 0;
}

三:常量和變量

1. 常量 變量的定義

生活之中有着一些值是保持不變的,比如圓周率,身份證,血型,性別等;而有些值卻是可變的,比如我們的年齡,體重,薪資,和成績。
在C語言之中,我們經常將那些不變的值定義爲常量,也用常量這個概念名詞來進行表示,那些變化的值我們使用變量這個概念名詞來進行表示。

2. 常量

C語言之中的常量和變量的定義的形式是有所差異的。
C語言之中的常量可以分爲以下幾種:

  1. 字面常量
  2. const修飾的常變量
  3. #define定義的標識符常量
  4. 枚舉常量
    一個程序就讓大家直接明白這些到底都是什麼意思!
#include <stdio.h>
enum Sex
{
MALE,//枚舉常量
FEMALE,
SECRET
};
int main()
{
3.14;//字面常量
1000;//字面常量
const float pai = 3.14f; //const 修飾的常量
pai = 5.14;//ok?
#define MAX 100 //#define的標識符常量
return 0;

3. 定義變量的方法

int age = 150;
float weight = 45.5f;
char ch = 'w';

對於後半部分所定義的值式可以進行改變,是完全由編程者來進行定義的一種數據。

4. 變量的分類

4.1 局部變量

局部變量又稱之爲內部變量,是由某對象或者某個函數所創建的變量,只能被內部引用,不能夠被其他函數或對象引用。

4.2 全局變量

被整個程序所使用的變量稱之爲全局變量。

#include <stdio.h>
int global = 2019;//全局變量
int main()
{
int local = 2018;//局部變量
//下面定義的global會不會有問題?
int global = 2020;//局部變量
printf("global = %d\n", global);
return 0;
}

5. 變量在函數裏的使用

#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("輸入兩個操作數:>");
scanf("%d %d", &a, &b);
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
//這裏介紹一下輸入,輸出語句 有一個瞭解
//scanf
//printf

6. 變量的作用域和生命週期

6.1 作用域

作用域(scope),程序設計概念,通常來說,一段程序代碼中所用到的名字並不總是有效/可用的,而限定
這個名字的可用性的代碼範圍就是這個名字的作用域。

  1. 局部變量的作用域是變量所在的局部範圍。
  2. 全局變量的作用域是整個工程.

6.2 生命週期:

變量的生命週期指的是變量的創建到變量的銷燬之間的一個時間段

  1. 局部變量的生命週期是:進入作用域生命週期開始,出作用域生命週期結束。
  2. 全局變量的生命週期是:整個程序的生命週期。

四:字符串 轉義字符 註釋

1. 字符串

"hello bit.\n"

1.1 這種由雙引號(Double Quote)引起來的一串字符稱爲字符串字面值(String Literal),或者簡稱字符串。

1.2 字符串的結束標誌是一個 \0 的轉義字符。在計算字符串長度的時候 \0 是結束標誌,不算作字符串內容。

2. 轉義字符:

顧名思義是轉變意思,我們要在屏幕上打印一個目錄: c:\code\test.c 我們該如何寫代碼?

#include <stdio.h>
int main()
{
printf("c:\code\test.c\n");
return 0;
}

實際上程序的運行結果是這樣的:
轉義字符運行結果
由於它存在着’\t’ 和’\n’ 所以打印出愛的時候是不存在這兩個轉義字符的。

3. 都有那些轉義字符

轉義字符 釋義
\? 在書中寫連續多個問號時使用,防止他們被解析成爲三個字母詞
\’ 用於表示字符串常量 ‘
\’’ 用於表示一個字符串內部的雙引號
\\ 用於表示一個反斜槓,防止它被解釋爲一個轉義序列符
\a 警告字符,蜂鳴
\b 退格符
\f 進紙符
\n 換行
\r 回車
\t 水平製表符
\v 垂直製表符
\ddd ddd表示1~3個八進制的數字。 如: \130
\xddd ddd表示3個十六進制數字。 如: \x030
#include <stdio.h>
int main()
{
//問題1:在屏幕上打印一個單引號',怎麼做?
//問題2:在屏幕上打印一個字符串,字符串的內容是一個雙引號“,怎麼做?
printf("%c\n", '\'');
printf("%s\n", "\"");
return 0;
}

重點例題:不妨自己試試看

//程序輸出什麼?
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));
// \32被解析成一個轉義字符
printf("%d\n", strlen("c:\test\32\test.c"));
return 0;

4. 註釋

  1. 代碼中有不需要的代碼可以直接刪除,也可以註釋掉
  2. 代碼中有些代碼比較難懂,可以加一下注釋文字
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
/*C語言風格註釋
int Sub(int x, int y)
{
return x-y;
}
*/
int main()
{
//C++註釋風格
//int a = 10;
//調用Add函數,完成加法
printf("%d\n", Add(1, 2));
return 0;
}

註釋有兩種風格:

  1. C語言風格的註釋 /xxxxxx/
    缺陷:不能嵌套註釋
  2. C++風格的註釋 //xxxxxxxx
    可以註釋一行也可以註釋多行

五:內部存儲結構

1. 內部存放形式

因爲CPU只能夠進行加法運算,是不是有點打破很多小程序猿固有的思維啊,所以對於整數來說,在內存之中是以補碼的形式存放的。
符號位的表示

2. 原碼反碼補碼

正數:原碼是其本身 反碼 補碼和原碼是完全相同的
  7  原碼 0000 0111
     反碼 0000 0111
     補碼 0000 0111
 
負數:原碼是其本身 反碼符號位不變,其餘位全部取反,補碼則是在反碼的基礎上加1
 -7   原碼 1000 0111
      反碼 1111 1000
      補碼 1111 1001
零:零分爲正零+0 和負零 -0
  +0  原碼 0000 0000
      反碼 0000 0000
      補碼 0000 0000
  -0  原碼 1000 0000
      反碼 1111 1111
      補碼 0000 0000

3. 大小端

3.1 在計算機系統中是以字節爲單位的,每個地址單元都對應着一個字節,一個字節爲8bit,但在C語言中還有其他類型的存在,因此要對這些多字節安排問題就導致了大端存儲和小端存儲。
3.2 大小端的故事
根據我瞭解到的好像是一個國家在吃一個食物的時候,衆大臣討論到底應該從大的一頭開始吃,還是從小的一頭開始吃起。
3.3 大小端區別
大端是和我們的認知是相符合的,則是從低到高開始進行排列。
小端的話則和我們的認知相反,它是從小往大的排列。

int 19 
在內存中的存儲以大端序排列則爲 00 01 00 11;
在內存中的存儲以小端序排列則爲 11 00 01 00

一個代碼檢測自己的電腦到底是大端序還是小端序

#include <stdio.h>

int main(){
    int x=1;
    char c=(char)x;
    if(c==1)
    {
    	printf("小端機\n");
    }
    else if(c==0)
    {
    	printf("大端機\n");
    }
    return 0;
}

4. 什麼是ASCII碼

ASCII 是我們所看見的字母和數字在內存之中所在的十進制地址值

每一部分總結練習題和代碼鏈接:(之後會附上 )

第貳部分:操作符 關鍵字 表達式

一:操作符

1. 算數操作符

 +-*/%  求餘
  1. 除了%操作符之外,其他的幾個操作符是可以作用於整數和浮點數的。
  2. 對於 / 操作符,如果兩個操作符都爲正數的話,執行整數除法,而只要有浮點數存在就執行浮點數除法。
  3. % 操作符的兩個操作符規定必須爲整數,返回的則是整除之後所得到的餘數。

2. 移位操作符

<< 左移操作符
>> 右移操作符
  1. 左移操作符移位規則:
    左邊拋棄,右邊補0
int num=10;

num<<1  (左移一位)

左移

  1. 右移操作符移位規則:
    首先右移運算分兩種:
    1. 邏輯移位 左邊用0填充,右邊丟棄
    2. 算術移位 左邊用原該值的符號位填充,右邊丟棄

      例題
      邏輯右移:左邊補0;
      邏輯右移
      算術右移:左邊用原該值的符號位進行填充,由於是負數,所以符號位爲1,即左邊補1
      算術右移

3. 位操作符

位操作符有:

&  //按位與
|  //按位或
^  //按位異或
注:他們的操作數必須是整數

簡單練習題

#include <stdio.h>
int main()
{
int num1 = 1;
int num2 = 2;
num1 & num2;
num1 | num2;
num1 ^ num2;
return 0;
}

高難度練習題
要求:不能夠創建臨時變量(第三個變量),實現兩個數的交換。

#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d b = %d\n", a, b);
return 0;
}

4. 賦值操作符

賦值操作符是一個很棒的操作符,他可以讓你得到一個你之前不滿意的值。也就是你可以給自己重新賦值。

int weight = 120;//體重
weight = 89;//不滿意就賦值
double salary = 10000.0;
salary = 20000.0;//使用賦值操作符賦值。
賦值操作符可以連續使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//連續賦值
這樣的代碼感覺怎麼樣?
那同樣的語義,你看看:
x = y+1;
a = x;
這樣的寫法是不是更加清晰爽朗而且易於調試。

4.1 複合賦值符

+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
這些運算符都可以寫成複合的效果

例子

int x = 10;
x = x+10;
x += 10;//複合賦值
//其他運算符一樣的道理。這樣寫更加簡潔

5. 單目操作符

單目操作符 含義
邏輯反操作
- 負值
+ 正值
& 取地址
sizeof 操作數的類型長度(以字節爲單位)
~ 對一個數的二進制按位取反
- - 前置、後置- -
++ 前置、後置++
* 間接訪問操作符(解引用操作符)
(類型) 強制類型轉換
#include <stdio.h>
int main()
{
int a = -10;
int *p = NULL;
printf("%d\n", !2);
printf("%d\n", !0);
a = -a;
p = &a;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//這樣寫行不行?
printf("%d\n", sizeof int);//這樣寫行不行?
return 0;
}

6. 關係操作符

>
>=
<
<=
!= 用於測試“不相等”
== 用於測試“相等“

這些關係運算符比較簡單,沒什麼可講的,但是我們要注意一些運算符使用時候的陷阱。
!!! 注意: = 和== 的意義是不同的,用時需慎重

7. 邏輯操作符

&&    邏輯與
||    邏輯或

區分邏輯與和按位與 區分邏輯或和按位或

1&2----->0
1&&2---->1
1|2----->3
1||2---->

8. 條件操作符

exp1 ? exp2 : exp3
if (a > 5)
b = 3;
else
b = -3;
轉換成條件表達式,是什麼樣?
2.使用條件表達式實現找兩個數中較大值。

二:關鍵字

1. 常見關鍵字

auto break case char const continue default do
double else enum extern float for goto if int
long register return short signed sizeof
static struct switch typedef union unsigned void 
volatile while

2. 關鍵字typedef

typedef 顧名思義是類型定義,這裏應該理解爲類型重命名。

//將unsigned int 重命名爲uint_32, 所以uint_32也是一個類型名
typedef unsigned int uint_32;
int main()
{
//觀察num1和num2,這兩個變量的類型是一樣的
unsigned int num1 = 0;
uint_32 num2 = 0;
return 0;
}

3. 關鍵字static

在C語言中:
static是用來修飾變量和函數的

3.1 修飾局部變量

代碼1
#include <stdio.h>
void test()
{
int i = 0;
i++;
printf("%d ", i);
}
int main()
{
for(i=0; i<10; i++)
{
test();
}
return 0;
}
//代碼2
#include <stdio.h>
void test()
{
static int i = 0;
i++;
printf("%d ", i);
}
int main()
{
for(i=0; i<10; i++)
{
test();
}
return 0;
} 

對比代碼1和代碼2的效果理解static修飾局部變量的意義。
結論:static修飾局部變量改變了變量的生命週期,讓靜態局部變量出了作用域依然存在,到程序結束,生命週期才結束。

3.2 修飾全局變量

代碼1
//add.c
int g_val = 2018;
//test.c
int main()
{
printf("%d\n", g_val);
return 0;
}
代碼2
//add.c
static int g_val = 2018;
//test.c
int main()
{
printf("%d\n", g_val);
return 0;
}

代碼1正常,代碼2在編譯的時候會出現連接性錯誤。
結論:一個全局變量被static修飾,使得這個全局變量只能在本源文件內使用,不能在其他源文件內使用。

3.3 修飾函數

代碼1
//add.c
int Add(int x, int y)
{
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}
代碼2
//add.c
static int Add(int x, int y)
{
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}

代碼1正常,代碼2在編譯的時候會出現連接性錯誤。
結論:一個函數被static修飾,使得這個函數只能在本源文件內使用,不能在其他源文件內使用。

4. define定義常量和宏

define定義標識符常量
#define MAX 1000
define定義宏
#define ADD(x, y) ((x)+(y))
#include <stdio.h>
int main()
{
int sum = ADD(2, 3);
printf("sum = %d\n", sum);
sum = 10*ADD(2, 3);
printf("sum = %d\n", sum);
return 0;

使用宏的話:整體而言,宏的本質相當於文本的替換

  1. 定義一個常量
  2. 藉助宏來重定義一個類型的別名
  3. 宏還能夠影響到編譯器的行爲
  4. 宏還能夠定義一個代碼片段(類似於函數的效果)

三:表達式

1. 逗號表達式

exp1, exp2, exp3, …expN

逗號表達式,就是用逗號隔開的多個表達式。 逗號表達式,從左向右依次執行。整個表達式的結果是最後一個表達式的結果。

代碼1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗號表達式
c是多少?


代碼2
if (a =b + 1, c=a / 2, d > 0)


代碼3
a = get_val();
count_val(a);
while (a > 0)
{
//業務處理
a = get_val();
count_val(a);
}
如果使用逗號表達式,改寫:
while (a = get_val(), count_val(a), a>0)
{
//業務處理
}

2.下標引用

[ ] 下標引用操作符
操作數:一個數組名 + 一個索引值。

int arr[10];//創建數組
arr[9] = 10;//實用下標引用操作符。
[ ]的兩個操作數是arr和9

3. 函數調用

( ) 函數調用操作符
接受一個或者多個操作數:第一個操作數是函數名,剩餘的操作數就是傳遞給函數的參數。

#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //實用()作爲函數調用操作符。
test2("hello bit.");//實用()作爲函數調用操作符。
return 0;
}

4. 結構成員

訪問一個結構的成員
. 結構體.成員名
-> 結構體指針->成員名

#include <stdio.h>
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
}

四:語句和函數

未完待續(先收藏起來之後慢慢看)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章