彙編語言:爲特殊的中央處理單元設計的一系列內部指令,使用助記符表示,不同的CPU系列使用不同的彙編語言
C語言具有彙編語言才能具有的微調控制能力,可以根據具體情況微調程序獲得最大的運行速度或者有效的使用內存。
編譯器和解釋器的區別:https://blog.csdn.net/touzani/article/details/1625760
CPU的工作原理:從內存中獲取並執行一條指令,然後再從內存中獲取並且執行下一條指令。cpu的工作區是由若干個寄存器組成的,每個寄存器可以儲存一個數字。一個寄存器儲存下一條指令的內存地址,CPU使用該地址獲取和更新下一條指令,獲取指令後,CPU在另一個寄存器中儲存該指令,並更新第一個寄存器儲存下一條指令的地址。
編譯器: 編譯器就是把高級語言程序翻譯成計算機能理解的機器語言指令集的程序,使用合適的編譯器,可以把一種高級語言程序轉換成供各種不同類型的CPU使用的機器語言程序。
GUN編輯器集合也被稱之爲GCC,它可以根據不同的版本選擇運行時選項來調用不用C標準:
gcc -std=c99 inform.c
gcc -std=clx inform.c
gcc -std=c11 inform.c
簡單C程序示例:
# include <stdio.h> // 預處理器指令
int main(void) // main()總是第一個被調用的函數, void表明不帶任何參數
{
int num;
num = 1;
printf("my favorite number is %d beacaue ...", num);
return 0;
}
上面的#include <stdio.h>
是包含另一個文件,告訴編譯器應該把stdio.h中的內容包含在當前的程序中,這個文件是編譯器軟件包的標準,提供鍵盤輸入和屏幕輸出的支持
數據類型和c
- int類型:
int a; // 聲明變量,聲明爲變量創建內存空間
int b = 1; // 初始化變量
對應的打印轉換說明是%d
0x
或者0X
標識16進制,轉換說明爲%x
0
開頭表示8進制,準換說明爲%o
-
其他整數類型:
short int, long int, long long int, unassigned int
long long佔64位,long佔32位,short佔16位,int佔16或32位 -
char類型:
用於儲存字符,字母或者標點符號,c語言將1字節定義爲char類型佔用的位(bit)數, 對應的轉換說明爲%c -
_Bool 類型:
布爾值,c語言用1表示True,用0表示False,雖然實際上也是一種整數類型,但是原則上僅佔用1bit的儲存空間 -
float 類型:
浮點類型有float,double,long double
十進制計數法的浮點數的轉換說明是%f
指數計數法的浮點數的轉換說明是%e
int:4 bytes
char:1 bytes
long:8 bytes
字符串介紹
char name[40]
c中沒有專門的字符串變量類型,所以儲存在char類型的數組當中。數組由連續的儲存單元組成,每個單元儲存一個字符
這裏數組的末位是\0空字符,所以這裏40個儲存單元只能儲存39個字符。
這裏詳細解讀這一行:方括號代表這是一個數組,40表明數組中的元素數量,char表明每個單元都儲存該類型的值
strlen()
能以字節爲單位給出對象的大小
#define ABC 0.1
定義常亮,在編譯的時候,所有的a都會被替換成0.1,常量需大寫
const int a=1
使得a成爲一個只讀值
轉換說明:
%a 浮點數,十六進制數
%c 單個字符
%d 有符號的十進制整數
%f 浮點數,十進制計數法
%p 指針
%s 字符串
循環,分支,跳轉
條件運算符:?:
x = (y < 0) ? -y : y;
意思就是如果y小於0,那麼x=-y,反之,x=y
switch:
switch(ch)
{
case 'a':
printf("aaaa");
break;
case 'b':
printf("bbbb");
break;
default:
printf("default");
}
函數
函數類型:
void show_n_char(char ch, int num)
{
int count;
for (count = 1; count <= num; count++){
putchar(ch);
}
}
這是一個沒有返回值的函數,所以函數的類型爲void,也可以在函數的最後加上return;
也同樣是不返回任何值
函數類型爲int的函數:
int what_if(int n)
{
double z = 100.0 / (double) n;
return z;
}
那麼如果傳入的實參爲64,最後z計算出來的結果是1.5625,那麼函數的返回值和函數的類型不一致,那麼實際上是相當於把z的值賦值給int類型的變量,所以最後得到的值爲1
重點:聲明函數時必須聲明函數的類型,程序在第一次使用函數之前必須知道函數的類型,所以可以把完整的函數都放在調用的前面,但是更好的方法是提前聲明函數,把函數的信息告訴編譯器
#inclue <stdio.h>
int imin(int, int);
int main(void)
{
...
}
編譯多源代碼文件:
linux系統中安裝了GCC,然後有file1.c和file2.c兩個文件,使用gcc file1.c file2.c
,就可以生成a.out的可執行文件,並且同時生成file1.o和file2.o的目標文件。
那麼如果將main()放在第一個文件,將函數定義都放在第二個文件,那麼還需要一個頭文件,包含了該程序所有源文件中使用的自定義符號常量和函數原型。
- 第一個文件:主文件 usehotel.c
#include <stdio.h>
#include "hotel.h" //定義符號常量,聲明函數
int main(void)
{
...
nights = getnights();
}
- 第二個文件:函數支持文件 hotel.c
int getnights(void)
{
...
return nights;
}
- 第三個文件:函數文件的頭文件 hotel.h
#define a 100
...
int getnights(void);
...
數組與指針 ※※※※※※※※※※
指針用於儲存變量的地址
指針是一個值爲內存地址的變量 !!!!
1. 一元運算符:&
首先看一元運算符&
:&是變量的儲存地址,也就是變量在內存中的位置,所以如果count是一個變量名,那麼&count就是該變量的地址。
打比方我們需要對調x,y的值,然後傳入函數裏面處理,但是函數只處理了裏面的參數比如說u,v的值,當然我們可以用return語句把值傳回main(),但是return語句只能把被調用函數中的一個值傳回主調函數,但是現在需要傳回兩個值,那麼就需要使用指針
pt = &count // 把count的地址賦給pt
也就是: pt指向count
pt是變量,而&count是常量,所以pt是可以修改的值
2. 間接運算符:*
ptr = &count
val = *ptr
======>>>>>
val = count
上面第一行ptr指向count,也就是ptr是count的地址,所以ptr就是一個指針
第二行是找出ptr指向的值,也就是找到ptr這個地址裏面存放的值,也就是count
所以兩者結合起來等價於val = count
總結
- &給出變量的地址
- *給出儲存在指針指向地址上的值
聲明指針
聲明指針的時候必須指定指針所指向變量的類型,因爲不同類型佔用不同的儲存空間,所以:
int * pi;
pi是指向int類型變量的指針
然後我們使用指針來解決上面的交換值的問題:
void interchange(int * u, int * v);
int main(void)
{
int x = 5, y = 10;
interchange(&x, &y); // 這裏把地址發送給函數
}
void interchange(int * u, int * v)
{
int temp;
temp = *u;
*u = *v;
*v = temp;
}
比如 int a[10] = {0,1,2, …}
那麼 int *p = a 是成立的,因爲a與&a是一樣的,a是數組的名字也是數組的地址
那麼 *(p+1) 從起始位置跳四個字節,等同於a[1]
另一種情況: char a[10] = {0,1,2,3…}
還是 char *p = a
那麼 *(p+1) 是從起始位置跳一個字節
再來一個例子: int (*a)[3] 是一個指針,那麼這個指針從起始位置每次跳12個字節,也就是4x3,所以我們可以看出指針每次跳的字節是根據指針的類型來判斷的
所以,當date是一個數組的時候
date + 2 == &date[2] 成立,因爲都是取第三個元素的地址
*(date + 2) == date[2] 同樣成立,因爲都是取第三個元素地址裏面的元素值
如果有一個需要處理數組的函數:
total = sum(a_array); // 函數的調用
int sum(int * ar); // 對應的函數原型
數組名是地址,所以需要把它賦值給一個指針 形式參數
對於函數的形參,只有在函數的原型或者函數定義頭裏面纔可以使用int ar[]來代替int * ar
int sum (int ar[], int n);
這兩個形式都標識ar是一個指向int的指針
由於處理數組的函數必須傳入指針,那麼就可能在函數裏面破壞原始數據。如果確定函數裏面不準備修改數組中的原始數據,那麼在函數原型和函數定義的時候可以使用關鍵字const:int sum(const int ar[], int n);
多維數組的指針:
int zippo[4][2]
zippo[0]可能等於[1,2],是一個兩個整數的數組,根據數組名的定義,zippo[0]又是自己這個數組的第一個元素的地址,也就是&zippo[0][0]
所以zippo = zippo[0] = &zippo[0][0]
小型總結:
指針儲存的永遠都是地址
數組的名字就是數組首元素的地址!!!
所以假設有一個數組alist,那麼 alist == &alist[0]
函數在編譯後就是一串地址,然後運行彙編碼的時候,遇到函數也就是直接運行函數的那個地址
在c中,指針加1後的地址是下一個元素的地址,不是下一個字節。這也是爲什麼要聲明指針所指向對象類型的原因之一:
char類型佔用1個字節
short類型佔用2個字節
int類型佔用4個字節
double類型佔用8個字節
編寫一個函數的時候,要選擇是傳遞int類型的值還是傳遞指向這個值的指針,通常是直接傳遞數值的,但是如果程序需要改變該數字的時候,就應該傳遞指針。但是對於數組而言,必須傳遞指針。
對指針或者地址加1,其值會增加對應類型大小的數值
結構和其他數據類型
結構聲明:
struct book{
char title[MAXTITL];
char author[MAXAUTL];
float value;
}
struct book library; // 創建結構變量,可以把book就當做一個類型
訪問結構成員:library.value
使用結構成員運算符:點(.)
結構的初始化器,也是使用點運算符:
struct book surprise = { .value = 10 }
聲明結構數組
同樣把book當做數組每個元素的類型: struct book library[MAXBKS];
聲明和初始化結構指針
struct guy * him;
現在指針him可以指向任何guy類型結構變量
him = &guy1;
結構名不是結構的地址, 所以需要在結構名前面加上&運算符
用指針訪問結構成員:
方法一:
如果him == &barney, 那麼him -> income,就是barney.income
方法二:
如果him == &fellow[0]
那麼 *him == fellow[0]
那麼 fellow[0].income == (*him).income
聯合與結構: union與struct
聯合使用於結構相同的語法,但是聯合的成員共享一個共同的存儲空間,聯合同一時間只儲存一個單獨的數據項,也就是說,結構可以同時儲存一個int一個double一個char,但是聯合在某時刻只能保存一個int或者一個double。。。
函數指針※※※※※※※※※
可以聲明一個指向函數的指針,通常函數指針常用於另一個函數的參數,告訴該函數要使用哪一個函數。
函數也有地址,因爲函數的機器語言實現由載入內存的代碼組成,所以,指向函數的指針中儲存着函數代碼的起始處地址。
聲明函數指針: 必須聲明指針指向的函數類型,也就是要指明函數的返回類型和形參類型
void toUpper(char *); // 這是函數原型,把字符串轉換成大寫
void (*pf) (char*); // 這裏pf就是一個指向函數的指針
pf = ToUpper; // 這裏給函數指針賦值函數的地址,函數名就是函數的地址
*pf被括號括起來,那麼就表明這是一個指向函數的指針
聲明函數指針的最簡單的方法是:先寫出函數原型,然後將函數名替換成(*pf)
即可
這裏的指針pf還可以指向其他帶char *類型參數,返回類型是void的函數
如果不加括號:
void *pf (char *); // pf是一個返回字符指針的函數
聲明瞭函數指針後,就可以把函數的地址賦值給它,函數名可以用於表示函數的地址
使用函數指針調用函數的兩種方法: 接上面
pf = ToUpper; // 將函數的地址賦值給指針
char mis[] = "Nina hello how are you";
(*pf)(mis);
pf(mis);
上面兩種調用函數的方法:
方法一:pf指向ToUpper函數,所以*pf當然就是從pf地址裏面取值,也就是該函數了
方法二:由於第一行函數名就是指針,所以pf與ToUpper是等價的
最常用方法:函數指針用於函數的參數
void show(void (* fp) (char *), char * str);
該函數聲明瞭兩個形參,fp和str,fp是一個函數指針,str是一個數據指針
所以,fp指向一個參數類型爲char *,返回值爲void類型的函數;str指向一個char類型的值。
調用函數:
show(ToUpper, mis);
show(pf, mis);
void show(void (* fp) (char *), char * str)
{
(*fp)(str); // 用fp指向的函數轉換str
puts(str);
}