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)
編輯
收藏
舉報