C語言筆記
序號
類型與描述
1
基本類型:
它們是算術類型,包括:整數類型、浮點類型
2
枚舉類型:
也是算術類型,被用來定義只能使用某些整型值的變量,使用時需要程序員先使用eumn關鍵字來聲明定義
3
Void類型:
用於函數,指明函數的返回值或參數。作用於變量會發生編譯錯誤
4
派生類型:
包括:指針類型、數組類型、結構類型、聯合體類型、函數類型
補充:1.函數類型是指函數返回值的類型,數組類型與結構類型統稱為聚會類型。
2.除了基本類型,其他的類型都是程序員使用相關關鍵詞或通過基本數據類型構造的自定義類型。
類型參考
整數類型
Char、int、short、long
浮點類型
Float、double、long double
枚舉類型
enum typeName{ valueName1, valueName2, valueName3, ...... };
Void類型
指針類型
Type * name;
數組類型
Type name[count];
結構類型
Struct name{merber1;merber2;merber3};
聯合體類型
Union name{merber1;merber;merber};
補充:假如要說明整數類型的符號,就在類型前面添加signed/unsigned
參考連接:
枚舉類型:http://c.biancheng.net/cpp/html/99.html
整型、浮點類型:http://www.runoob.com/cprogramming/c-data-types.html
聯合體類型:http://www.runoob.com/cprogramming/c-unions.html
結構體類型:http://www.runoob.com/cprogramming/c-structures.html
例子:
#include <stdio.h>
#include <string.h>
enum week{ Mon = 'm', Tues, Wed, Thurs, Fri, Sat, Sun }; //枚舉,值只能是整數類型
struct stu{ char name[8];
char sex;
int age;
}; //結構體
union school{
char name[20];
int peopleNumber;
int phone;
week day;
}; //聯合體,成員同時只能存在一個
int main(void){
int I = 1;
int * P; //指針
P = &I;
union school mySchool;
struct stu student;
char name[10] = "john";
int number[3] = { 1, 2, 3 };
strcpy_s(student.name, "john");
student.sex = 'm';
student.age = 20;
printf("name:%s, sex : %c, age : %d\n", student.name, student.sex, student.age);
printf("i = %d, p = %d, name = %s\n", I, *P, name);
strcpy_s(mySchool.name, "*****\n");
printf("mySchool name : %s\n", mySchool.name);
mySchool.peopleNumber = 2000;
printf("mySchool peopleNumber : %d\n", mySchool.peopleNumber);
mySchool.day = Mon;
printf_s("day : %c", mySchool.day);
getchar();
return 0;
}
變量:
聲明:dataType name ;
特別的是數組變量的聲明:dataType name[count] ;
常量:
聲明:#define name value //屬於預處理階段
const dataType name= value; //屬於編譯階段
運算符
算術運算
+、-、*、/、%、++、--
關系運算
== 、 != 、 > 、 < 、>= 、 <=
邏輯運算
&& 、 || 、 !
位運算
& 、 | 、^ 、<< 、 >> 、~
賦值運算
= 、+= 、-= 、*= 、/= 、%= 、 <<= 、>>= 、&= 、^= 、|=
雜項運算
sizeof() 、* 、& 、?:
判斷:
if( condition ){
Statement block;
}
else if( condition ){
Statement block;
}
else{
Statement block;
}
switch( condition ){
case 常量表達式:Statement block;
case 常量表達式:Statement block;
default : Statement block;
}
循環:
1.
while( condition ){
Statement block;
}
do{
Statement block;
}while( conditon );
2.
for( int I ; I >10 ; I ++ ){
Statement block;
}
循環控制:
break : 跳出當前循環,不再執行
continue : 跳出當前循環,並繼續
指令
描述
#include
包含一個源代碼文件
#define
定義宏
#undef
取消已定義的宏
#ifdef
判斷宏的定義情況,如果宏已經定義,返回真
#ifndef
判斷宏的定義情況,如果宏沒有定義,返回真
#if
如果給定的條件為真,則編譯下面的代碼
#else
#elif
#endif
#error
當遇到標准錯誤時,輸出錯誤消息
#pragma
使用標准化方法,向編譯器發布特殊的命令到編譯器中
補充:#pragma comment(linker, "/entry:main2") 可以用來改變入口函數
定義:
return_type function_name( parameter list ){
Statement block;
}
使用:
function_name(parameter list);
補充:對應一個可執行文件的源代碼,始終要存在一個主函數,才能讓編譯器正確編譯。
默認main 函數,但不一定是main,可以通過設置編譯器的參數來進行更改。
標准庫:
應該知道的:標准庫並不是c語言本身的構成部分,只是將一些非常非常常用的功能進行了標准化,讓程序員能夠更好的進行程序的開發和移植。並且提供一些系統調用的封裝,這樣程序員就可以不用去考慮程序與系統與硬件的關系,只要調用相關的庫函數就能進行與硬件的交互了。
標准庫中包含了大量的宏定義和使用typedef 關鍵字定義的新的類型,以及函數的聲明與實現。
標准庫名稱
說明
常用庫函數
<stdio.h>
標准輸入輸出:用以提供基於常用硬件的I/O操作(文件讀寫、屏幕輸入輸出)。
fclose、fopen、fseek、fwrite、printf、
scanf
<stdlib.h>
定義了四個變量類型、一些宏和各種通用工具函數
NULL、atof(字符串轉換為一個浮點數)、atoi、free、malloc、abort、
exit、getenv、
<string.h>
與操作字符串有關
memcmp(將str1與str2的前n個字節進行比較)、strcat、strcpy、strlen、
<stdarg.h>
與函數的參數有關,獲取參數個數可變時獲取函數中的參數
<math.h>
定義各種數學函數
exp(x):返回e的x次冪的值
pow(x,y):返回x的y次冪
fabs(x) :返回x的絕對值
<time.h>
<stddef.h>
定義各種變量類型與宏
NULL、
<assert.h>
提供一種診斷宏型函數
唯一的庫函數:assert
<ctype.h>
提供函數來測試和映射字符
islower、isupper、isxdigit、tolower、toupper …
<errno.h>
定義了一些與錯誤有關的宏
errno
<float.h>
包含一組與浮點值有關的平台常量
FLT_MAX:平台能表達的的最大浮點數,FLT_MIN:平台能表達的的最小浮點數
<limits.h>
定義在其中的宏限制了各種變量類型的值
CHAR_BIT:定義一個字節的比特數
<locale.h>
定義特定地域的設置,比如日期格式和貨幣符號
LC_CTYPE:影響所以的字符函數
setlocale : 設置或讀取地獄化信息
<setjmp.h>
程序中的長跳轉,不局限在函數內部。
setjmp/longjmp的最大用處是錯誤恢復,類似try ...catch...
<signal.h>
提供與信號有關的宏和函數
signal : 設置一個函數來處理信號
raise() : 產生信號
<raise.h>
用於產生信號
共享庫與靜態庫、靜態調用與動態調用、靜態鏈接與動態鏈接
靜態庫與動態庫的編譯方式不同。屬於編譯部分。
靜態鏈接與動態鏈接是屬於鏈接部分的,是告訴鏈接器使用的是什麼類型的庫,靜態庫就是靜態鏈接,動態庫就是動態鏈接。
#pragma comment( lib, "..\\debug\\libTest.lib" ) //告訴連接器使用靜態庫進行靜態鏈接
靜態調用和動態調用是屬於動態庫的一部分,是關於如何將動態庫加載到內存的。
靜態調用時,操作系統的加載程序會先為進程創建虛擬地址空間,接著把可執行模塊映射到進程的地址空間中,之後加載程序會檢查可執行模塊的導入段,試圖對所需的DLL進行定位並將它們映射到進程的地址空間內。(windows 核心編程第五版 19章中的運行可執行模塊)。#pragma comment(lib,"dllTest.lib") 這個宏告訴編譯器使用靜態調用。
動態調用時,由程序員通過loadLibrary這個API將DLL映射到進程的地址空間中,再由程序員通過GetProcAddress這個API獲取想要的導出函數的地址(在使用返回的函數指針來調用函數之前,需要轉型為與函數的簽名相匹配的正確類型)來調用所需函數。
外部函數:
由其他源文件或者庫提供的函數。程序員使用時,需要它人提供的頭文件或者使用extern或者extern "c"__declspec(dllimport)來進行函數聲明,表明該函數來至外部文件。
編譯:
鏈接:
軟件的運行過程:
操作系統與軟件的關系:
系統調用:
低層c與應用層的c的關系:
系統調用與標准庫的關系:
系統調用就是操作系統提供的一組API,通過這些API函數就可以通過系統與硬件進行交流了,這些API函數需要到操作系統的開發者處獲得。而語言的標准庫是語言的標准化組織為了讓程序員在各種平台或者系統上進行少量的程序修改就可以編譯運行所制定的一系列的函數。也就是說標准庫是脫離了操作系統或者平台的,屬於是抽象層,具有平台無關性。
但是每個操作系統能夠提供的API(系統調用)並不是一致的,也就是函數名呀、函數的參數呀、返回值類型呀會有所不同。
但為了讓程序能夠運行在某種特定的系統上,就需要去實現該系統上的標准庫中的庫函數(標准函數)。而這部分會由編譯器來提供,就是說編譯器的開發者會提供該編譯器能編譯的語言的標准庫部分(標准庫的實現)。他們通過閱讀操作系統開發者提供的API文檔來編寫標准庫的實現代碼。
到此處就可以了解到程序員只需調用對應的庫函數就能擺脫操作系統,來編寫自己的可移植的程序代碼了(標准庫消除平台的差異性,給程序員提供一種統一體驗的編碼方式,讓程序更方便的移植)。
庫代碼解析:
補充:在vs2013中開發沒有使用純c的,只有使用c++的源代碼了。但是c++是c的升級,向下兼容c,所有差別不大,只是在語法上有些出入。
windows中的vc:
time.h
#include <time.inl> //采用了c++的內聯語法
time.inl //內聯
static __inline time_t __CRTDECL time(time_t * _Time) //32位
{
return _time32(_Time);
}
time.c //庫函數的實現部分
#include <#include <time.h>
#include <windows.h>> //操作系統提供的系統調用的頭文件
__time32_t __cdecl _time32 (
__time32_t *timeptr
)
{
__time64_t tim;
FT nt_time;
GetSystemTimeAsFileTime( &(nt_time.ft_struct) ); //系統調用
tim = (__time64_t)((nt_time.ft_scalar - EPOCH_BIAS) / 10000000i64);
if (tim > (__time64_t)(_MAX__TIME32_T))
tim = (__time64_t)(-1);
if (timeptr)
*timeptr = (__time32_t)(tim); /* store time if requested */
return (__time32_t)(tim);
}
可以看出庫函數 time 的實現其實是調用了windows.h中的GetSystemTimeAsFileTime系統調用。
補充:由於庫的實現不是應用程序員所需要關心的事情,程序員只關心提供的 .h 文件中的函數聲明(也就是標准庫提供的接口的方式),所以編譯器開發者可以使用高超的技術來進行各種優化與數據操作(也就是我們看不懂的方式來進行庫的實現)。所以不必在意庫的實現。
glibc
time.h
# include <bits/time.h>
extern time_t time (time_t *__timer) __THROW; //使用liunx提供的time
time.c
time_t
time (timer)
time_t *timer;
{
__set_errno (ENOSYS);
if (timer != NULL)
*timer = (time_t) -1;
return (time_t) -1;
}
<sys/time.h>
extern int gettimeofday (struct timeval *__restrict __tv,
__timezone_ptr_t __tz) __THROW __nonnull ((1));
由於能力有限不能找到具體的函數實現,但是可以通過網絡了解到liunx上的c標准庫的實現使用了linux提供的time系統調用,采用的是函數映射方式實現的。
liunx上獲取時間的系統調用為 #include <sys/time.h>中的gettimeofday。
再次補充:一般地,操作系統為了考慮實現的難度和管理的方便,它只提供一少部分的系統調用,這些系統調用一般都是由C和匯編混合編寫實現的,其接口用C來定義,而具體的實現則是匯編,這樣的好處就是執行效率高,而且,極大的方便了上層調用。
寫在最後:一種編程語言只是抽象層的東西,要讓計算機能夠識別該種語言所編寫的程序,就需要編譯器或者解釋器,將程序員編寫的屬於字符類型的源代碼文件轉換成計算機(cpu)所能懂的指令。由於計算機分成了很多的平台與架構(操作系統的不同,cpu的差異),所以就要編寫各種的編譯器或解釋器來進行該平台的語言實現,實現語言中的語法和語義,並且提供語言標准組織所制定的標准部分(要提供什麼標准庫和其他編程方面的東東)(但不一定非要這樣,也可以增刪部分語法和庫)。所以編譯器或解釋器是最終得boss,它擁有語言實現的決定權。說白了程序員就是一個給編譯器或解釋器打工的苦勞力,最終你的工作成果還的它來說了算。管他嗎的語言標准呀,說他媽的可移植的語言,沒有實現,都是空話。還有就是羅馬不是一天建成的,現在的一切程序都是由最開始的cpu指令構成的(人不就是史前的那個單細胞慢慢演變的嗎?)。