C語言概述

weixin_34050427發表於2016-07-07
1772607-3f39ea7d27bfadf6.png

知識掃盲

CPU從內容讀取指令,執行相應的操作.

計算機只能識別1和0指令

第一個C語言程式

連線:就是把我們自己寫的.c編譯後的.o檔案和系統函式合併在一起,生成一個可執行檔案

編譯:

分步編譯:
cc -c xx.c
cc xx.o -o name

快速編譯:
cc xxx.c -o name  //快速編譯,不會報錯

執行:

./name

檔名:

.c 原始檔
.o 編譯後的檔案  (windows  .obj)
.out 可執行檔案  (windows  .exe)

基本語法

關鍵字

就是c語言提供的有特殊含義的符號,也叫做保留字

識別符號

就是我們自己定義的一些符號和名稱,用來區分某些東邪,比如函式名,變數名

識別符號命名規則

  1. 只有26個字母,數字,下劃線組成
  2. 嚴格區分大小寫
  3. 不能以數字開頭
  4. 不可以使用關鍵字
  5. 有意義

註釋

註釋用來表明一段程式碼有什麼作用,不會被編譯,還可以用來排錯

可以寫任何文字,一般用豆綠色

單行註釋: // 可以巢狀

多行註釋: /* */ 不可以巢狀

資料

靜態資料

永久性的資料,一般存在硬碟

硬碟的訪問速度很慢

動態資料

在程式執行過程中,動態產生的臨時資料,一般儲存在記憶體中.

斷電就會消失.

記憶體訪問非常快

資料型別

  1. 基本資料型別
  2. 指標型別
  3. 構造型別
  4. 空型別

常量

表示一些固定的資料

整形常量

10   -33

浮點常量

0.33f  0.055

字元常量

'a'  'dd'  '+' //字元那個用單引號

字串常量

"fasdfa"  "343ed"  //字串用雙引號

0.0是小數

變數

一些經常改變的不確定資料,就用變數來表示,比如遊戲積分

定義變數

  1. 使用之前必須定義
  2. 變數型別 變數名
  3. 不同的變數型別佔用不同大小的儲存空間.
  4. 第一次賦值是初始化

int 4個位元組
char 1個位元組

輸出變數:

printf('%d',name);

佔位符:

%d/$i   整形
%.4f 4為小數
%c   字元
%p  地址

變數在函式中的作用域

在函式裡面:

從開始定義的那個地方開始,一直到函式結束.

函式在呼叫的是偶分配記憶體空間,函式結束的時候釋放記憶體空間

變數在程式碼塊中的作用域

程式碼塊:

int main()
{
    {
        這就是一個程式碼塊,在這裡面定義的變數,只能在這裡面用
    }
    
    //但是在這裡定義的變數可以被程式碼塊中使用
    int a = 10;
    {
        printf('d%',a);
    }
}

在程式碼塊中使用變數,優先在自己的快裡找,然後依次往上找

在函式外面定義的是全域性變數,函式內的為區域性變數

程式碼塊用來提高效能的,可以在適當的時候釋放變數,不會一直佔用記憶體
及時回收不再使用的變數

變數的記憶體分析

變數在記憶體中佔據多個位元組:

char 1
int  4
float 4
double 8
  1. 記憶體的定址從最大的開始,也就是從底部入棧

  2. 變數越先定義,記憶體的地址就越大.

  3. 與之對應的變數的地址是變數所佔位元組分配中的最小的一位

  4. 可以用&name取得name的地址. 輸出用 &p

  5. 只要定義變數,就會分配地址,不管有沒有值

scanf函式

用來接收使用者的輸入,是個阻斷函式,必須完成後,下面的程式才會執行

只接收變數的地址

scanf("%d",&name);

多個引數以分隔符(任意)隔開,在輸入的時候也必須以這個符號隔開
如果以空格隔開,在輸入的時候用tab或者回車也可以

裡面不能寫\n,不然不會停止

運算子

+加法運算 還可以做正號
%去餘 兩邊都是整數 正負數與左邊的值有關

型別轉換

自動轉換

在運算或者賦值時,會自動將大型別轉換為小型別

int a = 19.8;   //19

也可能將小型別提升為大型別:

double c = 10.9 +9;  //19.9000000

運算結果和參與運算的資料型別有關,兩個整數運算結果必定是整數.

1/3 = 0;

強制轉換

int a = (int)12.3;

符合運算

a += 1+2+3;  //等於 a = a+1+2+3;

b = ++a  先自增運算在賦值

b = a++  先賦值在自增運算

b = (++a) + (a++)  //b = 10 + 12

b = (a++) + (++a)  //b = 11 + 11

只有變數才允許自增自減

sizeop

獲取一個變數或常量佔據了多少位元組

關係運算子

< <= > >= 的優先順序高於 == !=

邏輯運算子

&& 邏輯與
|| 邏輯或
!邏輯非

三目運算子

int a = 10>5 ? 1:0;

流程控制

條件語句

如果要在if後面定義新的變數,必須用{}括起來

if()
int a = 10;   //報錯
     
if()
printf("%d",a);  //對的

在switch語句中,case中也不能定義變數,不然又作用域不明確了,非要定義變數的話,必須用大括號括起來

switch(c){
    case '+':
        int sum = a + b;   //報錯
        break;
}

但是可以加個大括號解決
switch(c){
    case '+':
        {
            int sum = a + b;   //報錯
            break;
        }
}

迴圈

white迴圈

continue; 終止本次迴圈,繼續下一次迴圈
break; 結束整個迴圈

如果沒有大括號,就會執行挨著迴圈的那一條語句,注意變數作用域

do while 不管條件怎麼樣,先執行一次,再判斷條件是否成立

注意事項

for(int i=0;i<5;i++){
    int i = 10;
    printf("%d",i);   //會輸出5個 10 ,因為每一次迴圈結束,{}中的i就被釋放了         
}
這是對的,因為括號裡的作用域要比{}裡面的大,可以看成for迴圈也是一個區域,而{}是form迴圈區域裡的一起個小塊

函式

格式:

返回值型別 函式名(引數){
    函式體
}

形參: 函式括號裡的引數,可有可無

實參: 呼叫函式時傳遞的引數

函式體內不能定義和形參一樣的變數

return 退出函式,返回一個具體的值給函式呼叫者 ,返回的型別必須和定義函式時的型別一樣

也可以沒有返回值,這樣定義函式:

void test(){
    return;
}  這個函式也可以用return退出函式;

不寫返回型別,預設返回int型別
就算寫了返回型別,也可以不返回值

注意

函式不能重名

函式不能巢狀定義

函式要定義在main函式前面,因為程式是從上往下執行的

可以這樣解決,把函式放在main的後面,但是要在main的前面寫一個函式宣告:

void test();

int main(){};

voed test(){}

函式可以重複宣告

還可以在呼叫前宣告:

init main(){
    void test();
    test();
}

編譯的時候只檢查語法
連結的時候會檢查函式存不存在

include

系統自帶的用<>
我們自己寫的用""

多人開發

各自編譯出來各自的之後,然後合併

cc a.o b.o c.o

還可以直接編譯,但是不會提示報錯資訊

cc a.c b.c c.c

使用別人的函式,還在前面寫出宣告,我們可以引入.h檔案

#include "func.h";

一般一個.c檔案都會對應一個.h檔案

main 和 printf

main函式中,return 0,正常退出;return -1,異常退出;

printf也有返回值,是輸出佔用字元的數量

中文佔三個字元

進位制

就是計數的方式

要了解四種進位制: 10 2 8 16

預設就是10進位制

二進位制:

int num = 0b1100;  //前面帶個0b就是二進位制

八進位制:

int num = 014;  //前面帶個0就是八進位制

十六進位制:

int num = 0xc;  //前面帶個0x就是十六進位制

%d十進位制輸出, %o八進位制輸出,%x十六進位制輸出,%p輸出地址,$f輸出小數

變數在記憶體中的儲存細節

任何數字在記憶體中存的都是二進位制

一個int型別佔四個位元組,32bit

0000 0000 0000 0000 0000 0000 0000 0000 (一個位元組八位,32位存4個位元組)

進位制轉換

二進位制轉十進位制

0000 0000 0000 0000 0000 0000 0000 1100 = 02的0次方 + 0的2的1次方 + 12的2次方 + 12的3次方

十進位制轉二進位制

67 = 64 + 2 + 1
= 2的6次方 + 2的一次方 + 2的0次方
= 100011

二進位制的取值範圍

0-2的n次方 -1

int 2的31次方-1 因為第一位是符號位

最高位是0是正數

最高位是1是負數

型別說明符

int 4位元組 %d (32位處理器2位元組)
long 8位元組 %ld (32位處理器4位元組) 4354353425423523453425L
long long 8位元組 %lld (32位處理器8位元組) 34524352345243523452LL
short 2位元組 %sd
sigend 有符號,正數,0,負數
unsigend 無符號 (Int會取值到32位)
同一個型別的修飾符不能同時使用,不同型別的可以同時使用

位運算

按位與: 相同為1 9&5

10101
01011
-----
00001

可以按位與一個1,返回最後一位的值,用來判斷奇偶性,1為奇數,0為偶數

按位或: 任何一個為1就為1 9|8

101010
100101
------
101111

按位異或: 不同為1 9^8

101010
100101
------
001111

按位取反: 0變1,1變0 ~9

左移: 把整數的各二進位制全部左移N位,高位丟棄,低位補0, 左移N位其實就是乘以2的N次方

有時候會把負數搞成正數

右移: 往右移動N位,低位丟失,高位補符號位,右移就是除以2的N次方

Mac高位補符號位,其他看編譯器

char型別

字元在記憶體用是以ASCII碼存的,佔一個位元組

A  65  記憶體中 0100 0001
B  66     記憶體中 0100 0010

列子:

char a = 'A' + 32;   // 結果是a
先進行運算  65 + 32 = 97   //就是a了

單引號只能用於一個單位元組的字元

陣列

構造型別: 由無數個基本型別組成的型別,構造型別

定義:

型別  名字[元素個數]
int arrs[5];
arrs[1] = 3;
arrs[2] = 4;

int arrs[5] = {1,2,3,4,5};

int arrs[5] = {[4]=9,[5]=9};

int arrs[] = {3,4,5};

int ages[bian]; //對

int ages[bian] = {3,3,4};//錯  如果在定義的時候初始化,必須寫常量

長度:

int count = sizeof(arrs)/sizeof(int);

陣列放到函式裡,可以省略引數:

void change(int array[]){}

如果在函式修改了陣列元素,原陣列也會改變,因為傳進來的就是一個地址.也就是一個指標,指標永遠是八個長度
所以無法獲取到陣列長度,在傳遞引數的時候,要把長度也傳過去
基本型別是值傳遞

二維陣列:

int arr[2][4]  = {
    {},
    {},
    ...
}

字串

定義:

char name[] = "adsfdsa";  //預設最後會有一個\0

char name[7] = "adfads";

0的作用:

如果沒有\0,字串就不知道在哪結束,就會把其他的變數也都輸出了,在記憶體中一直往下找,知道找到\0停止

長度

#include <string.h>
strlen("aaa");  //原理就是去說字串的位元組數,也就是指標地址,不包括\0

字串陣列:

name[3][10] = {"fdfd","dfa","adaa"}

指標變數

定義:

變數型別 *變數名
int *p = a;   //把a的地址存到了p裡面

指標變數只能儲存地址
指標能根據一個地址值,訪問對應的儲存空間,並進行值的修改
int表示P只能指向int型別的儲存空間

    賦值   *p = 10;
    取值    *p;

指標使用注意

  1. 指標變數只能存地址
  2. 指標變數不能為空,必須要有確定指向的值,指標變數未經過初始化,不要拿來訪問其他儲存空間
  3. 定義指標時候的*沒有任何意義,僅僅是一個象徵,和後面的*不一樣
  4. int *p; 代表 (int *)p,僅僅說明p是一個指標型別變數
  5. p = &a; 把某一個變數的地址給p , 後面再使用*p就是代表a的空間
  6. *p = 90; 代表的是給a的空間賦值, 而 *p是直接取出a空間的值
  7. char *cp; 字元指標

指向指標的指標

int a = 10;
int *p = &a;   //等於  int *p;  p = &a;
int **pp = &p;  //等於  int **pp; pp = &p;

此時, *p **p 本質上指向的都是變數a的儲存空間,列印他們的地址,都是一樣的
而每個指標變數,還有自己的地址,都是不一樣的

指標型別

%zd,  sizeof(*cp);

任何指標都佔用8個位元組的儲存空間

指標的的型別,決定了指標從地址開始取多少個位元組

指標與陣列

int ages[5] = {34,56,65,67,34};    
int *p;        
p = &ages[0];   //或者 p = &ages;
p++;   // 或者p+1;
printf("%d\n",*p);

把陣列的一個元素的指標給p,
p儲存的是指標型別,是一個地址,那麼p+1,就是一個地址加上整形1,由於整形1佔4個位元組,那麼p所指向的
地址也加四個位元組,又因為整形佔四個位元組,所以p+1相當於指標p指向陣列的元素地址向下移動了一個位置,
也就變成下一個元素了

指標變數的+1究竟加多少,取決與指標的型別:

int *  4
char * 1
double * 8

把陣列給一個指標變數,就是把陣列的第一個元素的地址給了指標變數

指標與字串

char name[] = "it";
char *name2 = "it";   //把一個字串給一個指標變數,參照陣列,其實就是把字串的第一個字元的地址給了指標變數.
printf("%c\n",*name2);  //i

printf("%s\n",name2);  //it

在OC中,用的比較多的就是指標操作字串

陣列定義的字串是在棧中,它的值是隨便可以更改的 (字串變數)//使用場合,字串的內容需要經常修改

而指標指向的字串,是放在常量區,值不可以更改 (字串常量) //經常用到的不需要修改的字串

*name2 = "it";
*name3 = "it"; 
他們的地址是一樣的

也就是說,放在常量區的會有一個快取,我們下次再用的時候就不會再開闢一個儲存空間了

指標陣列: char *name[5] = {"aaaa","baaa","caaa","daaa","eaa"}

返回指標的函式

char *test()
{
    return "json";
}

指向函式的指標

//void代表函式的返回值型別
//固定寫法 (*p)代表 這是一個函式指標
//()  指標指向的函式有沒有形參
 void (*p)();   

p = test;  //把test函式的指標給P

(*p)();  //這樣就可以直接代用函式  (*p)取出函式  ()呼叫函式

p();  //這樣也可以呼叫函式   p已經代表函式的指標了

test();  //這樣原始呼叫

其他資料型別

變數型別

區域性變數:

定義在函式或者程式碼塊中的變數,函式和程式碼塊一執行完就銷燬

全域性變數:

定義在檔案中,程式執行完銷燬

結構體

構造型別:

陣列  只能有一種資料型別

結構體:

可以有混合型別

結構體型別是不存在的,要我們自己定義:

    struct Person   //定義型別
    {
        int age;
        double height;
        char *name;
    }
    
    struct Person p = {20,1.55,"jack"};  //定義變數
    
    p.age = 30;  //還可以這樣定義值
    
    struct Person p = {.age = 30,.height = 1.44,.name = "rose"};  //還可以這樣定義

記憶體分析:

定義結構體並不會分配儲存空間

一個結構體佔的空間,是所有成員的和

對其演算法,結構體所佔的儲存空間,一定是最大成員所佔位元組數的倍數

定義結構體的多種方式

上面說了一種,還有一種
struct Person   //定義型別
{
    int age;
    double height;
    char *name;
} p;   //定義變數;

不可重用的方式:
struct //定義型別
{
    int age;
    double height;
    char *name;
} p;   //定義變數;

型別的作用域

和變數的作用域差不多

結構體陣列

struct Person pes[3] = {
    {},
    {},
    {}
};

指向結構體的指標

struct Person *p;
p = &Person;  //指標變數想儲存誰,就把誰的地址拿過來存起來

第一種方式:
Person.age;

第二種方式:
(*p).age;

第三種方式:
p->age;

巢狀定義:

    struct Person   //定義型別
    {
        int age;
        double height;
        char *name;
        struct Date a;
        struct Date b;
    };

列舉型別

定義列舉型別

enum Season
{
    spring,   //0
    summer,    //1
    autumn,   //2
    winter    //3
}

定義列舉變數
enum Season s = spring;

總結

基本資料型別

int

long int, long: 8位元組 %ld
short int, short: 2   %d %i
unsigned int, unsigned: 4  %zd
signed int, signed , int:4 %d  %i

float\double

float: 4 %f
double: 8  %f

char

1位元組  %c  %d
char型別儲存在記憶體中的是它的ASCII值

'A'  => 65

構造型別

陣列

只能由同一種型別的資料組成
定義: 資料型別  陣列名[元素個數]

結構體

可以由不同型別的資料組成
先定義結構, 再利用型別定義變數

指標型別

變數的定義

int *p

指標賦值

p = &age;

取值

*p;

修改值

*p = 22;

列舉型別

當一個變數只允許有幾個固定取值時,可以使用列舉

其他

預處理指令

把程式碼翻譯成0和1之前執行的指令(編譯)

所有的預處理指令都是以#開頭

位置隨便寫(也是有作用域的,從編寫指令的那個位置開始,一直到檔案結束)

巨集定義

全域性定義

#define COUNT 6  //也就是常量了,不過在編譯之前就會執行,這樣陣列使用不會報錯了

int age[COUNT] = {1,2,3,4,5,6};

失效

#undef COUNT;

帶引數的巨集定義

#define sum(v1,v2) (v1+v2)  //不加括號在用的時候會亂

條件編譯

按條件去編譯

#if (A == 5)
    ...
#elif (A == 6)
    ...
#endif
    ...

判斷有沒有定義巨集

#if define(MAX)  //#ifndef MAX
    ...code...
#endif
    ...

檔案包含

<>表示系統自帶的檔案, ""表示自定義的檔案

不允許迴圈包含 A包含B,B包含A

解決多次包含只解析一次:

#ifndef A
    #define A 123  //可以不寫值,定義一個空的巨集
    int sum () {}
#endif

一般以檔名為巨集名

關鍵字

typedef

int

typedef int MyInt;  //給int起個別名

char *

typedef char * String;  //給字串指標一個別名
String name = "asdf";

結構體

typedef struct Student Mystu;  //給結構體定義一個別名
Mystu stu1,stu2;

還可以

typedef struct Student
{
    int age;
} Mystu;

還可以

typedef struct     //只能用Mystu訪問了, 上面那種方法還可以使用原本的方式訪問
{
    int age;
} Mystu;     

定義列舉型別,也可以參照結構體

指向函式的指標

typedef int (*MyPoint)(int, int);

Mypoint p = sum;
Mypoint p2 = minus;

原本是
int (*p)(int ,int) = sum;

指向結構體的指標

typedef struct Per * PersonPoint;

PersonPoint p = &p;

還可以用巨集定義來代替
#define String char *; //但是隻能單個使用,不能 String s1,s2;

static 和 extern

對函式和變數有效

對函式

外部函式:本文字和其他文字都可以訪問,預設   extern省略
內部函式:只能被本文字訪問,其他檔案不能訪問  static

兩個檔案都有同樣名字的內部函式,是不衝突的

對變數

全域性變數分兩種:
    外部變數:本檔案和其他檔案都可以訪問  預設  extern  ()
        不同檔案中的同名變數,都代表同一個變數
    內部變數:只有本檔案可以訪問   static
    

static 定義了一個內部變數
extern 宣告瞭一個外部變數

static 對區域性變數的作用
    static修飾的區域性變數(函式內) 函式結束變數不釋放,一直到程式結束
            並沒有改變區域性變數的作用域
            多次執行函式,變數的值一直在變,所有的函式共享這個變數

遞迴

其他補充

指標的實質就是儲存了一個地址

指標在64位系統中佔8個位元組

因為64的系統,記憶體地址有2的64次方,所以要8個位元組去儲存

在分配記憶體的時候,gcc編輯器會做一個優化,優先分配同一型別的變數,
而不是按照程式碼的編寫順序進行分配

記憶體結構,從上到下:

系統記憶體       系統使用

棧記憶體         儲存的函式執行的狀態,區域性變數值 

可分配記憶體      由應用程式自由調配

堆記憶體         程式設計師自己分配

資料段         常量,全域性變數,和靜態變數

程式碼段         程式碼  a + b

array本質上是一個指標常量,地址固定
P指標是一個指標變數,位置可以隨便移動 也可以用p[i]取值

1772607-a9bbe5749a97d244.png

相關文章