致初學者的我:一切都是由淺入深。
每種語言都有每種語言的特性,基本的特性是相同的,下面依照慣例寫hello world,相關編譯後面再介紹。
// C語言用“//”和“/**/”來寫注釋,注釋、空格等不影響語義(程序的意思)的符號會在程序編譯階段會被刪除。 #include<stdio.h> //#include 預編譯處理,這裡把stdio的C函數庫載入進程序; main() //main() 主程序入口; { printf("hello world!"); //printf() stdio函數庫中的函數,表示格式化輸出; return 0; //return 0 返回狀態0,表示程序正常結束。 }
part1 基本數據類型及表達式
定義(或聲明),根據數據類型在內存中申請空間,如int age;
賦值:修改變量的值,如 age = 30;
使用:將變量的值從內存中讀出來進行使用,如 printf("%d",age),或運算 age = age + 1;
int age = 23 數據類型 變量名 賦予變量的值
short n = 10; //內存中占16位 int n = 10; //占32位 long n = 10; //占64位
float f = 1.1; //占32位,有效位數6~7 double d = 1.1; //占64位,有效位數15~16
char c = 'a'; //占8位
二進制: 138 = 128(即1*2^7) + 8(1*2^3) + 2(1*2^1) ----->10001010 八進制: 138 = 128(2*8^2) + 1(1*8^1) + 2(2*8^0) ----->212 十進制: 138 = 100(1*10^10) +30(3*10^1)+ 8(8*10^0) ----->138 十六進制: 138 = 128(8*16^1) + 10(10*16^0) ----->8A //十六進制中 10-15 分別以A-F來表示
short s = -1; unsigned short us = s; // us = 65535 short s2 = 65; char c = s2; //c = 'A'
精度損失:如果把存儲大的類型賦值給小的類型,會面臨精度丟失的問題。
{ int age = 10; ... ....以上這些行都可以訪問age } 這裡不能訪問age
const double PI = 3.14; //定義圓周率的符號常量 #define PI 3.14; //用宏定義的方式,分不出數據類型 printf("PI=%f\n",PI); 注:盡量使用const來定義符號常量
注意:盡量避免使用魔法數,指的是在程序中多處出現的常量值,應該使用上述方法將其賦值給一個恆定的變量。
id = 3 //賦值表達式, =是賦值操作符 1+2 //計算表達式,+是算術運算符 ++i //計算表達式,++是算術計算符 2>1 //邏輯表達式,>是關系運算符符 (double)a //類型轉換表達式 1 && 0 //邏輯表達式,&&是邏輯運算符 ...
int a = i++ + 3; //先讓i參與表達式運算,然後將i+1 int a = ++i + 3; //先將i+1,然後讓i參與運算
3 & 5 = 1 0011 & 0101 = 0001
3 | 5 = 7 0011 | 0101 = 0111
3 ^ 5 = 6 0011 ^ 0101 = 0110
~3 = -4
3<<1 = 6 011 --> 0110
3>>1 = 1 011 -->01
char[] fullName = "Rudolph_Browne";//fullName為左值,Rudolph_Browne為右值
1)數據:變量的數據值,存儲在地址中,這個值被稱之為右值(rvalue),r是read(可讀)的意思。 2)地址:存儲數據值的內存地址,地址被稱之為左值(lvalue),l是location(可尋址)的意思。
“每一個表達式要麼是lvalue,要麼是rvalue”。左值指表達式結束之後依然存在的持久對象,而右值指表達式結束之後就不復存在的(臨時)對象。
sizeof(float); //4 sizeof(double); //8 int n; sizeof(n); //4
int a = 1?5:10; //a為5 int b = (a%2==0)?1:0; //a為偶數時,b=1,否則為0
a=3, b=a+1, c=b+1; x = (a=3, b=6*3); //逗號表達式的值為最後一個表達式的值 (a=3,b) = 5; //最後表達式為左值時,逗號表達式也可為左值
part2 程序結構
printf("格式控制字符串"[,輸出列表]); 注意:輸出列表與格式控制字符串之間有一個逗號分隔(如果輸出列表存在的話)。
printf("%05hd\n",10); //h表示短類型,l表示長類型 輸出短整型數據10,寬度為5位,前面補0 //00010
d(i):十進制整數,負數帶-號 u:無符號十進制整數 o:無符號八進制整數,無前導字母o x(X):無符號十六進制整數,無前導0x,X表示大寫 f:小數形式的浮點數,默認6位小數 e(E):指數形式的浮點數 g(G):自動選擇e和f中寬度較小者,不打印無效的0 c:單個字符 s:字符串
putchar(c); //c可以是字符或整型數字
scanf("格式控制字符串",地址列表); scanf("%d",&a); //&a表示變量a的地址
注意:通常在用c庫函數輸出提示語句時都會適當地緩存,要輸出緩存時使用下面語句。
setbuf(stdout,NULL); //這句代碼是為了先輸出提示語句
char c = getchar(); //等待鍵盤輸入一個字符給變量c
if(條件1) //如果條件為真 { //那麼 //當條件成立時執行這裡的代碼 }else if(條件2) { //或者
//當條件2成立時執行這裡的代碼 }else
{ //否則
//執行這裡的代碼
}
switch(rank){ case 1: //獎勵iphone; break; case 2: //獎勵ipad; break; case 3: //獎勵ipod break; default: //無視 }
while(循環條件){ //循環操作 }
do{ //執行循環操作 }while(循環條件); //注意有分號
注意:while和do-while是在程序不知道具體停止位置而使用的,如果知道具體位置,請使用for。
for(參數初始化;條件判斷;參數更新){
//循環操作
}
while(...){ .... break; //下面的語句執行不到了,因為break跳出了循環 printf(...); } //break以後,執行這個語句
for(int i=0; i< 100; ++i){ if(如果是奇數){ continue; } sum += i; }
#include <stdio.h> int main(){ int i,j; for(i=0;i<4;++i){ printf("%d\n",i); for(j=0;j<4;++j){ if(i*j==4){ goto l1; } printf("\t%d:\n",j); } } l1: printf("循環跳出,i=%d,j=%d\n",i,j); return 0; } //注:在C語言中,由於goto的靈活性,一般不提倡使用goto進行跳轉。
part3 高級數據類型與
類型 變量名稱
int size = 3; int nArray[3] = {1,2,4}; int d1 = nArray[0];//0是下標,nArray[0]是下標為0的元素 //注意下標不能超出數組的長度-1
for(i=0;i <= size;i++){
//循環體
}
int a[4]; //a[0](假設地址為2000H)、a[1]、a[2]、a[3] 2000H 2004H 2008H 200CH a[0] a[1] a[2] a[3]
int a[4]={1,2,3,4,5}; //編譯提示越界
但在運行時訪問下標的時候,編譯器不會檢查越界,此時程序員要自己注意。例如:
int a[4]={1,2,3,4}; printf("%d",a[4]); //a[4]指向2010H地址,內存的值未知 a[4] = 100; //如果修改了a[4]的值,實際是修改了別人的數據,可能導致程序出錯或崩潰
1)int a[4]={}; 2) #include <string.h> int a[4]; memset(a, 0, 4*sizeof(int)); //以字節數為單位賦值 3)for循環將每個元素設置成0
注:static變量或全局變量自動會初始化成0。
#include <stdio.h> int main(){ int i,a[4]; for(i=0;i<4;++i){ printf("a[%d]的地址是:%p\n",i,&a[i]); } printf("數組變量a的地址是:%p\n",&a); return 0; }
輸出結果為:
=====運行開始====== a[0]的地址是:0x7fff1d9e5ae0 a[1]的地址是:0x7fff1d9e5ae4 a[2]的地址是:0x7fff1d9e5ae8 a[3]的地址是:0x7fff1d9e5aec 數組變量a的地址是:0x7fff1d9e5ae0 =====運行結束======
int i, max=arr[0]; for(i=1;i<len;++i){ if(max<arr[i]){ max = arr[i]; } } //循環完成以後,得到最大值max
int i,sum=0; for(i=0;i<len;++i) sum += arr[i]; double avg = (double)sum/len;
int value = 5; //要查找的數 int i; for(i=0;i<len;++i){ if(arr[i] == value){ break; } } //如果i<len表示有找到,i就是該元素的下標,如果i = len表示沒有找到
for(i=3; i<LEN-1; ++i){ arr[i] = arr[i+1]; //依次往前移動一位,下標3的被4的覆蓋了 } arr[len-1] = '\0'; //最後一位設置為0
刪除給定值的元素:先通過查找獲得下標,然後執行1。
int find=0; int removeIndex; for(removeIndex=0; removeIndex<LEN; ++removeIndex){ if(arr[removeIndex] == 81){ find = 1; break; } } if(find){ for(i=removeIndex; i<LEN-1; ++i){ arr[i] = arr[i+1]; } arr[LEN-1] = '\0'; }else{ printf("沒有找到要刪除的元素"); }
int a[6] = {1,2,3,4,5}; //注意數組長度要夠 for(i=len-1; i>2; --i){ //注意i從大到小 a[i] = a[i-1]; //依次往後移動,空出a[2] } a[2] = 10;
for(i=0; i<len; ++i){ newArr[i] = arr[i]; } //或:memcpy(newArr, a, len*sizeof(a[0]));
合並:同樣可以用循環賦值或memcpy函數。
int a[3]={1,2,3}, b[4]={4,5,6,7}; int c[7]; for(i=0; i<7; ++i){ c[i] = (i<3)?a[i]:b[i-3]; } //或:memcpy(c, a, 3*4); //memcpy(c+3, b, 4*4);
int scores[2][4] = {{1,2},{2,3},{3,4,5,6}}; for(int i=0; i<2; i++){ for(int j=0; j<4; j++){ printf("%d",scores[i][j]);
}
printf("\n");
}
結果為
=====運行開始====== 1200 2300 //因為2行中賦值中有{1,2,(0,0)}和{2,3,(0,0)},即不足的以0來填補。 =====運行結束======
scores[3][5],三個行如下: scores[0] --> 一班的分數:5個元素的一維數組 scores[1] --> 二班的分數:5個元素的一維數組 scores[2] --> 三班的分數:5個元素的一維數組 共15個元素,按照順序存儲,如: 2000H 2001H 2002H 2003H 2004H 00 01 02 03 04 2005H 2006H 2007H 2008H 2009H 10 11 12 13 14 2010H 2011H 2012H 2013H 2014H 20 21 22 23 24
矩陣算法可以使用二維數組進行運算,如a[3][3]:
1 2 3 --> a[0] 4 5 6 --> a[1] 7 8 9 --> a[2]
"Hello" ---> {'H','e','l','l','o','\0'} //字符串長度為5,數組長度為6
輸出字符串數組變量時,遇到’\0’結束,’0’是ASCII碼的0,可以通過循環輸出看到,例如
char a[] = "Hello"; int len = sizeof(a)/sizeof(a[0]); //6 printf("%s\n",a); //Hello for(int i=0; i<len; ++i) printf("%d ",a[i]); //--> 最後一個是 0
字符數組的定義和賦值:
char a[] = "Hello"; char a[] = {"Hello"}; char a[] = {'H','e','l','l','o','\0'}; char a[]; a = "Hello"; //錯誤,定義後就是常量,不能再整句賦值
char s[10] strcpy(s, "Hello"); //s-->Hello\0
strcpy的覆蓋性:如果目標字符數組中已經存在內容,將被覆蓋。
char s[10]="123456789" strcpy(s, "Hello"); //s-->Hello\0789 printf("%s",s) //Hello
strncpy:strncpy(目標字符數組,源字符數組,拷貝長度),將源字符數組的前n個字符(不一定包括結束符\0)拷貝到目標字符數組中,函數返回目標字符數組。
char s[10]="123456789" strncpy(s, "Hello",3); //s-->Hel456789 printf("%s",s); //Hel456789
strcpy的溢出性:如果目標字符數組的長度比源字符數組的長度短,strcpy函數會導致溢出,即目標字符數組地址後面的內存地址會被寫入多出的字符,產生不可預期的後果。
char a = 'a'; char s[3]; strcpy(s,"Hello"); //s-->Hello\0 --> 可能會導致a被修改
char s1[20] = "i am"; char s2[] = "a boy"; strcat(s1,s2); //s1-->i am a boy\0 printf("%s",s1); //i am a boy
字符數組1 = 字符數組2, 返回值=0 字符數組1 > 字符數組2, 返回值>0 字符數組1 < 字符數組2, 返回值<0
當需要判斷兩個字符串是否相等,例如判斷密碼是否為“137000”,代碼如下:
char pwd[] = "137000"; int b = strcmp(pwd,"137000"); //b == 0
char s[20] = "hello"; printf("%d",strlen(s)); //5
strlen與sizeof的區別
1)sizeof是編譯時計算,關注分配了多少空間,不關注實際存儲的數據 2)strlen是運行時計算,關注存儲的數據內容,不關注分配的空間。
char s[10] = "hello"; printf("%d",strlen(s)); //5 printf("%d",sizeof(s)); //10
int main(){ //打印三個*行 int i; for(i=0; i<3; ++i) printStar(); }
函數的調用:函數是一個黑匣子,外部無需知道函數裡面具體怎麼執行的,只需要調用函數,獲取返回的結果即可。
返回值類型 函數名(參數列表) void printStar(int n, char c){ //在這裡輸出*號組成的行 }
函數需要先定義、後調用,即函數的定義應當在主調函數之前,如
void printStar(int n, char c){} int main(){ printStar(5,'*'); ... }
有時候我們想先調用函數,之後再定義函數,則需要在調用之前先聲明,這和變量的聲明、使用、賦值類似,如
int main(){ void printStar(int n, char c); //聲明,注意沒有函數體 printStar(5,'*'); } void printStar(int n, char c){//定義這裡有函數體}
調用系統庫函數,要使用#include<庫文件>,在c語言中,庫文件使用*.h文件,例如
#include <stdio.h> #include <stdlib.h> //使用庫文件 int main(){ printf("%d的絕對值是%d", -3,abs(-3)); //輸出絕對值 return 0; }
#include <stdio.h> int add(int a, int b){ printf("\t開始執行add函數,參數%d,%d\n",a,b); int sum = a + b; printf("\tadd執行完成,准備返回%d\n",sum); return sum; } int main(){ printf("開始執行main函數\n"); printf("調用add函數\n"); int a = add(3,5); printf("調用add函數返回:%d\n",a); a = add(1+2,2*4); printf("調用add函數返回:%d\n",a); printf("main函數執行結束\n"); return 0; }
=====運行開始====== 開始執行main函數 調用add函數 開始執行add函數,參數3,5 add執行完成,准備返回8 調用add函數返回:8 開始執行add函數,參數3,8 add執行完成,准備返回11 調用add函數返回:11 main函數執行結束 =====運行結束======
int add(int a, int b){ //在這裡可以使用變量a和b }
參數列表的定義相當於定義了局部變量,其作用域是函數體,在函數沒有被調用時,形參沒有內存分配,當函數調用時被分配內存,函數結束後,內存被釋放。
int main(){ int sum = add(3.2, 5); //3.2傳給a(自動轉換),5傳給b }
int change(int a){ return ++a; } int main(){ int a = 5; change(a); printf("%d",a); //a還是5,不會變成6 }
1)靜態存儲區:主要存放全局變量、靜態變量(static)、字面常量,其生存期是從程序開始到程序結束; 2)動態存儲區:主要存放局部變量、參數等,在程序運行期間動態分配,其中棧和堆的使用又有所不同。
{ int a; ---> auto int a; }
自動變量屬於動態存儲方式,當函數被調用時才分配存儲,函數結束即自動釋放。作用域是定義它的單元,如函數或代碼塊。
{ static int a; //函數結束不會釋放,下次還可以繼續用 }
static的理解:具備靜態存儲特性,又具備函數的作用域。
注:全局變量或函數前面加static,表示作用域在本文件內部,不能被其它文件引用。
file1.cpp file2.cpp int global; <--- extern int global; //用到file1.cpp
int maain(){ register int i, sum=0; }
int sum(int a, int b){return a+b;} int avg(int a, in b){return sum(a,b)/2;}
int sum(int a){return a+sum(a+1);}
遞歸的2個條件: 1)有反復執行的過程(自己調自己) 2)有跳出反復過程的條件(遞歸出口)
//由於傳遞的是地址,不需要指定個數,需要傳入個數的參數 void changeArr(int a[], int len){ a[0] = 100; } int main(){ int a[5] = {1,2,3,4,5}; changeArr(a,5); //傳入的是數組名 printf("%d",a[0]); }
int max(int a[], int len){ int i, max = a[0]; for(i=0; i<len; ++i){ if(max<a[i]) max = a[i]; return max; }
struct student //student是新定義的結構名 { int no; //學號 char name[10]; //姓名 int age; //年齡 char address[30]; //地址 }; //注意分號
結構體體現了數據的封裝性,用結構體變量進行信息傳遞,一方面可以減少參數的數量,另一方面當業務邏輯有變化時,可以很方便的修改結構體的定義,不需要修改傳遞接口(函數定義)。例如:
struct car { ... } //當汽車增加新成員字段時,不需要修改buyCar的函數定義 void buyCar(struct car c){ .... }
結構體變量作為函數參數時,采取的是值傳遞的方式,即形參產生了和實參同樣數據的一個內存拷貝。一方面,結構體數據多的時候內存開銷大,另一方面,函數裡面對數據的修改不會影響到實參。如
void changeCar(struct car c){ c.price = 200; } struct car c = {"寶馬","X5","3.0","紅色",130}; changeCar(c); //在函數裡修改價格 printf("%d",c.price); //價格還是130
如果想讓結構體變量在函數中被修改,跟其他類型一樣,可以使用指針,如
void change2(struct car *c){ c->price = 200; //指針引用成員用 -> 運算符 } change2(&c);
結構體作為返回值:當函數需要返回多個值時,可以用結構體變量。例如
struct point { int x; int y; }; struct point createPoint(){ struct point p = {0,1}; return p; }
union test { int i; char c; float f; } a, b, c;
注意:一個聯合體變量一次只能給一個成員賦值,後賦值會覆蓋前面的賦值,只有最後賦值的成員才有意義。
#include <stdio.h> union test{ int i; char c; float f; }; int main(){ union test t ; printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f); t.i = 10; printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f); t.c = 'a'; printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f); t.f = 1.0f; printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f); return 0; }
=====運行開始====== i=1314011120,c=-16,f=881720320.000000 i=10,c=10,f=0.000000 i=97,c=97,f=0.000000 i=1065353216,c=0,f=1.000000 =====運行結束======
enum weekday {sun,mon,tue,wed,thu,fri,sat}; enum weekday day1 = sun; //變量的值只能是枚舉元素之一
#include <stdio.h> enum weekday {sun,mon,tue, wed,thu,fri,sat}; int main(){ enum weekday day1 = sun, day2; printf("day1 = %d\n",day1); day2 = (enum weekday)(day1 + 3); printf("day2 = %d\n",day2); if(day2 == wed) printf("星期天過3天是星期三\n"); return 0; }
typedef struct student{ int no; string name; } STUDENT; //定義新的結構體類型STUDENT STUDENT zhangsan;
int n = 10; 2001H~2004H ----> 10 &n (int有4個字節)
指針:變量的指針就是變量的地址(內存中的首地址)
int a = 10 int *p; //定義一個指針變量,指針的類型是int p = &a; //p是指針變量,p的數據是a的地址 p p的數據是地址 地址指向的值 2005H ---> 2001H --> 10 *p --> a的地址裡面的數據 --> 10
*是“間接運算符”,用來定義一個指針變量,以及取指針變量指向的數據。賦給指針變量的值必須是地址常量或變量,不能為普通整數(可以為 0表示空指針),此地址中存放的數據類型必須與指針類型相符。如
1)int *p; //定義指針變量 p int *p = &a; ---> int *p; p=&a 2)printf("%d\n",*p); //取指針變量p指向的數據 *(&a) --> a //取&a這個地址(指針)指向的數據
*p = 5; //把指針的值修改為5,n == 5
int n; char c; void *p; p = &n; p = &c;
注意:void類型指針不能直接輸出或運算,需要轉換成指定類型。
void *pv; int *pint, n; pv = &n; pint = (int*)pv; //強制類型轉換
int n[5]; int *p1=n,*p2=&n[3]; p1-p2 --> 3
注:不是同一數組的指針相減沒有意義。
int n, *p=&n; //int是4字節 p+1 --> (n的地址 + 4 )的新指針
int a=10,b=20,c=30; int *p=&b; *p --> 20 *p++ --> 20,但此時p指向 p+1的 地址(4個字節)
p<q p指向的元素在q指向的元素之前,返回1,否則0 p>q p指向的元素在q指向的元素之後,返回1,否則0 p==q 兩者指向同一元素,返回1,否則0 p!=q 不指向同一元素,返回1,否則0
p==NULL 空指針返回1,否則0 p!=NULL 不是空指針返回1,否則0
int a = 3, b = 10; const int *p = &a; *p = 5; //報錯,不能修改 p = &b; //可以修改
int a=3, b=10; int *const p = &a; p = &b; //報錯,不能修改 *p = 5; //可以修改
理解:看const後面是什麼,如果是*p則表示指針的值不能修改,如果是p則表示指針不能更換指向。
struct 結構體類型 *變量名; struct student stu, *p; p = &stu;
stu.name; (*p).name; p->name; //指針專用,指向運算符,優先級比單目運算符高 p->age++; //先獲得age成員,再++
void change(int *p){ *p = 20; } int main(){ int a=5; change(&a); //傳入指針,在函數裡a的值被修改 }
void print(int n){ //回調函數,打印信息 printf("回調函數調用:%d\n",n); } void loop(int max, int (*p)(int n)){ //遍歷1..n,如果7的倍數就打印信息 int i; for(i=0;i<max;++i){ if(i%7==0) p(i); } }