知識(shí)盤點(diǎn):老師上課不會(huì)講到的C語言知識(shí)!補(bǔ)課了的已經(jīng)面試成功
對(duì)于工科生,C語言是一門必修課。?標(biāo)準(zhǔn)C(ANSI C)?這個(gè)?看似簡單?的語言在硬件底層編程、嵌入式開發(fā)領(lǐng)域還是穩(wěn)坐頭把交椅。在20年5月份,C語言就憑借其在醫(yī)療設(shè)備上的廣泛應(yīng)用,時(shí)隔五年重回編程語言榜首。

同學(xué)們?cè)谀玫綄W(xué)分之后還有沒有使用這門“手藝”呢?
想做軟硬件項(xiàng)目的同學(xué)還需要補(bǔ)足哪些知識(shí)呢?
不論是正在學(xué)習(xí)還是曾經(jīng)學(xué)習(xí)過C語言的同學(xué),這篇文章總結(jié)的一些要點(diǎn)能提供一個(gè)?新的角度來理解C語言的設(shè)計(jì)理念和特性?。
一起來看看吧!
原文鏈接:https://www.cnblogs.com/huxiaoan/p/14749360.html?utm_source=tuicool&utm_medium=referral

關(guān)鍵字
一共有多少個(gè)關(guān)鍵字?這個(gè)的確不好說,在C99和C11里都添加了新的關(guān)鍵字,也有的關(guān)鍵字由于過時(shí)淡出了我們的視線。下面這些關(guān)鍵字的用法都掌握了嗎?
auto
它可謂默默無聞,不少人應(yīng)該知道它沒什么用——局部變量默認(rèn)就是auto類型。除此之外,auto變量存放在動(dòng)態(tài)儲(chǔ)存區(qū)中的棧區(qū)。也就是這種變量時(shí)有時(shí)無,壽命可變,自動(dòng)(auto)管理。
注意:正因?yàn)閬砣プ匀?,?chuàng)建的局部變量不會(huì)自動(dòng)初始化為零??!切記
但是,在C++11里auto翻身了,可以用作?類型推導(dǎo)?。比如:

可是這和C有什么關(guān)系?
static
你能想到static的幾個(gè)用法?有人說是三個(gè),我覺得就兩個(gè)。
修飾全局變量或函數(shù)時(shí),它的作用是?僅限本文件訪問?。C語言里沒有命名空間,如果不加static,不同文件里的同名變量會(huì)引起混淆,畢竟他們的作用域相同。
修飾局部變量,成為存放在靜態(tài)儲(chǔ)存區(qū)的靜態(tài)變量。生命周期為整個(gè)程序執(zhí)行過程。與auto不同,靜態(tài)變量在程序開始之前就初始化完畢。這也就是常說的第三個(gè)用法,將局部變量初始化為零。其實(shí)這只是變量存放在靜態(tài)儲(chǔ)存區(qū)的一個(gè)特征,所以不單獨(dú)拿出來。
volatile
大多數(shù)學(xué)校課程是不會(huì)用到它的,接觸單片機(jī)和多線程就能懂得它的重要性。
volatile的意思是”易變的、無常的“,名副其實(shí)。
volatile是對(duì)變量的修飾,比如?volatile int flag=0;?它是對(duì)編譯器的提醒:
”嘿,這變量是變化無常的,你可小心點(diǎn)!“
這針對(duì)的是編譯器的”小聰明“——優(yōu)化。
舉個(gè)例子

計(jì)算機(jī)運(yùn)算要先把變量從內(nèi)存加載到寄存器,這一步是耗時(shí)間的。編譯器一看,前腳我才讓flag=1,這個(gè)flag還在寄存器里,到下一句判斷之間也沒有能改變flag的語句,那我不直接用這個(gè)寄存器里的flag=1嘛。
可萬萬沒想到,就在flag=1之后,if之前,來了一次?硬件中斷?,終端回調(diào)函數(shù)把flag改成0了。
編譯器是料不到的,就認(rèn)為flag=1。這在很多實(shí)際情況下是很恐怖的。
不僅僅是中斷,插入一段匯編,其他線程改變內(nèi)存都會(huì)引起這類問題。
如果改為?volatile int flag=0;?,凡是用到這個(gè)變量,?就會(huì)去內(nèi)存里不怕麻煩地找到它?,不再偷懶。
當(dāng)時(shí)我年輕不懂事,一個(gè)單片機(jī)項(xiàng)目里用中斷改變標(biāo)志位。怎么都不正常,后來哥們讓我在標(biāo)志前面加個(gè)volatile就解決了。。。
__WEAK
這?不是個(gè)關(guān)鍵字?,這只是GCC的一個(gè)特性??碨TM32官方固件庫的同學(xué)應(yīng)該沒少見到它,但它不是C++里virtual那種虛函數(shù)。?如果有同名的不帶_WEAK前綴的函數(shù),優(yōu)先使用不帶的。
如果用戶自定義了,那就使用用戶的,如果沒有,那就用默認(rèn)的。這樣方便用戶自定義一些回調(diào)函數(shù)、處理函數(shù)。
類型
相信大家對(duì)C語言的?強(qiáng)類型?特性印象非常深刻。尤其是printf的格式化輸出和復(fù)雜指針的類型。
程序不就是數(shù)據(jù)結(jié)構(gòu)+算法,基本類型則是構(gòu)成數(shù)據(jù)結(jié)構(gòu)高樓大廈的一磚一瓦。
char
冥冥之中,我覺得char類型是最神奇的類型。在C語言標(biāo)準(zhǔn)里char的大小是1 Byte,這是不會(huì)變的,也就是sizeof(char)無論在哪都是1。但是:

輸出是多少?是4,一個(gè)int的大?。]錯(cuò),?字符常量的類型不是char而是int?。
來放松一下。
你平時(shí)怎么讀“char”?反正我是讀了好幾年的?”差“?,后來轉(zhuǎn)念一想,字符的英文是character[?k?r?kt?(r)],那不應(yīng)該是。。。。其實(shí)有三個(gè)發(fā)音,英文char(煤炭)、car(汽車)、care(關(guān)心)都可以。
float
浮點(diǎn)數(shù)比整形更貼近實(shí)際,也不至于出現(xiàn)除法去尾的情況。要注意的是,計(jì)算機(jī)的浮點(diǎn)數(shù)是分立的,有時(shí)候1.30會(huì)變成1.299999。比如matlab里查看eps(epsilon)可以得到浮點(diǎn)數(shù)的最小分度值。(win64下)

結(jié)構(gòu)體、數(shù)組、指針
三者的關(guān)系可以說是糾纏不清。
剛學(xué)C都遇到過,函數(shù)返回值可以是一個(gè)龐大的結(jié)構(gòu)體,卻不能是一個(gè)簡單的數(shù)組??墒牵瑪?shù)組類型可以是結(jié)構(gòu)體,結(jié)構(gòu)體的成員也可以包含數(shù)組,僅僅是組織方式的區(qū)別。
結(jié)構(gòu)體和數(shù)組
舉例說明一下,現(xiàn)有結(jié)構(gòu)體struct_a,有成員a、b、c三個(gè)。

注意最后一種寫法,有時(shí)候編譯器為了對(duì)齊,會(huì)填充一些地址,導(dǎo)致不連續(xù)。不要這樣訪問結(jié)構(gòu)體成員!
如果是數(shù)組呢?我們常用arr[n]這種方式來訪問數(shù)組成員,”[]“這個(gè)符號(hào)的用途是把a(bǔ)[b]變成*(a+b)。請(qǐng)結(jié)合例子理解一下。

數(shù)組和指針
把數(shù)組傳入函數(shù)時(shí),有兩種寫法

在C里,第一種會(huì)自動(dòng)轉(zhuǎn)化成第二種,所以訪問數(shù)組本質(zhì)還是指針。
教你個(gè)竅門:
int arr[10];
int* ptr=&arr[-1];
然后就可以從下標(biāo)1開始用數(shù)組ptr[]。

指針
毫無疑問,指針是個(gè)麻煩事。比如?char *(*(*(a[2])())()?是一個(gè)?包含2個(gè)指向返回 指向字符的指針的函數(shù)指針的數(shù)組?,幾乎很難看出它到底是數(shù)組還是指針。
希望這些要點(diǎn)能幫到你!
優(yōu)先與[]結(jié)合再與*結(jié)合
指針類型:把聲明中指針名稱去掉,就得到了指針的類型。
Int * ptr→int *
Int(* ptr)[3]→int(*)[3]
同時(shí)注意結(jié)合關(guān)系,比如下面這個(gè)的名稱就是ptr[3],而不是上面的ptr
int *ptr[3]->int *
所指向的類型:去掉指針名稱和一個(gè)*
int*ptr; : 指針?biāo)赶虻念愋褪?int
int ** ptr; : 指針?biāo)赶虻牡念愋褪?int*
int(*ptr)[3]; : 指針?biāo)赶虻牡念愋褪?int()[3]
指針賦值時(shí),左邊指針?biāo)赶虻念愋捅仨毦哂杏疫呏羔標(biāo)赶蝾愋偷娜肯薅ǚ?/strong>,比如

函數(shù)
C語言絕不是Python那樣自備電池的全能型語言,它是一門中級(jí)語言。
標(biāo)準(zhǔn)庫函數(shù)往往看起來簡陋而且有缺陷。
來了解一下吧。
函數(shù)調(diào)用順序

這三個(gè)函數(shù)的執(zhí)行順序是不確定的,C標(biāo)準(zhǔn)把選擇順序的權(quán)力交給編譯器以便針對(duì)各個(gè)平臺(tái)進(jìn)行優(yōu)化。
可以確定的一點(diǎn)是,乘法優(yōu)先級(jí)高于加法。
參數(shù)壓棧順序:從右至左嗎?
可能很多人都知道這個(gè)考點(diǎn),函數(shù)參數(shù)壓棧的順序是從右至左,右邊的表達(dá)式會(huì)先被運(yùn)行。
重點(diǎn)在后面的問號(hào)
C標(biāo)準(zhǔn)對(duì)于壓棧順序?并沒有明確規(guī)定?,也就是編譯器?可以修改成從左至右?的壓棧順序。
默認(rèn)的從右至左是為了支持可變參數(shù),用來計(jì)算棧的大小。
確保你明白它們的用法、原理和......缺陷
早期的?gets()?導(dǎo)致了蠕蟲病毒,因?yàn)樗粰z查緩沖區(qū)是否越界,其實(shí)scanf也有這個(gè)問題。標(biāo)準(zhǔn)輸入輸出有許多設(shè)計(jì)上的細(xì)節(jié)需要被了解,比如printf使用%來轉(zhuǎn)義%而不是\ , scanf里的?\n?并不代表等待一個(gè)換行符而是讀取并拋棄所有空格......
這些設(shè)計(jì)上的特點(diǎn)可能會(huì)在意想不到的地方產(chǎn)生出意想不到的效果。所以,為了程序的健壯性,多多了解它們吧。
預(yù)處理
預(yù)處理是個(gè)很好的想法,增強(qiáng)了程序的可移植性和裁剪性。
不僅僅是常見的#include、#define,它的功能可以非常強(qiáng)大,如果用得好的話。
#include能做些什么?
復(fù)制,原封不動(dòng)的復(fù)制。
甚至可以這樣:

真就是原封不動(dòng),但要注意預(yù)處理命令是要?在一行的開頭,獨(dú)占一行?。
如果是?#include<>?則會(huì)首先在標(biāo)準(zhǔn)位置(C語言安裝位置)搜尋,?#include ""?則現(xiàn)在同一文件夾下搜索,找不到再去標(biāo)準(zhǔn)位置搜索。
#define不好嗎?
在C++里,用#define來定義常量是不被推薦的,因?yàn)?define也僅僅是預(yù)處理替換,沒有類型檢查。
推薦使用const修飾的變量。仁者見仁,兩種方法各有特點(diǎn)。

結(jié)語
“?看似簡單?”這個(gè)描述對(duì)于C語言是再恰當(dāng)不過的。最早的K&R標(biāo)準(zhǔn)只有40頁,ANSI C手冊(cè)則超過了兩百頁,盡管這樣,C語言特性給了編程者極大的自由,衍生出來許多意想不到的用法和Bug。。。
本文整理的內(nèi)容不過是冰山一角,還有更多的進(jìn)階內(nèi)容等待探索。
周雖舊邦,其命唯新。多次的標(biāo)準(zhǔn)更新已經(jīng)讓C語言不再是教科書里的簡陋模樣。了解新特征,有利于C語言的實(shí)際應(yīng)用。
希望大家都能夠熟練掌握這門?傳統(tǒng)藝能?!
最后還有句話是這么說的:栽一棵樹最好的時(shí)間是十年前,其次是現(xiàn)在。對(duì)于學(xué)習(xí)編程或者在工作想升職的程序員,如果你想更好的提升你的編程能力幫助你提升水平!筆者這里或許可以幫到你~
微信公眾號(hào):C語言編程學(xué)習(xí)基地
分享(源碼、項(xiàng)目實(shí)戰(zhàn)視頻、項(xiàng)目筆記,基礎(chǔ)入門教程)
歡迎轉(zhuǎn)行和學(xué)習(xí)編程的伙伴,利用更多的資料學(xué)習(xí)成長比自己琢磨更快哦!
