C語言進階學習
數組
1)數組的使用 java
2)字符串(String):數組(尤爲是一維數組)最經常使用的地方——————>C語言中字符串就是一維數組
數組:
定義:具備相同類型的數據的有序集合,並用惟一的名字來標識。
1)數組必須直接聲明,編譯器在編譯階段爲其分配內存空間
2)C89數組必須是定長的,數組的大小在編譯時是固定的;C99容許使用變長數組,數組的大小在運行時肯定
void f(int longeur,int wide)
{
int matrix[longeur][wide];/*定義一個矩陣*/
/*數組的長度由兩個參數決定*/
}
3)數組的全部元素佔連續的內存空間,在內存中是線性(順序)存放的,保存數組所須要的內存空間直接與基本類型和數組長度有關。
數組佔用的內存空間 = sizeof(基類型)*數組長度
4)C不檢查數組是否越界,程序能夠在兩邊越界。程序員應本身加入越界檢查。數組能夠越界使用,可是初始化時不容許!
5)向函數傳遞數組:
定義數組形參的方法有三種:指針,定長數組,無尺寸數組
void func1(int *a){...}
void func2(int a[10]){...}
void func3(int a[]){...}
在函數的形參的聲明中,數組尺寸無所謂,由於C語言沒有邊界檢查
實際上,第二種方法在編譯後,編譯器產生的代碼就是讓函數接受指針,並不生成10個元素的數組
A.形參中的數據不能再理解爲數組,而必須理解爲指針:不能用sizeof求大小;但能夠再賦值,這與數組名的指針常量性質不同。傳值時有內容的賦值,但數組內的元素可能有不少,爲了不內容的大量賦值而佔用太多的內存,C規定數組傳參數就是傳指針
B.int a[][]不能作性參,由於a是指向int[]這樣一種數據類型的數組指針,但小表大小沒有肯定。而int a[][8]能夠,而且能夠直接用二維數組名(無需顯示轉換)作其實參[必定要指定一維行參的個數]
6)在處理一個數組的元素時,使用指針自增(p++)的方式一般比直接使用數組下標更快,使用指針可以使程序得以優化
7)C容許定義多維數組,維數上限由編譯器定義。但多於三維的數組並不經常使用,由於多維數組所須要的內存空間對維數呈指數增加。而且計算各維下標會佔用CPU時間(存取多維數組元素的速度比存取一維數組元素的速度慢)
8)對數組初始化時注意,C89要求必須使用常量初始化字符,而C99容許使用很是量初始化字符來初始化本地數組
void main()
{
int i,j,min,temp;
int array[10];/*定義一個整型的一維數組*/
printf("please input ten integer:\n");
for(i=0;i<10;i++){
printf("array[%d]=",i);
scanf("%d",&array[i]);
}
printf("the array is");
for(i=0;i<10;i++){
printf("%d",array[i]);
}
printf("\n");
/*排序*/
for(i=0;i<9;i++)
{
min = i;
for(j=i;j<10;j++)
{
if(array[min]>array[j])min = j;
temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
printf("\nthe result:\n");
for(i=0;i<10;i++)
{
printf("%d",array[i]);
}
printf("\n");
}
字符串:
1)C沒有專門的字符串變量,對於它的操做所有由一維數組實現。字符串是字符數組的一種特殊形式,惟一的區別就在於她是做爲一個總體操做,而普通數組則不能。最終差異就在末尾的NULL(0)上
2)初始化操做:要使用字符串常量時則把它放到數據區中的CONST區(數據區,全局變量區和靜態變量區),用字符串常量初始化字符數組時有一個複製內容的操做,而不只僅是用一個指針指向他。實際上字符串常量是在常量區仍是在堆區,採用何種存儲結構,以及是否連續的問題,取決於不一樣的編譯器
3)字符串的輸入與輸出,下面的函數均由<stdio.h>定義
A.printf("%s",str);
B.puts(str);
C.scanof("%s",str);
D.gets(str)
4)字符串運算:下面的函數均由<string.h>定義
strcpy(s1,s2),strcat(s1,s2),strlen(str),strcmp(s1,s2),strchr(s1,ch),strstr(s1,s2)
注意:字符數組,字符指針之間的==比較是地址的比較,結果不多是true.但字符常量的==比較則不必定。爲了不二義性,應該儘量用strcmp()來比較
jellonwu
@jintao :~/Desktop$ vim test13.c
#include <stdio.h>
#include <string.h>
main()
{
char a[20] = "hello world";
//字符串最後一個字符默認會多加一個字符'\0'
//等價於a[0]='h';a[1]='e'....a[11]='\0'
char b[20];
int len;
len = strlen(a);//字符串長度不包含最後一個字符中止符'\0'
printf("The string length is %d.\n",len);
len = sizeof(a);
printf("The string sizeof vlaue is %d.\n",len);
strcpy(b,a);
printf("%s\n",b);
}
jellonwu
@jintao :~/Desktop$ gcc test13.c -o test13
jellonwu
@jintao :~/Desktop$ ./test13
The string length is 11.
The string sizeof vlaue is 20.
hello world
======================================
指針(Pointer)-----尤爲重要
指針是C語言的精華
1)使用指針的好處:
-->可以內調用函數靈活地修改實參變量的值
-->支持動態內存分配,可以方便地實現動態的數據結構(如二*數和鏈表)
-->能夠提升某些程序的效率
-->實現緩衝方式的文件存取
2)指針是地址。技術上,任何類型的指針均可以指向內存的任何位置。可是指針的操做都是基於類型的。
指針操做是相對於指針的基類型而執行的,儘管在技術上指針能夠指向對象的其餘類型,但指針始終認爲它指向的是其基類型的對象。指針操做受指針類型而不是她所指向的對象類型的支配
指針表達式:
1)printf("%p",....)/*以宿主計算機使用的格式現實地址*/
2) 指針轉換:
-->void * 型:爲Generic Pointer,經常使用來講明基類型未知的指針
它容許函數指定參數,此參數能夠接受任何類型的指針變量而沒必要報告類型失配
當內存語義不清時,也經常使用於語指原始內存
例子:一個函數能夠返回多個類型(相似於malloc())
void * f1(void *p)
{
return p;
}
void main(void)
{
int a = 100;
int * pp;
pp = &a;
printf("%d\n",*(int*)f1(pp));
}
地址自己就是個整數
指針自己也是個變量
-->其餘類型的指針轉換必須使用明確的強制類型轉換。但要注意,一種類型的指針向另外一種類型轉換時可能會產生不明確的行爲。
-->容許將int型轉換爲指針或將指針轉換爲int型,但必須使用強制類型轉換,且轉換結果是已經定義的實現,可能致使非定義的行爲。(轉換0時不須要強制轉換,由於它是NULL指針)
-->爲了與C++更好的兼容,不少C程序員捨棄了指針轉換,由於在C++中,必須使用強制類型轉換,包括void * 型
3)指針算術:能夠用於指針的算術操做只有加法和減法
-->指針與整數的加,減法
-->從一個指針中減去另外一個指針(主要目的是爲了指針偏移量)
4)指針比較:主要用語兩個或多個指針指向共同對象的狀況
初始化指針:
1)非靜態局部指針已聲明但未賦值前,其值不肯定
2)全局和靜態局部指針自動初始化爲NULL
3)賦值前使用指針,不只可能致使程序癱瘓,還有可能使OS崩潰,錯誤可謂嚴重之至!
4)【習慣用法】對於當前沒有指向合法的內存空間的指針,爲其賦值NULL
由於C保證空地址不存在對象,因此任何空指針都意味着它不指向任何對象,不該該使用它
用空指針來講明不用的指針基本上就是程序員遵照的協定(但並非C的強制規則)
例如:int * p = NULL;
* p = 1;/*ERROR!*/
/*能經過編譯,但對0賦值一般會使程序崩潰*/
jellonwu
@jintao :~/Desktop$ vim test14.c
#include <stdio.h>
void main()
{
int i ;
char a[] = "I am a student";
char b[20];
char * p1, * p2;
p1 = a; p2 = b;
//a和b是數組名是一個指針常量,在內存中固定分配,是一個地址,因此沒用&符號
for(;*p1!='\0';* p1++,* p2++)
{
*p2 = *p1;
}
//p1其實就是a,a是一個維數組,默認最後加了一個'\0'
//上面的for循環過程爲:其實就是遍歷數組a中的每個字符,p1++,其實就是a++就是內存地址+1個字節,也就是下標+1
//p2[0] = p1[0]對應就是b[0]=a[0],p1++,p2++後再來一次循環,p2[1]=p1[1]對應b[1]=a[1]....直到末尾'\0'結束
//這一過程實際上就等因而將p1整個數組指針賦給了p2指針
*p2 = '\0';//加上一個'\0'表示數組結束,若是不加'\0',由於b是20個長度,打印出來剩下的可能都是亂碼
printf("string a is:%s\n",a);
printf("string b is:");
for(i = 0;b[i]!='\0';i++)
{
printf("%c",b[i]);
}
printf("\n");
}
jellonwu
@jintao :~/Desktop$ gcc test14.c -o test14
jellonwu
@jintao :~/Desktop$ ./test14 string a is:I am a student string b is:I am a student 函數指針: void process(char *a,char *b,int(*apple)(const char *,const char *)) /*函數是經過指針調用的,apple是函數指針,傳入函數在內存中的首地址*/ 在工做中常須要向過程傳入任意函數,有時須要使用函數指針構成的數組。如在解釋程序運行時,常須要根據語句調用各類函數。此時,用函數指針構成的數組取代大型switch語句是很是方便的,由數組下標實施調用。 函數不能直接看成類型做爲參數,由於函數自己不是類型,函數指針纔是類型 int * f(int * a);-->表示函數返回值是指針類型 int (* f){int * a};--->表示函數指針 例子 #include <stdio.h> #include <string.h> void check(char * a,char * b,int (*cmp)(const char *,const char *)); void main() { char s1[80],s2[80]; int(* p)(const char*,const char *); /*函數指針*/ p = strcmp;/*將函數strcmp的地址賦給函數指針p*/ printf("輸入兩個字符串:\n"); gets(s1);/*輸入字符串1*/ get(s1);/*輸入字符串2*/ check(s1,s2,p);/*經過指針變量p傳遞函數strcmp的地址*/ } void check(char * a,char * b,int(* cmp)(const char *,const char *) ) { printf("測試是否相等:\n"); if(!(* cmp)(a,b)) { printf("結果:相等\n") } else { printf("結果:不相等\n") } } 指針動態分配(Dynamic Allocation)內存空間: 指程序在運行中取得內存空間。 全局變量在編譯時分配內存空間,非靜態局部變量使用桟區,二者在運行時使用固定長度的內存空間 1)爲了實現動態的數據結構,C動態分配函數在堆區分配內存空間。堆是系統的自由內存區,空間通常都很大 2) 核心函數malloc()分配函數和free()釋放函數,使用malloc必須使用free,這個是必須的. 3) 堆區是有限的,分配內存空間後必須檢查malloc()的返回值,確保指針使用前它是非空的。使用malloc()必定要判斷malloc返回值進行判斷,若是返回值是NULL表示申請內存失敗 4)絕對不要使用無效的指針調用free(),不然將破壞自由表 指針相關問題: 1)在某些狀況下,用const來限制指針對提供程序的安全性有重要意義。你們能夠仔細看一下微軟編寫的系統函數便會有所體會 好比拷貝字符串這個操做,被拷貝的字符串由於不能被修改,應該加上const 2)指針的錯誤難以定位,由於指針自己並無問題。問題在於,經過錯誤的指針操做時,可能引入最難排除的錯誤:程序對未知內存區進行讀或寫操做 --> 讀:最壞的狀況是取得無用數據 --> 寫:可能沖掉其餘代碼或數據 這種錯誤可能要到程序執行了至關一段時間後纔出現,所以把排錯工做引入歧途 使用指針必定要肯定指針指向內存的什麼位置 常見錯誤例子 例子:未初始化的指針 int * p; scanf("%d",p);/*ERROR*/ 運行小程序時,p中隨機地址指向安全區域(不指向程序的代碼和數據,或OS)的可能性比較大。但隨着程序的增大,p指向重要區域的機率增長,最終使程序癱瘓。 指針與數組: 一維數組名可被不嚴格地認爲是指針常量:可執行指針的操做;可按地址單元賦值和引用;可用來初始化同類型的指針變量;但不能對一維數組名複製。 char p[] = "Hello,World"; char *p = "Hello,world";//指針變量 /*兩條語句徹底等價*/ 1)指針數組:經常使用於放置指向串的變量,數組中的每個元素都是指針 在一些特殊項目中,可能須要把若干個串做爲參數傳遞給函數,但串的長度不能肯定,這時採用頂長數組顯然不合適 例子一:給頂錯誤編號後,輸出錯誤信息 void print_error(int n) { static char *error[] = {"Syntax Error\n","Variable Error\n","Disk Error\n"}; printf("%s",error[n]) } 例子二:打印命令行參數,相似於echo命令 void main(int argc,char **argv, char **env) { while(*++argv) printf("%s",*argv); } argc --->附加參數的個數,默認包含函數名,什麼參數沒有argc是1 argv --->參數具體的字符串數組 例子三:訪問命令行參數中的字符(對argv施加第二下標) void main(int argc,char *argv[],char **env) { int i,j; for(i = 0;i<argc;++i) { j = 0; while(argv[j]) { putchar(argv[j]); j++; } printf("\n"); } } 注意: --->不要像數組那樣按下標單獨複製,也不要使用*(p+n)這樣的間接引用來修改某個單元的值,這樣作均可能引發運行錯誤,由於字符串常量是在常量區,是不容許被修改的。嚴格來講,用一個普通指針指向一個常量是不對的。會產生一個cannot overt from const type * to type *的編譯錯誤。字符指針的初始化是一個特列,可用一個字符指針指向一個字符串常量,但仍然不能修改其內容,將const char *強制轉換成char *能得到正確的地址,可引用,但仍然不能修改其內容。(將const int *轉換成int *則沒法得到正確的地孩子,這是字符串常量的特殊性) --->按下標或*(p+n)這樣的簡介引用來讀取某單元的內容有時是可行的,這取決於不一樣的編譯器對字符串常量的存放方式:在常量區仍是堆區;採用何種存儲結構;是否連續(塊鏈就不連續)。而字符數組這樣引用和賦值老是可行的,由於它開闢了一塊連續的空間並賦值了內容。 2)對於數組a[],輸出a,*a,&a的值都是同樣,但咱們並不能認爲含義同樣。a有雙重含義,只不過經過printf()表現出來的是首元素的地址;*a中a去了其數組指針含義,值爲第一個元素的地址;&a中a去了其自定義函數類型含義,值爲第一個元素地址。若把a賦給數組指針變量p,則&p是p的地址,由於p並不具有數組這一層自定義數據類型含義。用數組名初始化指針或數組指針時作了自動類型轉化,取代了其指針含義。 也就是說:不要把賦值就理解爲同樣了,這個等之後學C++後就會有一個比較深刻的認識了,C++的運算符重載和自動類型轉化隱含了不少東西 3)純指針數組:即java中定義數組的方法。這樣的數組是真正的多級指針,不能用sizeof()求數組大小,但能夠實現多維動態,並且存取效率較高(無需作乘法尋址)---這個不經常使用 4)指向指針的指針:也就是二級指針,也不經常使用 char * * p 5)動態分配的數組(Dynamically ) 例子一:char *p = (char *)malloc(80); /*必須對p進行下面的測試,以免使用空指針*/ if(!p) { printf("ERROR!\n"); exit(1); } get(p); for(int i = strlen(p)-1;i>=0;i--) putchar(p); free(p); 例子二: int (*p)[10] = (int(*)[10])malloc(4*sizeof(int)); /*爲了與C++兼容,必須捨棄全部的指針轉換*/ if(!p) { printf("ERROR\n"); exit(1); } 桟自己就是內存,只不過是特殊的內存