天天看點

C語言基礎複習總結

C語言基礎複習總結

大一學的C++,不過後來一直沒用,大多還給老師了,最近看傳智李明傑老師的ios課程的C語言入門部分,用了一周,每晚上看大概兩小時左右,效果真是頂一學期的課,也許是因為有開發經驗吧,廢話少說,直接把總結貼出來了~

#include <stdio.h>

int main(int argc, const char * argv[])

{

    printf("Hello, World!\n");

    return 0;

}

#include是預處理指令,在編譯前把尖括号裡的内容原封不動地拷貝到對應位置。.h是頭檔案,裡面是庫函數的聲明(不是實作)。尖括号表明是系統自帶的,會去系統目錄找,雙引号是自己的檔案,會先在源程式目前目錄找,找不到就去作業系統的path路徑找,還找不到才去C函數庫裡找。

包含關系允許嵌套包含但是不允許遞歸包含(死循環)。

C語言文法不嚴格,main函數可以不寫傳回值,預設傳回int,參數可以不要,可以不return。

C語言程式運作的過程:

1.把源代碼翻譯成目标代碼。Xcode是64位編譯器。編譯成功後生成同名.obj檔案(多個c檔案對應多個obj檔案)。

2.把C語言源檔案之間的調用依賴以及C語言函數庫連結進來,成為可執行的機器代碼。在xcode下生成的是unix可執行檔案。

在java當中方法定義沒有順序限制,但是在标準c當中隻能後面的函數調用前面的,因為C是從上往下編譯的,如果想放在前面,需要聲明,聲明可以省略參數名,隻要類型:

#include <stdio.h>

int sum(int, int);

int main(int argc, const char * argv[])

{

    int c = sum(10,3);

    printf("%d\n", c);

    return 0;

}

int sum(int a, int b)

{

    return a+b;

}

一般來說,會把函數聲明和定義放在不同檔案當中,比如把sum函數的聲明放在test.h裡,實作放在test.c裡,然後在main函數之前引入:

#include "test.h"

反複引用同一個檔案是沒關系的,可以使用預編譯指令做檢查機制。但是不要導入.c檔案,以免在連結時出現函數重複而報錯,C不是面向對象的,函數名不能重複。

printf輸出需要使用百分号占位符,.2f是保留兩位小數,不是四舍五入。

    // My age is 26, height is 1.55, name is 李明傑,sex is \'A\'

printf("My age is %d, height is %.2f, name is %s,sex is ‘%c’\n", 26,1.55f,"李明傑",\'A\');

scanf是阻塞性的函數,等待标準裝置輸入,需要傳變量的位址。

#include <stdio.h>

int main(int argc, const char * argv[])

{

    printf("請輸入兩個整數,用逗号隔開:");

    int a,b;

    scanf("%d,%d", &a, &b);  //傳a的位址

    printf("%d\n", a+b);

    return 0;

}

C語言的類型分四類,一種是空類型viod,一種是int,float,double,char,一種是構造類型,比如數組,struct,union(基本沒用),enum,另一種是指針類型void*。C是強類型語言,指定類型是為了配置設定适當大小的空間。Char類型不論多少位的編譯器,都占一個位元組。

C與java不同,局部變量沒有初始化使用也不會報錯,但預設值不一定是0,是随機數,是以不要不初始化。全局變量則會被預設初始化。

Char類型範圍是-128到127,最好不用ascii碼的值,直接用’a’,它不是unicode的,也不支援字元串。

類型修飾符:short,long,signed,unsigned,最常用的是修飾int,被修飾的int可以省略,比如隻寫long,與java不同,這并不代表這是long類型,而是int。不管什麼編譯器,int至少2個位元組。

C語言裡沒有boolean,關系判斷傳回1或0的int值,沒有-1!任何非0值都位真,隻有0才是假,比如if(9)為真。

C語言可以用逗号連接配接多個表達式,它的傳回值是最後一個表達式的值,下面的代碼輸出12。

#include <stdio.h>

int main(int argc, const char * argv[])

{

    int a=9;

    int b=10;

    int c;

    c = (a=a+1,b=3*4);

    printf("%d",c);

}

對于一個變量,變量存儲單元的“第一個位元組”的位址就是該變量的位址,取位址用&,傳回數字,習慣用16進制。

#include <stdio.h>

int main(int argc, const char * argv[])

{

    char a = \'A\';

    int b = 66;

    printf("%x\n", &a);

    printf("%x", &b);

}

數組用來存放“同一種”類型的變量,不能用變量做長度,應該用常量,但是xcode不會報錯。系統為數組配置設定的空間是連續的。

    int ages[5];

printf("%d",sizeof(ages));

得到的長度是20,C語言數組名就代表數組位址,是以ages就是個常量,不能指派。取數組位址的方法有:

    printf("%d\n",&ages[0]);

    printf("%d\n",ages);

數組可以初始化,比如

int a[2] = {8, 10};

放在後面的元素可以省略,但是可讀性不好,比如

int a[2] = {8, };

如果後面已經定義了全部元素則長度可省略,比如

int a[] = {8, 10};

C語言傳參是傳值,但是傳數組(指針)的話則不是,實際傳的是位址,是以内容會被改變。

#include <stdio.h>

//接受數組參數,可以不寫長度

void test(int array[])

{

    array[0] = 9;

}

int main(int argc, const char * argv[])

{

    int a[3];

    a[0] = 10;

    printf("%d\n",a[0]);

    test(a);    //數組名代表位址,傳的是指針

    printf("%d\n",a[0]);

}

二維數組是一維數組的集合,是由一維數組組成的一維數組。它在記憶體中是按行存儲的,比如a[0][0]->a[0][1]->a[0][2]->a[1][0]。

取位址的方式有a,a[0],&a[0][0]。

初始化可以按行:

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

也可以都寫出來:

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

可以部分省略,預設為0:

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

可以省略行數,不能省略列數:

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

C語言沒有String類型,多個字元用字元數組存儲,為了和普通字元數組區分,字元串數組用’\0’結尾,必須寫,否則可能會記憶體溢出,它是一個ascii碼為0的字元,是空操作符,表示什麼也不幹,是以“mj”的長度是3,不是2。通常用下面第二種初始化:

char s1[] = { \'m\',\'j\',\'\0\' };

char s2[] = "mj";

列印的方式:

printf("%s\n", s2);

puts(s2);

放在尾部\0是因為輸出過程會從字元串位址開始向後找第一個\0,是以必須要有結尾,沒有結尾就會一直找下去,輸出錯亂的東西。

字元串輸入的過程會自動在尾巴加\0,例子:

    char s[20];

    scanf("%s", s); //s就是位址,不需要&s

printf("%s", s);

但是gets是不安全的,原理同上,會把另一個字元串s2裡的内容沖掉,例如:

    char s2[] = "mj";

    char s1[2];

    gets(s1);

    printf("%s\n", s1);

printf("%s\n", s2);

字元串本身就是數組,如果要存儲多個字元串,則需要使用二維數組,比如char names[15][20]表示可以存15個名字。代碼:

char names[2][20] = {{"jay"},{"jim"}};

字元串處理的兩個方法:

    //輸出到控制台

    putchar(\'a\');

    //等待使用者輸入

    char c;

c = getchar();

字元串處理函數聲明在string.h當中。

測量字元串的字元長度:

    int len = strlen("李明傑");

printf("%d\n", len);

輸出9,strlen傳回字元串的字元數,不是長度,中文是3個字元。

字元串拷貝:

    char left[10];

    strcpy(left, "itcast");

printf("%s", left);

從右邊的常量拷貝給左邊的變量并且自動加\0。

拼接字元串:

    char left[10] = {\'m\',\'j\',\'\0\'};

    strcat(left, "ios");

printf("%s", left);

把右邊的字元串,接在左邊的後面,會去掉左邊的“第一個”\0,但是要保證左邊字元串的長度足夠,否則會記憶體溢出。

字元串比較:

    int delta = strcmp("abc","ABC");

printf("%d", delta);

傳回左邊減右邊的差,一位一位地比ascii碼,\0就是0.

指針變量用來儲存一個特定類型的變量的位址,如:

    char a;

    char *b = &a;

    *b = \'a\';

printf("%c",a);

b是一個char*類變量指向a的位址,第三行*b當中的*是指針運算符,*b表示通路b的值(a的位址)所對應的存儲空間。

指針類型所占用的空間隻和編譯器有關。

其中第二句可以拆分成兩句:

    char *b;

b = &a;

第二句不能寫*b,*是通路符。

指針操作的兩個錯誤:

    //錯誤1:不要直接使用未配置設定的指針

    char *p;

    *p=10;

    //錯誤2:不要給指針變量直接賦位址

p = 100;

交換a和b的例子:

void swap(int *v1, int *v2)

{

    int temp = *v1;

    *v1 = *v2;

    *v2 = temp;

}

int main(int argc, const char * argv[])

{

    int a = 10;

    int b = 9;

    swap(&a,&b);

    printf("%d %d\n",a,b);

}

因為預設會傳臨時變量,是以要傳位址,參數表用指針類型,調用的時候就要傳位址進去。交換的時候也要用星号來取指針變量裡的位址對應的值。

用指針可以實作函數多傳回值,類似c#的out參數。比如:

int sumAndMinus(int v1, int v2, int *p) {

    *p = v1 - v2;

    return v1 + v2;

}

int main(int argc, const char * argv[])

{

    int a = 10;

    int b = 4;  

    int sum;

    int minus;

    sum = sumAndMinus(a,b,&minus);

    printf("%d %d",sum,minus);

}

數組的名字就是它的位址,是以把指針指向數組的時候,後面兩行代碼是等價的:

    int a[2];

    int *p;

    p = &a[0];

p = a;

可以用指針周遊數組,如下:

    int a[3] = {1,2,3};

    int *p = a;

    for (int i=0; i<3; i++) {

        printf("a[%d]=%d\n",i,*(p+i));

}

對于指針變量來說,p+i當中的i要看p指向的類型,比如指向一個兩個位元組的類型,i就是兩個位元組,不是純粹加一個數字。上面的做法不會改變p所指向的内容,但是如果寫*(p++)則會改。

另外,既然數組名就是p所指的,是以*(a+i)也可以,但是*(a++)不可以,數組的首位址是常量不能改。

像下面這樣也可以,隻是p最終位置改變了:

    int *p = a;

    for (int i=0; p < a + 3; i++, p++) {

        printf("a[%d]=%d\n",i,*p);

}

如果參數表是數組,那麼傳數組名或指針都可以:

void change(char c[]) {

    c[0] = 1;

}

int main(int argc, const char * argv[])

{

    char a[3];

    change(a);

}

如果參數表是指針,那麼也可以傳指針:

void change(char *c) {

    *c = 1;

}

int main(int argc, const char * argv[])

{

    char a[3];

    change(a);

    printf("%d\n", a[0]);

}

總之,如果形參是數組或指針,則可以傳遞數組名或指針。

對于一個字元串,周遊的方式有:

    char s[7] = "itcast";

    for (int i=0; s[i]!=\'\0\'; i++) {

        printf("%c\n", s[i]);

    }

比較好了解的方法是:

    char *p = "itcast";

    for (; *p!=\'\0\'; p++) {

        printf("%c\n", *p);

}

第一種方式利用數組定義的是字元串變量,但是第二種方式用指針定義的是字元串常量,是以,第二種方法一旦用下面的方式來改寫就錯了:

    char *p = "lmj";

*p = \'f\';

總之,char a[] = “lmj”是變量,char *p = “lmj”是常量,嚴格來說前面應該加上const。

一個函數可以傳回一個指針,比如:

char * test() {

    return "itcast";

}

函數的名稱就代表函數的位址,可以定義指向函數的指針,比如:

#include <stdio.h>

int sum(int a, int b) {

    return a+b;

}

int main(int argc, const char * argv[])

{

    //定義一個特定傳回值和參數表的指針p

    //需要占位的是函數名,并且指向sum函數

    int (*p)(int, int);

    p = sum;

    //利用指針變量p取出所指的函數,間接調用

    int result = (*p)(1,2);

    //也可以直接調用

    int result2 = p(5,6);

    printf("%d %d", result,result2);

}

可以把函數指針當做參數來使用,類似c#當中傳遞lambda表達式:

#include <stdio.h>

int calculate(int a, int b, int (*p)(int,int)) {

    return p(a,b);

}

int sum(int a,int b){

    return a+b;

}

int main(int argc, const char * argv[])

{

    int result = calculate(1,2, sum);

    printf("%d", result);

}

預處理指令以#開頭,是在編譯之前執行的,三種常用的預處理指令分别是宏定義、檔案包含和條件編譯。

宏定義的功能是字元串替換,通常用來定義常量:

#define NUM 6

int main(int argc, const char * argv[])

{

    int a[NUM] = {1,2,3,4,5,6};

}

也可以使用帶有參數的宏定義:

#define mul(a,b) ((a)*(b))

int main(int argc, const char * argv[])

{

    int a = mul(1,2);

    printf("%d",a);

}

定義帶有參數的宏最好把參數帶上括号,因為它的實質是字元串替換。最外層最好也加一個括号,因為宏替換之後,它的整體還會和其他代碼進行數學運算。

宏定義沒有記憶體檢測,純粹是字元串替換,是以一些簡單的計算,執行起來性能比函數要好。

條件編譯指,某段代碼,隻讓它在滿足某種條件時才編譯。

#include <stdio.h>

#define NUM 10

int main(int argc, const char * argv[])

{

#if NUM > 0

    printf("NUM大于0");

#elif NUM == 0

    printf("NUM等于0");

#else

    printf("NUM小于0");

#endif

    return 0;

}

注意預處理指令裡的宏NUM隻能用預處理指令裡定義的。

另外,可以根據有沒有定義過宏來判斷:

#define NUM 10

int main(int argc, const char * argv[])

{

#ifdef NUM

    printf("定義了");

#endif

#ifndef NUM

    pringf("沒定義");

#endif

    return 0;

}

C的變量有不同的存儲類型、生命周期和作用域。

局部變量隻在函數内有效。全局變量被其他函數共享,從定義的位置到源代碼結尾有效。

存儲類型:有三個地方可以存變量:運作時堆棧、普通記憶體和硬體寄存器,它決定了變量的生命周期。

三個地方分别對應自動變量、靜态變量和寄存器變量。

被關鍵字auto修飾的“局部”變量是自動變量,預設所有局部變量都是自動變量,基本不寫auto。當執行到自動變量所在的函數時,自動變量會建立,離開函數時會銷毀。

靜态變量會在程式運作時建立,在程式結束後銷毀。所有全局變量都是靜态變量。另外有一種被static修飾的局部變量也是靜态變量,這種修飾改變了它的生命周期,但是沒改變作用域,它的建立時間也是在函數調用的時候。函數内的static變量可以在函數内部進行計數。

存儲在硬體寄存器内的變量叫寄存器變量,它被register修飾,性能最高。隻有自動變量才能存儲在寄存器中,隻限存儲int、char和指針類型。如果寄存器已滿,在運作時會自動轉化為自動變量處理。寄存器變量通常是一些頻繁使用的變量。寄存器變量的生命周期和自動變量一緻。

允許被其他源檔案調用的函數是外部函數,函數預設是外部函數,不允許有同名的外部函數,否則會有連結錯誤。

定義或者提前聲明一個外部函數需要用extern,但是預設就是外部的,是以這個關鍵字一般不寫。在C99标準當中,如果一個源檔案裡沒有提前聲明外部函數會報編譯錯誤,但是xcode裡不會出錯。

不允許其他檔案通路的函數是内部函數,不同源檔案裡允許有同名的内部函數。C的static和java中完全不一樣,内部函數需要用static關鍵字修飾,這樣在連結的時候其他源檔案就無法找到這個static函數了。在同一個源檔案中,聲明一個static函數也需要加這個關鍵字。

C語言中一個函數不能使用在函數後面聲明的變量,除非在函數前面聲明,聲明變量的關鍵字是extern,這個extern其實又可以省略:

extern int a;

int main(int argc, const char * argv[])

{

    a = 10;

    return 0;

}

int a;

在一個函數内,如果用extern聲明了一個變量,那麼它使用的還是外部的變量,extern本來就是外部的意思,證明:

#include <stdio.h>

void test();

int main(int argc, const char * argv[])

{

    extern int a;

    a=10;

    test();

}

int a;

void test()

{

    printf("%d", a);

}

在C語言中在不同的源檔案當中存在同名的全局變量,則這些變量都代表同一個變量。允許被其他檔案通路的變量叫外部變量,預設情況下定義的變量都是外部變量。想使用其他檔案裡的外部變量,需要在前面聲明(extern可省)。注意,extern後面的變量,一定是聲明,而不是定義!

用static修飾的全局變量叫做内部變量,外部通路不到這個變量(外部的extern聲明語句無法看到内部的static變量)。如果說兩個檔案中的同名變量有一個或者都加了static,則它們不是一個變量。

結構體可以定義在方法内部或外部,如果在内部,則隻能在函數内使用這個結構體:

void main(int argc, const char * argv[])

{

    //定義結構體類型

    struct Student {

        int age;

        char *name;

        float height;

    };

    //定義結構體變量

    struct Student stu = { 27, "mj", 180.3};

    printf("%d", stu.age);

}

也可以同時定義結構體并定義變量指派,在這種情況下結構體的名字可以省略,因為沒有意義,類似java的匿名方法:

    struct {

        int age;

        char *name;

        float height;

} stu = { 27, "mj", 1.8f};

結構體内可以包含别的結構體,但是不允許包含自己導緻遞歸。需要注意的是,結構體的定義和初始化要寫成一句,如果拆成兩句會報錯。

可以定義指針指向結構體,通過指針有兩種方式取結構體内的值:

    struct Student stu = { 27, "mj", 180.3};

    struct Student *p = &stu;

    int a =(*p).age;

    int b = p->age;

把結構體當做參數傳給函數,傳的是值,不是位址,傳位址需要指針:

struct Student {

    int age;

};

void change(struct Student* stu) {

    stu->age = 1;

}

void main(int argc, const char * argv[])

{

    struct Student stu = { 27, "mj", 180.3};

    struct Student *p = &stu;

    change(p);

    printf("%d", stu.age);

}

C會把枚舉當做整形常量,從0開始,定義枚舉:

    enum Season { spring, summer, autumn, winter };

enum Season s = spring;

也可以同時定義枚舉變量并指派,和結構體一樣,枚舉的名稱也可以省略。

typedef關鍵字的作用是給資料類型定義一個别名,比如:

typedef int Integer;

typedef char* String;

void main(int argc, const char * argv[])

{

    Integer a = 10;

    String name = "itcast";

}

可以給結構體起别名,這樣定義變量的時候就能省略struct關鍵字了,這時建議用匿名的,因為沒用:

void main(int argc, const char * argv[])

{

    typedef struct {

        float x;

        float y;

    } CGPoint;

    CGPoint p = {10, 10};

}

例子:給指向結構體的指針重命名,此時也可以匿名:

    typedef struct Point{

        float x;

        float y;

    } * PPoint;

    struct Point point = {10,10};

    PPoint pp = &point;

事實上最常用的定義結構體和枚舉的方式是這樣,表意非常明确——給一個匿名的結構體命名:

    typedef struct {

        float x;

        float y;

} Point;

還可以給一個指向函數的指針一個别名:

int sum(int a, int b){

    return a+b;

}

void main(int argc, const char * argv[])

{

    typedef int (*SumPoint)(int,int);

    SumPoint p = sum;

    p(1,2);

}

在這裡SumPoint就是别名,不用在後面再起别名。

不建議用宏定義來其别名,它純粹是字元串替換,如果連續聲明了兩個變量就會有問題了。

posted on

2013-11-21 23:13 

HackerVirus 

閱讀(383) 

評論(0) 

編輯 

收藏 

舉報