c语言枚举变量自增报错,C_数据结构与算法(1):C语言基础
C_數據結構與算法(一):C語言基礎
致初學者的我:一切都是由淺入深。
每種語言都有每種語言的特性,基本的特性是相同的,下面依照慣例寫hello world,相關編譯后面再介紹。
// C語言用“//”和“/**/”來寫注釋,注釋、空格等不影響語義(程序的意思)的符號會在程序編譯階段會被刪除。
#include //#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數據類型 變量名 賦予變量的值
C基本數據類型(計算機中位指的是'0'或‘1’其中之一,而八位稱為一個字節,sizeof()函數可以查看數據類型具有多少個字節):
整數型
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為例,可以用程序員計算器來幫助日常換算)
二進制: 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來表示
自動類型轉換
(unsigned ) char、 short、int、long、float、double等類型之間可以互相賦值,系統會進行自動類型轉換:
short s = -1;
unsigned short us = s; // us = 65535
short s2 = 65;
char c = s2; //c = 'A'
精度損失:如果把存儲大的類型賦值給小的類型,會面臨精度丟失的問題。
變量相關信息
命名規則:變量的命名規則可以讓程序更容易理解,個人采用Camel-Case命名法,如:personalInformation
作用域:指包括此變量聲明的最小代碼塊,在這個作用域中,變量聲明后的所有代碼行都可以訪問此變量。變量在其作用域內被創建,離開其作用域時被撤銷。
{int age = 10;
...
....以上這些行都可以訪問age
}
這里不能訪問age
常量
一般我們在代碼中通過符號來代表常量,這個符號被稱為符號常量,通過#define或const定義,例如:
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 //邏輯表達式,&&是邏輯運算符
...
自增自減運算符
++、–表示對變量執行加1(減1)操作,同時參與表達式運算。例如
int a = i++ + 3; //先讓i參與表達式運算,然后將i+1
int a = ++i + 3; //先將i+1,然后讓i參與運算
位運算符
&:位與運算,將二進制每一位進行與運算,都是1則為1,如
3 & 5 = 1
0011 & 0101 = 0001
|:位或運算,將二進制每一位進行或運算,都是0則為0,如
3 | 5 = 7
0011 | 0101 = 0111
^:位異或運算,將二進制每一位進行異或運算,不相同為1,相同為0,如
3 ^ 5 = 6
0011 ^ 0101 = 0110
~:按位取反,如
~3 = -4
<
3<<1 = 6
011 --> 0110
>>:右位移,對有符號數,最左邊補原符號數,其它左邊位補0,右邊被擠掉,如
3>>1 = 1
011 -->01
賦值表達式
左值與右值:對一個變量(或表達式)來說,有兩個值與其關聯:
char[] fullName = "Rudolph_Browne";//fullName為左值,Rudolph_Browne為右值
1)數據:變量的數據值,存儲在地址中,這個值被稱之為右值(rvalue),r是read(可讀)的意思。2)地址:存儲數據值的內存地址,地址被稱之為左值(lvalue),l是location(可尋址)的意思。
“每一個表達式要么是lvalue,要么是rvalue”。左值指表達式結束之后依然存在的持久對象,而右值指表達式結束之后就不復存在的(臨時)對象。
其他表達式
sizeof運算符:返回變量或類型的字節數,如
sizeof(float); //4
sizeof(double); //8
int n;
sizeof(n); //4
條件表達式: 條件?值1:值2,條件成立時取值1,否則取值2,如:
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("格式控制字符串"[,輸出列表]);
注意:輸出列表與格式控制字符串之間有一個逗號分隔(如果輸出列表存在的話)。
格式字符串:%[標志][寬度][.精度][h | l],例如
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函數
putchar函數用來將單個字符輸出到標準輸出設備,通常是顯示器
putchar(c); //c可以是字符或整型數字
scanf函數
scanf函數用于接收輸入,以回車作為一次函數調用結束,格式為
scanf("格式控制字符串",地址列表);
scanf("%d",&a); //&a表示變量a的地址
注意:通常在用c庫函數輸出提示語句時都會適當地緩存,要輸出緩存時使用下面語句。
setbuf(stdout,NULL); //這句代碼是為了先輸出提示語句
getchar函數
getchar函數從鍵盤輸入一個字符,返回該字符的ASCII碼。例如
char c = getchar(); //等待鍵盤輸入一個字符給變量c
線性結構
線性結構的程序沒有太多的特殊性,程序按定流水線的方式被執行。
選擇結構
if-else if-else結構
if(條件1) //如果條件為真
{ //那么//當條件成立時執行這里的代碼
}else if(條件2)
{//或者//當條件2成立時執行這里的代碼
}else{//否則//執行這里的代碼
}
switch結構(注意是等值判斷:必須用int或char的整數值);注意每個case后面有一個break,忽略后面其它case。
switch(rank){
case 1:
//獎勵iphone;
break;
case 2:
//獎勵ipad;
break;
case 3:
//獎勵ipod
break;
default:
//無視
}
循環結構
while語句不斷判斷循環條件,當循環條件為真(非0)的時候,就執行循環操作,一直到循環條件變為假(0)才跳出。
while(循環條件){//循環操作
}
do-while先執行一遍操作,然后持續判斷循環條件,如果條件為真則繼續執行,直到條件為假退出。
do{//執行循環操作
}while(循環條件); //注意有分號
注意:while和do-while是在程序不知道具體停止位置而使用的,如果知道具體位置,請使用for。
for循環一般用于循環次數確定的情況,它將條件的初始化、條件判斷、條件變化抽取出來,和循環體分離。
for(參數初始化;條件判斷;參數更新){//循環操作
}
在while、do-while、for等循環語句中,break語句用來跳出循環從而執行循環后面的語句。
while(...){
....break;//下面的語句執行不到了,因為break跳出了循環
printf(...);
}//break以后,執行這個語句
continue用于跳過本次循環體剩余語句,進入下一次循環的條件判斷。
for(int i=0; i< 100; ++i){if(如果是奇數){continue;
}
sum+=i;
}
用break可以跳出一層循環,如果想跳出多重循環,可以使用goto語句。例如
#include
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?高級數據類型
數組
數組是一個變量,用來存儲相同類型的一組數據。數組四要素:變量名稱、元素、下標(從0開始)、類型,通過下標訪問數組的元素。
類型 變量名稱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]的值,實際是修改了別人的數據,可能導致程序出錯或崩潰
局部數組變量聲明后,并沒有自動初始化,此時如果2000H~200CH內存地址里面已經有值,就會被拿來使用,導致程序運行混亂。所以局部數組變量需要進行初始化,例如:
1)int a[4]={};2) #include
int a[4];
memset(a,0, 4*sizeof(int)); //以字節數為單位賦值
3)for循環將每個元素設置成0
注:static變量或全局變量自動會初始化成0。
數組地址分配的規則,用&顯示變量的地址,注意結果地址的增加字節數。
#include
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]的地址是:0x7fff1d9e5ae0a[1]的地址是:0x7fff1d9e5ae4a[2]的地址是:0x7fff1d9e5ae8a[3]的地址是:0x7fff1d9e5aec數組變量a的地址是:0x7fff1d9e5ae0
=====運行結束======
數組值求解
最大最小值:循環數組,將每個元素和max變量比較,如果元素比max大,則將元素賦值給max,循環完成以后max就是全數組的最大值。
int i, max=arr[0];
for(i=1;i
if(max
max = arr[i];
}
}
//循環完成以后,得到最大值max
均值:
int i,sum=0;
for(i=0;i
sum += arr[i];
double avg = (double)sum/len;
查找:用下標來判斷是否找到
int value = 5; //要查找的數
int i;
for(i=0;i
if(arr[i] == value){
break;
}
}
//如果i
刪除:刪除指定下標的元素:將此下標后面的元素依次往前移動一位,最后一位設置為’\0’,例如刪除下標為3的元素。
for(i=3; i
arr[i] = arr[i+1];
//依次往前移動一位,下標3的被4的覆蓋了
}
arr[len-1] = '\0'; //最后一位設置為0
刪除給定值的元素:先通過查找獲得下標,然后執行1。
int find=0;
int removeIndex;
for(removeIndex=0; removeIndex
if(arr[removeIndex] == 81){
find = 1;
break;
}
}
if(find){
for(i=removeIndex; i
arr[i] = arr[i+1];
}
arr[LEN-1] = '\0';
}else{
printf("沒有找到要刪除的元素");
}
在數組的指定位置插入一個新元素,前提是數組長度足夠。
算法:將此位置及之后的元素都往后移動一位,然后將此位置的元素設置為新元素的值。例如,在下標2處插入一個10
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;
拷貝:循環賦值或用memcpy函數。
for(i=0; 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);
二維數組a[i][j],可以把i視為行,把j視為列:
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來填補。
=====運行結束======
二維數組的存儲與矩陣的簡單表示
在邏輯上是二維的,其兩個下標在兩個方向變化,但實際的存儲是連續的,C語言中,按照行排列的方式存儲:存放完一行后,再存放下一行的元素,如:
scores[3][5],三個行如下:
scores[0] -->一班的分數:5個元素的一維數組
scores[1] -->二班的分數:5個元素的一維數組
scores[2] -->三班的分數:5個元素的一維數組
共15個元素,按照順序存儲,如:
2000H 2001H 2002H 2003H 2004H00 01 02 03 042005H 2006H 2007H 2008H 2009H10 11 12 13 142010H 2011H 2012H 2013H 2014H20 21 22 23 24
矩陣算法可以使用二維數組進行運算,如a[3][3]:
1 2 3 --> a[0]4 5 6 --> a[1]7 8 9 --> a[2]
字符串
概念:在C語言中,將字符串作為字符數組處理,以’\0’作為字符串的結束標志,’\0’占內存空間,但不計入字符串長度。
"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
printf("%d ",a[i]); //--> 最后一個是 0
字符數組的定義和賦值:
char a[] = "Hello";
char a[] = {"Hello"};
char a[] = {'H','e','l','l','o','\0'};
char a[];
a = "Hello"; //錯誤,定義后就是常量,不能再整句賦值
字符串函數(C語言提供了專門處理字符串的函數庫:#include
拷貝函數strcpy:strcpy(目標字符數組, 源字符數組),將源字符數組的內容(包括結束符\0)拷貝到目標字符數組中,函數返回目標字符數組。
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被修改
連接函數strcat(目標字符數組,源字符數組),將源字符數組的內容連接到目標字符數組后面,第一個連接過去的字符替代了目標字符數組的’\0’,函數返回目標字符數組。
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
比較函數:C語言中用strcmp函數對字符串進行比較:strcmp(字符數組1,字符數組2),返回值是:
字符數組1 = 字符數組2, 返回值=0字符數組1> 字符數組2, 返回值>0字符數組1< 字符數組2, 返回值<0
當需要判斷兩個字符串是否相等,例如判斷密碼是否為“137000”,代碼如下:
char pwd[] = "137000";
int b = strcmp(pwd,"137000"); //b == 0
長度計數函數strlen:strlen(字符數組)返回字符串的長度,計算到\0為止(不包括\0),如
char s[20] = "hello";
printf("%d",strlen(s)); //5
strlen與sizeof的區別
char s[10] = "hello";
printf("%d",strlen(s)); //5
printf("%d",sizeof(s)); //10
1)sizeof是編譯時計算,關注分配了多少空間,不關注實際存儲的數據2)strlen是運行時計算,關注存儲的數據內容,不關注分配的空間。
函數
概念:函數是程序的一個包含單元,用于完成某一特定的任務,比如一類計算、一種操作等等,使用函數對這些任務進行封裝,減少重復編碼,使程序更加模塊化,增強可讀性和可維護性。例如
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
#include //使用庫文件
int main(){
printf("%d的絕對值是%d", -3,abs(-3)); //輸出絕對值
return 0;
}
函數的執行過程
程序的執行入口是main函數,從main函數體的起始處開始執行,main函數體的所有代碼都執行完成后會自動退出程序。
遇到對其它函數的調用,則暫停當前執行,保存當前的狀態現場和返回地址,轉到被調用函數的入口地址進行執行,當遇到return語句或函數執行結束時,恢復之前保存的現場,從返回地址處開始繼續執行。例如
#include
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
}
內存和存儲類型
C語言中,程序占用的內存分為代碼區、數據區(靜態存儲區)、動態存儲區(又分為棧和堆)。
1)靜態存儲區:主要存放全局變量、靜態變量(static)、字面常量,其生存期是從程序開始到程序結束;2)動態存儲區:主要存放局部變量、參數等,在程序運行期間動態分配,其中棧和堆的使用又有所不同。
自動變量:說明符為auto,凡是函數內未加存儲類型說明的都是auto類型,例如
{
int a; ---> auto int a;
}
自動變量屬于動態存儲方式,當函數被調用時才分配存儲,函數結束即自動釋放。作用域是定義它的單元,如函數或代碼塊。
靜態變量:說明符為static,在函數中的static變量屬于靜態存儲方式,程序啟動時被分配,程序結束后釋放。作用域取決于其定義的位置,函數內定義的作用域就是函數體,函數外定義的作用域就是本文件。例如
{
static int a; //函數結束不會釋放,下次還可以繼續用
}
static的理解:具備靜態存儲特性,又具備函數的作用域。
注:全局變量或函數前面加static,表示作用域在本文件內部,不能被其它文件引用。
外部變量:說明符為extern,全局變量的默認方式,當文件中要引用另一文件中的全局變量,或者在全局變量定義之前要引用它時,可以用extern做說明。例如
file1.cpp file2.cpp
int global;
寄存器變量:說明符為register,建議將變量存儲在CPU的寄存器中,效率非常高。例如
int main(){
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);}
棧溢出:當調用函數時,當前狀態現場、返回地址要被保存到“棧”(STACK)里,等到函數調用結束時恢復現場,從返回地址繼續執行,如果遞歸函數沒有控制,一直遞歸下去,就會導致棧溢出(StackOverflowException),所以遞歸函數切記要考慮使用計數或狀態來終止遞歸循環。
遞歸的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
if(max
max = a[i];
return max;
}
結構體
結構體概述
定義:將不同類型的數據組合在一起,構造出一種新的數據類型,這種形式稱為“結構體”,聲明結構體的語法關鍵字是struct:
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),聯合的定義聲明方式和結構體(struct)類似,例如。
union test {
int i;
char c;
float f;
} a, b, c;
注意:一個聯合體變量一次只能給一個成員賦值,后賦值會覆蓋前面的賦值,只有最后賦值的成員才有意義。
#include
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)。例如:
enum weekday {sun,mon,tue,wed,thu,fri,sat};
enum weekday day1 = sun; //變量的值只能是枚舉元素之一
#include
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可以為已有類型定義新的類型名,往往可以使用我們自己定義的結構,這樣很好地具現化出真正的對象結構。如
typedef struct student{
int no;
string name;
} STUDENT; //定義新的結構體類型STUDENT
STUDENT zhangsan;
指針
概述
變量的內存結構:類型、變量名、地址、數據:
int n = 10;
2001H~2004H ----> 10
&n (int有4個字節)
指針:變量的指針就是變量的地址(內存中的首地址)
指針變量:指針變量是一種特殊的變量,它的數據是內存的一個地址(unsigned long int),通過這個內存可以找到此內存本身存儲的數據。如:
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
void類型的指針可以賦予任何類型對象的地址,如
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 )的新指針
自增自減:++和--可用于指針運算,、++、--的優先級相同,都是右結合性。例如:p++ –> 先計算*p,再將p指向p+1個類型長度的地址。
int a=10,b=20,c=30;
int *p=&b;
*p --> 20
*p++ --> 20,但此時p指向 p+1的 地址(4個字節)
指針的關系運算
當兩個指針p和q指向同一數組中的元素時,可以進行比較:
p
p>q p指向的元素在q指向的元素之后,返回1,否則0
p==q 兩者指向同一元素,返回1,否則0
p!=q 不指向同一元素,返回1,否則0
與NULL比較:
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的值被修改
}
函數指針:回調函數,又稱callback,是一種事件驅動的編程模式,將函數指針變量作為參數傳遞給另外一個函數,函數里面當某些事件滿足時會調用此函數指針變量指向的函數,例如。
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
if(i%7==0) p(i);
}
}
這篇文章屬于學習筆記,學習的網站為itbegin.com,希望讀CS的朋友能有更多的交流。:)
1樓chons學習了Re: Rudolph_Browne@chons,有點混亂,謝謝。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的c语言枚举变量自增报错,C_数据结构与算法(1):C语言基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 石油大c语言答案,中国石油大学C语言答案
- 下一篇: c语言编程后总有一个错误,C语言编程,之