小甲魚C語言《帶你學C帶你飛》學習筆記(1)
視頻鏈接https://www.bilibili.com/video/av27744141
由于篇幅限制,本筆記分兩部分:
小甲魚C語言《帶你學C帶你飛》學習筆記(1)-本文
小甲魚C語言《帶你學C帶你飛》學習筆記(2)-傳送門
本筆記是本人根據(jù)小甲魚視頻講課內容記錄,一些程序做了小修改。根據(jù)自身補充了一部分拓展內容。如有錯誤,歡迎一起討論????????——正能量的康sir?
喜歡我的分享,不放點個關注、點個贊、投個幣,謝謝:)? ?????????????
P2第一個程序
編譯型:c語言——》匯編——》機器語言——》CPU執(zhí)行
解釋型:java——》字節(jié)碼——》解釋器——》CPU執(zhí)行
???????????????????????????????????????
#include<stdio.h>
int main(){
printf("hello world!");
return 0;
}
?
linux環(huán)境下
編輯:vi test.c
編譯:gcc test.c -o test
執(zhí)行:./test
?
windows環(huán)境下
Dev C++
CodeBlocks
P3打印
printf("要打印的內容");
printf格式化輸出函數(shù),print打印,f即format格式化
\n換行
每行寫不完行尾用\,下一行為上一行的延續(xù)。函數(shù)、關鍵字中間也可以用,注意下行開頭如果有空格、縮進也會被解釋,會導致識別不了。
轉義字符
P4變量
確定目標并提供存放的空間
?
命名規(guī)則:
變量名只能是英文字母(A-Z,a-z)和數(shù)字(0-9)或者下劃線(_)組成
第一個字母必須是字母或者下劃線開頭(不能是數(shù)字)
變量名區(qū)分大小寫
不能使用關鍵字
32個關健字
1999年c99標準增加5個
2011年c11標準增加7個
?
數(shù)據(jù)類型
char字符型,占用1字節(jié)
int整型,通常反映了所用機器中整數(shù)的自然長度
float單精度浮點型
double雙精度浮點型
?
聲明變量語法
數(shù)據(jù)類型 變量名
如int a
P5常量和宏定義
常量:
整數(shù)常量
實型常量 小數(shù)等
字符常量 包括普通字符、轉義字符
字符串常量
符號常量 使用之前必須先定義
?
定義符號常量 宏定義
#define標識符 常量
?
示例
#include<stdio.h>
#define NAME "小甲魚"
#define YEAR 2010
int main (){
printf("%s成立于%d年",NAME,YEAR);
}
?
標識符:和關鍵字命名規(guī)則一樣
?
字符串常量 使用null結尾,轉義符是\0
P6數(shù)據(jù)類型
1.基本類型
整數(shù)型????? int、short int、long int、long long int???????? (short int<=int<=long int<=long long int)
浮點型????? float、double、long double
字符型????? char
布爾型????? _Bool
枚舉型????? enum
2.指針類型
3.構造類型
數(shù)組型
結構型
聯(lián)合性
4.空類型
?
sizeof運算符
用于獲取數(shù)據(jù)類型或表達式的長度
sizeof(變量或對象),可以不加括號,空格隔開。如sizeof(a)或sizeof a
sizeof(類型),如sizeof(int)
?
signed和unsigned類型限定符 限定char類型和任何整數(shù)類型的取值范圍
signed帶符號位,可以存放負數(shù)
unsigned不帶符號位,只能存放正數(shù)和0
[signed] short [int]
unsigned short [int]
[signed] int
unsigned int
[signed] long [int]
unsigned long [int]
[signed] long long [int]
unsigned long long [int]
示例
#include<stdio.h>
int main(){
short i;
unsigned short j;
i=-1;
j=-1;
printf("%d\n",i);
printf("%u\n",j);
}
輸出
-1
65535
P7取值范圍
比特位
CPU能夠讀懂的最小單位——比特位,單位bit縮寫b
字節(jié)
內存機構的最小尋址單位——字節(jié),單位Byte縮寫B(tài)
1Byte=8bit
1個字節(jié)等于8比特
1字節(jié)最大:二進制11111111 十進制255 十六進制FF
?
n個連續(xù)的1等于2的n次方減一
#include<stdio.h>
#include<math.h>
int main(){
int result = pow(2,32)-1;
printf("result=%d\n",result);
return 0;
}
?
符號位
存放signed類型的存儲單元中,左邊第一位表示符號位。如果該位為0表示該整數(shù)是一個正數(shù);
如果該位為1,表示該整數(shù)是一個負數(shù)。
一個32位的整數(shù)變量,除去左邊第一個符號位,剩下的表示值得只有31個比特位
事實上計算機是用補碼的形式來存放整數(shù)的值。
正數(shù)的補碼是該數(shù)的二進制形式
負數(shù)的補碼:
(1)先取得該數(shù)的絕對值的二進制形式 -7? 絕對值7的二進制10000111
(2)將第一步的值按位取反????????????????????????????????????????????????????????? ?11111000
(3)最后將第二步的值加1?????????????????????????????????????????????????????????? ?11111001
補碼的最大值127最小值-128
01111111????????? 127
…
00000001????????? 1
00000000????????? 0
11111111????????? -1
11111110????????? -2
10000000????????? -128
?
基本數(shù)據(jù)類型的取值范圍
?
P8字符和字符串
ASCII字符表
字符串:
聲明字符串???????????????????? char變量名[數(shù)量];
賦值??????????????????????????????? 變量名[索引號]='字符';
聲明+賦值 定義字符串 char name[5]={'a','b','c'};
?
#include<stdio.h>
int main(){
char a[9]={'b','i','l','i','b','i','l','i','\0'};
printf("%s\n",a);
return 0;
}
?
#include<stdio.h>
int main(){
//char a[8]={'b','i','l','i','b','i','l','i'};//錯誤 指定的數(shù)組長度不夠時
//char a[]={'b','i','l','i','b','i','l','i'};//錯誤 不指定長度,每個字符用單引號時
//char a[9]={'b','i','l','i','b','i','l','i'};//正確 預留一個位置
//char a[]={'b','i','l','i','b','i','l','i','\0'};//正確 人工添加
char a[]={"bilibili"};//正確 字符串復制
printf("%s\n",a);
return 0;
}
?
拓展
'\0'是由C編譯系統(tǒng)自動加上的。所以在用字符串賦初值時一般無須指定數(shù)組的長度, 而由系統(tǒng)自行處理。
把字符數(shù)組str1中的字符串拷貝到字符數(shù)組str2中。串結束標志'\0'也一同拷貝。
個案
1. 當數(shù)組長度不夠。假設我們指定了數(shù)組長度,如:u8 str1[13]={"cxjr.21ic.org"};
由于字符組str1的長度為13,所以后面的信息會丟失,即'\0'丟失。
2. 如果在給數(shù)組賦值時,把每個字符單獨用引號括起來。也會丟失'\0'。如:
u8 str1[]={'c','x','j','r','.','2','1','i','c','.','o','r','g'};
如果希望數(shù)組以'\0'結束,則可以寫成以下三者之一:
u8 str1[]={"cxjr.21ic.org"}; //字符串賦值
u8 str1[]={'c','x','j','r','.','2','1','i','c','.','o','r','g','\0'}; //人工添加
u8 str1[14]={'c','x','j','r','.','2','1','i','c','.','o','r','g'}; //故意給數(shù)組預留一個空位
P9算術運算符
求余%后面必須是整數(shù)
?
?
?
?
?
?
目
1+2?????????? +運算符,1、2兩個操作數(shù)(雙目)
?
表達式
用運算符和括號將操作數(shù)連接起來的式子
1+1
a+b
'a'+'b'
a+'b'+pow(a,b)*3/4+5
?
運算符的優(yōu)先性和結合性
?
類型轉換
1+2.0轉化為1.0+2.0
強制轉換
(int)1.2注意不會四舍五入
?
P10關系運算符和邏輯運算符
關系運算符 ?表示 ?比較:
優(yōu)先級高:<、<=、>、>=
優(yōu)先級低:==、!=
?
用關系運算符將兩邊的變量、數(shù)據(jù)、表達式連接起來,稱為關系表達式。
1<2
a>b
a<1+b
'a'+'b'<='c'
(a=3)>(b=5)
結果是布爾型,c語言中正確1,錯誤0
?
邏輯運算符
非???? !???? 優(yōu)先級高?? !a
與???? &&?? 優(yōu)先級中?? a&&b
或???? ||?????? 優(yōu)先級低?? a||b
?
短路求值
又稱最小化求值,是一種邏輯運算符的求值策略。只有當?shù)谝粋€運算數(shù)的值無法確定邏輯運算的結果,才對第二個運算數(shù)進行求值。
C語言對于邏輯與和邏輯或采用短路求值的方式。
#include<stdio.h>
int main(){
int a,b=3;
(a=0)&&(b=5);?? // 因為a為0,0表示假,能確定邏輯與的值,不會再對第二個運算數(shù)求值
printf("a=%d,b=%d\n",a,b);
(a=1)||(b=5);????? //因為a為1,1表示真,能確定邏輯或的值,不會再對第二個運算數(shù)求值
printf("a=%d,b=%d\n",a,b);
return 0;
}
結果
a=0,b=3
a=1,b=3
?
P11 if語句
語句(1):
if(表達式)
{
邏輯值為真所執(zhí)行的語句、程序塊
}
?
語句(2):
if(表達式)
{
邏輯值為真所執(zhí)行的語句、程序塊
}
else
{
邏輯值為假所執(zhí)行的語句、程序塊
}
?
語句(3):
if(表達式1) {……}
else if(表達式2){……}
else if(表達式3){……}
……
else if(表達式n){……}
else {……}
?
P12 switch語句和分支嵌套
?
switch(整型或字符型)
{
case常量表達式1:程序塊1???? break;
case常量表達式2:程序塊2???? break;
case常量表達式3:程序塊3???? break
…….
case常量表達式n:程序塊n???? break;
default:語句或程序塊n+1? 缺省一個break;可不寫
}
?
懸掛else
if(a==1)
if(b==2)
printf("a等于1,b等于2");
else{lprintf("a不等于1")}
這樣即使a等于1,b不等于2,也會輸出a不等于1。因為else就近if
正確做法加{}
if(a==1)
{
if(b==2)
printf("a等于1,b等于2");
}
else{lprintf("a不等于1")}
?
等于號帶來的問題
?
P13while語句和dowhile語句
?
入口條件循環(huán)
while(表達式){
循環(huán)體
}
?
出口條件循環(huán)
do
{
循環(huán)體
}
while(表達式);
?
?
P14for語句和循環(huán)嵌套
for(初始化表達式;循環(huán)條件表達式;循環(huán)調整表達式)
循環(huán)體
?
for(;;) 都可以省略 但是分號不能省
?
C99、C11允許在for語句的表達式1中定義變量?????????? 表達式1、3中可以用逗號運算符寫多個語句
for(int i=0;i<100;i++)
P15break語句和continue語句
break跳出 跳出循環(huán)或switch。不能用于循環(huán)語句和switch語句之外的任何其他語句中。break 只能跳出一層循環(huán)。當有多層循環(huán)嵌套的時候,break只能跳出“包裹”它的最里面的那一層循環(huán),無法一次跳出所有循環(huán)。同樣,在多層 switch 嵌套的程序中,break 也只能跳出其所在的距離它最近的 switch。但多層 switch 嵌套實在是少見。
?
continue 結束本次循環(huán),即跳過循環(huán)體中下面尚未執(zhí)行的語句,然后進行下一次是否執(zhí)行循環(huán)的判定。不能用于switch
P16拾遺
賦值運算符左邊必須是一個Lvalue,變量名就是Lvalue
int a;
a=5
lvalue 識別和定位存儲數(shù)值的標志符
?
復合的賦值運算符
+=
-=
*=
/=
%=
?
自增自減
i++
i—
i++、++i前后的區(qū)別
j=++i先i加1,再把加1后的值給j
j=i++ 先把i的值給j,i再加1
?
逗號運算符
優(yōu)先級最低
表達式1,表達式,。。。,表達式n
運算過程從左到右
作為一個整體,它的值為最后一個表達式(表達式n)的值
a=(b=3,(c=b+4)+5)
先將b賦值為3,c賦值為b+4的和7,c的值加5,最后賦值給a,a的值12
常見:
1.??????? 多個變量初始化
for表達式1、3
?
三目運算符
表達式1?表達式1:表達式3;
表達式1正確返回表達式2,錯誤返回表達式3
?
goto歷史遺留 不常用
goto A;
A:????? printf("123");
?
注釋
//單行注釋
?
/*多行注釋
多行注釋*/
?
P17數(shù)組
數(shù)組定義:
類型 數(shù)組名[元素個數(shù)]
int a[10];
int N=10;int a[N];
char b[24];
double c[3];
?
數(shù)組不能動態(tài)定義。[]中不能是一個變量,但是這個變量的值是固定的值的話可以
?
訪問數(shù)組中的元素
數(shù)組名[下標]
第一個元素的下標是0,不是1
?
數(shù)組的初始化
1.所有元素賦值為0(只有0可以)
int a[10]={0};?????????????? //只是將第一個元素賦值0,后面會自動初始化為0
2.賦值不同的值用逗號隔開
int a[10]={1,2,3,4,5,6,7,8,9,0};
3.只給一部分元素賦值,未被賦值的元素自動初始化為0.
int a[10]={1,2,3,4,5}
4.只給出各個元素的值,不指定長度,由編譯器自動判斷
int a[]={1,2,3,4,5};
5.C99新特性:指定初始化的元素。未被賦值的元素自動初始化為0
int a[10]={[3]=110,[5]=120,[8]=114};
P18啪啪啪
數(shù)組不能動態(tài)定義。[]中不能是一個變量,但是這個變量的值是固定的值的話可以
?
P19字符串處理函數(shù)
字符數(shù)組
char str1[10]={'F','I','S','H','\0'};
char str2[]={'F','I','S','H','\0'};不指定長度
char str3[]={"FISH"};使用字符串常量初始化字符數(shù)組,可以省略大括號
char str4[]="FISH";
?
字符串處理函數(shù) 在<string.h>
strcat連接字符串
strcmp比較字符串
strcpy拷貝字符串strcpy(str1,str2)把str2復制給str1.注意\0也會復制。要保證str2長度小于str1
strlen獲取字符串長度 數(shù)組元素的個數(shù)
strncat連接字符串(受限)
strncmp比較字符串(受限)
strncpy拷貝字符串(受限)strncpy(str1,str2,元素個數(shù)n) 把str2的n個元素復制給str1,要自己加\0 str1[n]='\0'
?
?
P20二維數(shù)組
定義:
類型 數(shù)組名[行數(shù)][列數(shù)]
訪問:
數(shù)組名[行下標][列下標]
a[0][0]第一行第一列
?
二維數(shù)組初始化
int a[2][3]={1,2,3,4,5,6};
int a[2][3]={{1,2,3},{4,5,6}};
int a[2][3]={{1},{4}}對每行第一個賦值,其余自動為0
int a[2][3]={0}整個二維數(shù)組初始化為0
int a[][3]={1,2,3,4,5,6} 行數(shù)可不寫,自動判斷
C99新特性 指定初始化的元素。
?
?
P21指針
通過變量名訪問變量
指針和指針變量
類型名 *指針變量名
char *pa;定義一個指向字符型的指針變量
int *pb;定義一個指向整型的指針變量
?
獲取某個變量的地址,可以使用取地址運算符&
char *pa=&a;
int *pb=&f;
?
如果需要訪問指針變量指向的數(shù)據(jù),可以使用取值運算符*
printf("%c,%d",*pa,*pb)
?
避免訪問未初始化的指針(野指針)
#include<stdio.h>
int main(){
int *a;??????? //要初始化才對int *pa,a;*pa=&a;*pa=123??????????????????????????? 但是字符串可以int *ps="haha"
*a=123;
return 0;
}
P22指針和數(shù)組
數(shù)組名是數(shù)組第一個元素的地址
?
指向數(shù)組的指針
char *p;
p=a;或p=&a[0];
?
對指針加減運算相當于指向距離指針所在位置向前或向后第n個元素
對比標準的下標訪問數(shù)組元素,使用指針進行間接訪問的方法叫作指針法
p+1并不是將地址加1,而是指向數(shù)組的下一個元素。(根據(jù)定義的類型長度加1個單位的int、char。。。)
?
P23指針數(shù)組和數(shù)組指針
int *p1[5]?? 指針數(shù)組 是數(shù)組 變量類型指針int *
int (*p2)[5] 數(shù)組指針 是指針 指向一個數(shù)組
?
定義數(shù)組指針int (*p)[3]=array
P24指針和二維數(shù)組
數(shù)組array[4][5]
*(array+1)==array[1]指向數(shù)組第二行第一個元素
*(array+1)+3==&array[1][3] 指向數(shù)組第二行第4個元素
結論
*(array+i)==array[i]
*(*(array+i)+j)==array[i][j]
*(*(*(array+i)+j)+k)==array[i][j][k]
?
P25void指針和NULL指針
void指針 稱為通用指針,可以指向任意類型的數(shù)據(jù)。也就是說任何類型的指針都可以賦值給void指針。
int a=1024;
int *pi=&a;
void *pv;
pv=pi;
?
NULL指針 用于指針和對象
#define NULL ((void *)0)
好習慣 不清楚將指針初始化為什么地址時,將她初始化為NULL
NULL不是NUL,NUL是空字符\0
P26指向指針的指針
int num=520;
int *p=&num;
int **pp=&p;兩次解引用
好處:
避免重復分配內存
只需進行一次修改
?
P27常量和指針
常量:1,'a',2.5
或者
#define N 10
或者使用const修飾
const int price = 520
?
指向常量的指針
指針可以修改為指向不同的變量
指針可以修改為指向不同的變量
可以通過解引用來讀取指針指向的數(shù)據(jù)
不可以通過解引用來修改指針指向的數(shù)據(jù)
?
常量指針
指向非常量的常量指針:
指針自身不可被修飾
指針指向的值可以被修改
指向常量的常量指針:
指針自身不可以被修改
指針指向的值也不可以被修改
?
指向"指向常量的常量指針"的指針
P28函數(shù)
函數(shù)的定義;
類型 函數(shù)名(參數(shù)列表)
{
???????? 函數(shù)體
}
?
函數(shù)的聲明
寫在主函數(shù)mian前面
或者
在mian方法后寫函數(shù),在前面寫函數(shù)聲明
void f1(int);
int main(){
int x=1;
f1(x);
}
void f1(int x){
…
}
?
?
P29參數(shù)和指針
形參 形式參數(shù)
實參 實際參數(shù)
?
傳值和傳址
?
可變參數(shù)
?
P30指針函數(shù)和函數(shù)指針
指針函數(shù)int *f()
char *f1(char c){
return "字符串"
}
字符數(shù)組只要指名開頭地址。結尾是\0自動判斷
?
不要 返回局部變量的指針
?
?
函數(shù)指針int (*p)()
int square(int num){
return num*num;
}
int main(){
int num=5;
int (*fp)(int);
fp=square;函數(shù)名相當于地址 等價于fp=&square
printf("%d",(*fp)(num))
}
?
P31局部變量和全局變量
不同函數(shù)的變量無法相互訪問
?
在函數(shù)里面定義的?局部變量
在函數(shù)外邊定義的?全局變量
?
如果不對全局變量進行初始化,它會自動初始化為0
如果在函數(shù)內部存在一個與全局變量同名的局部變量,并不會報錯,而是在函數(shù)中屏蔽全局變量(全局變量不起作用)
?
extern關鍵字?告訴程序變量在后面定義了,不要報錯
?
不要大量使用全局變量,因為
1、全局變量從被定義開始,直到程序退出才能被釋放,會占用更多的內存
2、污染命名空間,雖然局部變量會屏蔽全局變量,但這樣一來也會降低程序的可讀性,人們往往很難一下子判斷出每個變量的含義和作用范圍
?
?
P32作用域和鏈接屬性
變量的作用范圍?作用域
?
c語言編譯器可以確認4種不同類型的作用域:
代碼塊作用域block scope
文件作用域file scope
原型作用域prototype scoope
函數(shù)作用域function scope
?
代碼塊作用域
在代碼塊中定義的變量,具有代碼塊作用域。從變量定義的位置開始,到標志該代碼結束的右大括號}處
盡管函數(shù)的形參不在大括號內定義,但其同樣具有代碼塊作用域,隸屬于包含函數(shù)體的代碼塊
?
文件作用域
任何在代碼塊之外聲明的標識符都具有文件作用域。從聲明的位置開始,到文件的結尾處
函數(shù)名也具有文件作用域,因為函數(shù)名本身也在代碼塊之外
?
原型作用域
原型作用域只適用于那些在函數(shù)原型中聲明的參數(shù)名。函數(shù)在聲明的時候可以不寫參數(shù)的名字(但參數(shù)的類型是必須要寫上的),其實函數(shù)原型的參數(shù)名還可以隨便寫一個名字,不必與形式參數(shù)相匹配(當然,這樣做毫無意義)。
void func(int a,int b,int c);
void func(int d,int e,int f){
…
}
?
函數(shù)作用域
函數(shù)作用域只適用于goto語句的標簽,作用將goto語句的標簽限制在同一個函數(shù)內部,以及防止出現(xiàn)重名標簽。
?
定義和聲明
當一個變量被定義的時候,編譯器為變量申請內存空間并填充一些值
當一個變量被聲明的時候,編譯器就知道該變量被定義在其他地方
聲明通知編譯器該變量名及相關的類型已存在,不需要再為此申請內存空間。
局部變量既是定義又是聲明
定義只能來一次,否則就叫做重復定義某個同名變量;而聲明可以有很多次
?
鏈接屬性
.c編譯.o,.o鏈接lib庫文件
external外部的?多個文件中聲明的同名標識符表示同一個實體
internal內部的?單個文件中聲明的同名標識符表示同一個實體static
none無?聲明的同名標識符被當做獨立不同的實體
只有具有文件作用域的標識符才能擁有external或internal的鏈接屬性,其它作用域的標識符都是none屬性
默認情況下,具備文件作用域的標識符擁有external屬性。也就是說該標識符允許跨文件訪問。對于external屬性的標識符,無論在不同文件中聲明多少次,表示的都是同一個實體
?
?
P33生存期和存儲類型
c語言的變量擁有兩種生存期
靜態(tài)存儲期
自動存儲期
?
具有文件作用域的變量屬于靜態(tài)存儲期,函數(shù)也屬于靜態(tài)存儲期,屬于靜態(tài)存儲期的變量在程序執(zhí)行期間將一直占據(jù)存儲空間,直到程序關閉才釋放。
?
具有代碼塊作用域的變量一般情況下屬于自動存儲期。屬于自動存儲期的變量在代碼塊結束時將自動釋放存儲空間。
?
生存期
?
存儲類型
指存儲變量值的內存類型
auto
register
static
extern
typedef
?
自動變量auto
在代碼塊中聲明的變量默認的存儲類型就是自動變量,使用關鍵字auto來描述,可省略
#include<stdio.h>
int main(){
auto int I,j,k;
return 0;
}
?
寄存器變量register
將一個變量聲明為寄存器變量,那么該變量就有可能被存放于CPU的寄存器中。
寄存器變量和自動變量在很多方面都是一樣的,都擁有代碼塊作用域、自動存儲期和空連接屬性
不過將變量聲明為寄存器變量,那么你就沒有辦法通過取址運算符獲得該變量的地址。
?
靜態(tài)局部變量static
static使得局部變量具有靜態(tài)存儲期,所以它的生存期與全局變量一樣,直到程序結束才釋放
?
作用于文件作用域的static和extern,static關鍵字使得默認具有external鏈接屬性的標識符變成internal鏈接屬性,而extern關鍵字是用于告訴編譯器這個變量或函數(shù)在別的地方已經定義過,先去別的地方找找,不要急著報錯。
?
typedef
?
P34遞歸
漢諾塔
謝爾賓斯基三角形
目錄樹的索引
女神的自拍
?
函數(shù)調用本身
?
遞歸必須有結束條件,否則程序將崩潰
#include<stdio.h>
void f1(){
static int count =10;
printf("HI\n");
if(--count) f1();
}
int main(){
f1();
return 0;
}
?
?
遞歸求階乘
#include<stdio.h>
long fact(int num)??????????????????????? //循環(huán)方法
{
???????????????????????????????????????????????????? long result;
???????????????????????????????????????????????????? for(result=1;num>1;num--)
???????????????????????????????????????????????????? {
???????????????????????????????????????????????????? result*=num;
???????????????????????????????????????????????????? }
???????????????????????????????????????????????????? return result;
}
long fact1(int num)????????????????????? //遞歸方法
{
???????????????????????????????????????????????????? long result;
???????????????????????????????????????????????????? if (num>0)
???????????????????????????????????????????????????? {
???????????????????????????????????????????????????????????? result=num*fact1(num-1);
???????????????????????????????????????????????????? }
???????????????????????????????????????????????????? else
???????????????????????????????????????????????????? {
???????????????????????????????????????????????????????????? result=1;
???????????????????????????????????????????????????? }
???????????????????????????????????????????????????? return result;
}
int main(void)
{
int num;
printf("請輸入一個整數(shù):");
scanf("%d",&num);
printf("%d的階乘是:%d",num,fact1(num));
}???????????????????????????????????????????????????
?
實現(xiàn)遞歸滿足兩個條件
調用函數(shù)本身
設置了正確的結束條件
?
普通程序員用迭代,天才程序員用遞歸,但我們寧可做普通程序員
?
?
P35漢諾塔
?遞歸求解漢諾塔
簡單分為三個步奏:
1、將前63個盤子從X移動到Y上
2、將最底下的第64塊盤子從X移動到Z上
3、將Y上的63個盤子移動到Z上
而1、3都能在此分解為類似的三步
1分為
1.1、將前62個盤子從X移動到Z上
1.2、將最底下的第63塊盤子從X移動到Y上
1.3、將Z上的62個盤子移動到Y上
3分為
3.1、將前62個盤子從Y移動到X上
3.2、將最底下的第64塊盤子從Y移動到Z上
3.3、將X上的62個盤子移動到Y上
.。。。
#include<stdio.h>
void hanoi(int n,char x,char y,char z) //將x上的n個盤子借助y移動到z上
{
???????? if(n==1)
???????? {
????????????????? printf("%c-->%c\n",x,z);
???????? }
???????? else
???????? {
????????????????? hanoi(n-1,x,z,y);
????????????????? printf("%c-->%c\n",x,z);
????????????????? hanoi(n-1,y,x,z);
???????? }
}
int main()
{
???????? int n;
???????? printf("請輸入漢諾塔的層數(shù):");
???????? scanf("%d",&n);
???????? hanoi(n,'X','Y','Z');
???????? return 0;
}
?
P36快速排序
分治法
大事化小,小事化了
?
快速排序
基本思想:通過一趟排序將待排序數(shù)據(jù)分割成獨立的兩部分,其中一部分所有元素均比另一部分的元素小,然后分別對兩部分繼續(xù)進行排序,重復上述步驟直到排序完成。
?
?
P37動態(tài)內存管理1
4個庫函數(shù)stdlib.h
malloc申請動態(tài)內存空間
free釋放動態(tài)內存空間
calloc申請并初始化一系列內存空間
realloc重新分配內存空間
?
malloc
函數(shù)原型void *malloc(size_t size);
向系統(tǒng)中申請分配size個字節(jié)的內存空間,并返回一個指向這塊空間的指針。
函數(shù)調用成功,返回一個指向申請的內存空間的指針,由于返回的類型是void指針,所以它可以被轉換成任何類型的數(shù)據(jù);如果函數(shù)調用失敗,返回值是NULL。另外,如果size參數(shù)設置為0,返回值也可能為NULL,但并不意味著函數(shù)調用失敗。
#include<stdio.h>
#include<stdlib.h>
int main()
{
???????? int *ptr;
???????? ptr=(int *)malloc(sizeof(int));
???????? if(ptr==NULL)
???????? {
????????????????? printf("分配內存失??!\n");
????????????????? exit(1);
???????? }
???????? printf("請輸入一個整數(shù):\n");
???????? scanf("%d",ptr);
???????? printf("請輸入的整數(shù)是:%d\n",*ptr);
???????? return 0;
}
?
free
函數(shù)原型:void free(void *ptr);
free函數(shù)釋放ptr參數(shù)指向的內存空間。該內存空間必須是由malloc、calloc或realloc函數(shù)申請的。否則,該函數(shù)將導致未定義行為。如果ptr參數(shù)是NULL,則不執(zhí)行任何操作。注意:該函數(shù)并不會修改ptr的值,所以調用后仍然指向原來的地方(變?yōu)榉欠臻g)。
在以上程序中添加
free(ptr);
printf("請輸入的整數(shù)是:%d\n",*ptr);//此時輸出的值是無意義的
一個吃滿內存的死循環(huán)
#include<stdio.h>
#include<stdlib.h>
int main()
{
???????? while(1)
???????? {
????????????????? malloc(1024);
???????? }
???????? return 0;
}
?
內存泄露
如果申請的動態(tài)內存沒有及時釋放會怎么樣?
c語言沒有垃圾回收機制
導致內存泄露主要兩種情況:
隱式內存泄露(用完內存塊沒有及時使用free函數(shù)釋放)
丟失內存快地址
#include<stdio.h>
#include<stdlib.h>
int main()
{
???????? int *ptr;
???????? int num=123;
???????? ptr=(int *)malloc(sizeof(int));
???????? if(ptr==NULL)
???????? {
????????????????? printf("分配內存失??!\n");
????????????????? exit(1);
???????? }
???????? printf("請輸入一個整數(shù):\n");
???????? scanf("%d",ptr);
???????? printf("請輸入的整數(shù)是:%d\n",*ptr);
???????? ptr=#//ptr又指向了別處,原來malloc申請的內存塊丟失,free不能釋放原來的動態(tài)內存。
???????? free(ptr);//另一個錯誤,free不能釋放這里不是動態(tài)內存的ptr
???????? return 0;
}
P38動態(tài)內存管理2
malloc還可以申請一塊任意尺寸的內存空間
#include<stdio.h>
#include<stdlib.h>
int main()
{
???????? int *ptr=NULL;
???????? int num,i;
???????? printf("請輸入待錄入的整數(shù)的個數(shù):");
???????? scanf("%d",&num);
???????? ptr=(int *)malloc(num*sizeof(int));
???????? for(i=0;i<num;i++)
???????? {
????????????????? printf("請錄入第%d個數(shù):\n",i+1);
????????????????? scanf("%d",&ptr[i]);
???????? }
???????? printf("你錄入的整數(shù)是:\n");
???????? for(i=0;i<num;i++)
???????? {
????????????????? printf("%d ",ptr[i]);
???????? }
???????? free(ptr);
???????? return 0;
}
?
初始化內存空間
以mem開頭的函數(shù)被編入字符串標準庫,函數(shù)的聲明包含在string.h這個頭文件中
memset使用一個常量字節(jié)填充內存空間
memcpy?拷貝內存空間
memmove?拷貝內存空間
memcmp比較內存空間
?
memset示例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 10//沒分號
int main()
{
???????? int *ptr=NULL;
???????? int i;
???????? ptr = (int *)malloc(N*sizeof(int));
???????? if(ptr==NULL)
???????? {
????????????????? exit(1);
???????? }
???????? memset(ptr,0,N*sizeof(int));
???????? for(i=0;i<N;i++)
???????? {
????????????????? printf("%d ",ptr[i]);
???????? }
???????? free(ptr);
???????? return 0;
}
?
calloc
函數(shù)原型void *calloc(size_t nmemb,size_t size);
calloc函數(shù)在內存中動態(tài)地申請nmemb個長度為size的連續(xù)內存空間(即申請的總空間尺寸為nmemb *size),這些內存空間全部被初始化為0。
calloc函數(shù)與malloc函數(shù)的一個重要區(qū)別是:
calloc函數(shù)在申請完內存后,自動初始化該內存空間為零;malloc函數(shù)不進行初始化操作,里面數(shù)據(jù)是隨機的。
所以下面兩種寫法是等價的:
calloc()分配內存空間并初始化
int *ptr=(int *)calloc(8,sizeof(int));
malloc()分配內存空間并用memset()初始化
?int *ptr=(int *)malloc(8*sizeof()int);
memset(ptr,0,8*sizeof(int));
?
memcpy示例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
???????? int *ptr1=NULL;
???????? int *ptr2=NULL;
???????? ptr1=(int *)malloc(10 *sizeof(int));
???????? ptr2=(int *)malloc(20 *sizeof(int));
???????? memcpy(ptr2,ptr1,10);
???????? free(ptr1);
???????? //略...對ptr2進行若干操作
???????? free(ptr2);
???????? return 0;
}
?
realloc
函數(shù)原型void *realloc(void *ptr,size_t size);
以下幾點需要注意;
realloc函數(shù)修改ptr指向的內存空間大小為size字節(jié)
如果新分配的內存空間比原來的大,則舊內存塊的數(shù)據(jù)不會發(fā)生改變;如果新的內存空間大小小于舊的內存空間,可能會導致數(shù)據(jù)丟失,慎用!
如果ptr參數(shù)為NULL,那么調用該函數(shù)就相當于調用malloc(size)
如果size參數(shù)為0,并且ptr參數(shù)不為NULL,那么調用該函數(shù)就相當于調用free(ptr)
除非ptr參數(shù)為NULL,否則ptr的值必須由先前調用malloc、calloc或realloc函數(shù)返回
?
?
P39C語言的內存布局
C語言的內存布局規(guī)律
根據(jù)內存地址從低到高分別劃分為:
代碼段(Text segment)
數(shù)據(jù)段(Initialized data segment)
BSS段(Uninitialized data segment)
棧(Stack)
堆(Heap)
?
代碼段
代碼段(Text segment)通常是指用來存放程序執(zhí)行代碼的一塊內存區(qū)域。這部分區(qū)域的大小在程序運行前就已經確定,并且內存區(qū)域通常屬于只讀。在代碼段中,也有可能包含一些只讀的常數(shù)變量,例如字符串常量等。
?
數(shù)據(jù)段
數(shù)據(jù)段(Initialized data segment)通常用來存放已經初始化的全局變量和局部靜態(tài)變量。
?
BSS段
BSS段(Bss segment/Uninitialized data segment)通常是指用來存放程序中未初始化的全局變量的一塊內存區(qū)域。BSS是英文Block Started by Symbol的簡稱,這個區(qū)段中的數(shù)據(jù)在程序運行前將被自動初始化為數(shù)字0。
?
堆
堆是用于存放進程運行中被動態(tài)分配的內存段,它的大小并不固定,可動態(tài)擴展或縮小。當進程調用malloc等函數(shù)分配內存時,新分配的內存就被動態(tài)添加到堆上;當利用free等函數(shù)釋放內存時,被釋放的內存從堆中被剔除。
?
棧
大家平時可能經常聽到堆棧這個詞,一般指的就是這個棧。棧是函數(shù)執(zhí)行的內存區(qū)域,通常和堆共享同一片區(qū)域。
?
堆和棧的區(qū)別
申請方式:
堆由程序員手動申請
棧由系統(tǒng)自動分配
釋放方式:
堆由程序員手動釋放
棧由系統(tǒng)自動釋放
生存周期:
堆的生存周期由動態(tài)申請到程序員主動釋放為止,不同函數(shù)之間均可自由訪問
棧的生存周期由函數(shù)調用開始到函數(shù)返回時結束,函數(shù)之間的局部變量不能互相訪問
發(fā)展方向:
堆和其它區(qū)段一樣,都是從低地址向高地址發(fā)展
棧則相反,是由高地址向低地址發(fā)展
P40高級宏定義
宏定義
文件包含
條件編譯
?
不帶參數(shù)的宏定義
#define PI 3.14
為了和普通的變量進行區(qū)分,宏的名字通常我們約定是全部由大寫字母組成
宏定義只是簡單的進行替換,并且由于預處理是在編譯之前進行,而編譯工作的任務之一就是語法檢查,所以編譯器不會對宏定義進行語法檢查
宏定義不是說明或語句,在末尾不必加分號
宏定義的作用域是從定義的位置開始到整個程序結束
可以用#undef來終止宏定義的作用域
#include<stdio.h>
#define PI 3.14
int main(){
???????? int r;
???????? float s;
???????? printf("請輸入圓的半徑;");
???????? scanf("%d",&r);
???????? //#undef PI
???????? s=PI*r*r;
???????? printf("圓的面積是:%.2f\n",s);
???????? return 0;
}
宏定義允許嵌套
#include<stdio.h>
#define PI 3.14
#define R 6371
#define V PI*R*R*R*4/3
int main(){
???????? printf("地球的體積是:%.2f\n",V);
???????? return 0;
}
?
帶參數(shù)的宏定義
#define MAX(x,y) (((x)>(y))?(x):(y))
注意MAX沒有空格(x,y)
#include<stdio.h>
#define MAX(x,y) (((x)>(y))?(x):(y))
int main(){
???????? int a,b;
???????? printf("請輸入兩個數(shù):");
???????? scanf("%d%d",&a,&b);
???????? printf("較大的數(shù)是:%d\n",MAX(a,b));
???????? return 0;
}
括號不能省
#include<stdio.h>
#define SQUARE(x) x*x
int main(){
???????? int a;
???????? printf("請輸入一個數(shù):");
???????? scanf("%d",&a);
???????? printf("%d的平方是:%d\n",a,SQUARE(a));
???????? printf("%d的平方是:%d\n",a+1,SQUARE(a+1));//如果算x+1的平方會x+1*x+1,宏很傻,直接替換,不會幫你加括號?。保險做法#define SQUARE(x) (x)*(x),也不完美例如計算a++則(a++)*(a++)或造成a加兩次
???????? return 0;
}
?
P41內聯(lián)函數(shù)和一些鮮為人知的技巧
內聯(lián)函數(shù) 解決程序中函數(shù)調用的效率問題。(但會增加編譯時間)
定義函數(shù)前加上inline關鍵字
內聯(lián)函數(shù)執(zhí)行過程是在主函數(shù)中展開,而不是主函數(shù)-子函數(shù)-返回主函數(shù)。
現(xiàn)在的編譯器很聰明,不寫inline,也會自動將一些函數(shù)優(yōu)化成內連函數(shù)
#include<stdio.h>
inline int square(int x)
{
???????? return x*x;
?}
int main(){
???????? int i=1;
???????? while(i<=100){
????????????????????????? printf("%d的平方是:%d\n",i-1,square(i++)); //提高編譯效率,也可以避免想宏定義出現(xiàn)兩次加的錯誤
???????? }
???????? return 0;
}
?
?
#和##
兩個預處理運算符
在帶參數(shù)的宏定義中,#運算符后面應該跟一個參數(shù),預處理會把這個參數(shù)轉化為一個字符串。
#include<stdio.h>
#define STR(s) # s
int main(){
???????? printf("%s\n",STR(FISHC));
???????? return 0;
}
會把多個空格轉化為一個空格
#include<stdio.h>
#define STR(s) # s
int main(){
???????? printf(STR(Hello?? %s num=%d\n),STR(FISHC),520);
???????? return 0;
}
?
##運算符被稱為記號連接運算符,比如我們可以使用
##運算符連接兩個參數(shù)
#include<stdio.h>
#define TOGETHER(x,y) x ## y
int main(){
???????? printf("%d\n",TOGETHER(2,50));
???????? return 0;
}
?
可變參數(shù)
帶參數(shù)的宏定義也可以使用可變參數(shù)
#define SHOWLIST(...) printf(#__VA_ARGS__)
其中…表示可變參數(shù),__VA_ARGS__在預處理中被實際的參數(shù)集所替換(就像參數(shù)列表)(兩邊是兩個下劃線哦)。
#include<stdio.h>
#define SHOWLIST(...) printf(#__VA_ARGS__)
int main(){
???????? SHOWLIST(FishC,520,3.14\n);
???????? return 0;
}
可變參數(shù)允許空參數(shù)
#include<stdio.h>
#define PRINT(format,...) printf(#format,##__VA_ARGS__)
int main(){
???????? PRINT(num=%d\n,520);
???????? PRINT(Hello FishC!\n);//這個里面可變參數(shù)是空的
???????? return 0;
}
?
P42結構體
結構體聲明:
struct 結構體名? //英語單詞structure結構
{
結構體成員1;
結構體成員2;
結構體成員3;
...
};//這里有一個分號
示例
struct Book
{
char title[128];
char author[40];
float price;
unsigned int date;
char publisher[40];
};
?
定義結構體類型變量
struct結構體名稱 結構體變量名;
或者
在聲明結構體時定義
struct結構體名{
.。。。
} 變量名;//不過這時是全局變量
注意:如果
typedef struct結構體名稱{
。。。
}簡稱;或typedef struct結構體名 簡稱;
使用typedef給結構體定義了一個簡稱,并不是變量
?
結構體可以放在函數(shù)外(全局),可以放在函數(shù)內(局部)
?
#include<stdio.h>
struct Book
{
char title[128];
char author[40];
float price;
unsigned int date;
char publisher[40];
} book;
int main(){
???????? //struct Book book;
???????? printf("請輸入書名:");
???????? scanf("%s",book.title); //字符數(shù)組名指向開頭元素地址 不用&
???????? printf("請輸入作者:");
???????? scanf("%s",book.author);
???????? printf("請輸入售價:");
???????? scanf("%f",&book.price);
???????? printf("請輸入出版日期:");
???????? scanf("%d",&book.date);
???????? printf("請輸入出版社:");
???????? scanf("%s",book.publisher);
???????? printf("\n===========數(shù)據(jù)錄入完畢========\n");
???????? printf("書名:%s\n作者:%s\n售價:%.2f\n出版日期:%d\n出版社:%s\n",book.title,book.author,book.price,book.date,book.publisher);
}
?
初始化一個結構體變量
book={
"數(shù)學",
"小明",
21.5,
20200311,
"家里蹲大學出版社"};
?
c99新特性:初始結構體的指定成員值
結構體指定初始化成員使用點號運算符和成員名。
比如我們可以讓程序只初始化Book的price成員;
struct Book book={.price=21.5};
多個可以不按結構體順序進行初始化
struct Book book={.price=21.5,.title="數(shù)學"};
?
結構體的長度與內存對齊
#include<stdio.h>
int main(){
???????? struct A
???????? {
????????????????? char a;
????????????????? int b;
????????????????? char c;
???????? } a={'x',520,'o'};
???????? printf("size of a=%d\n",sizeof(a));//結果結構體a長度為12 ,因為內存對齊(讓CPU更快處理數(shù)據(jù) )
???????? // 如果順序為char a;char c;int b;則長度為8
???????? return 0;
}
拓展:掃碼閱讀 如何手工打包c結構體聲明,減少內存空間占用
?
P43結構體數(shù)組和結構體指針
結構體嵌套
struct Date
{
int year;
int month;
int day;
};
struct Book
{
char title[128];
char author[40];
float price;
struct Date date;
char publisher[40];
} book={
"數(shù)學",
"小明",
21.5,
{2020,3,11},
"家里蹲大學出版社"};
.。。。
printf("日期:%d-%d-%d",book.Date.year,book.Date.month,book.Date.day);
。。。
?
結構體數(shù)組
聲明方法
第一種
struct結構名稱
{
結構體成員;
…
}數(shù)組名[長度];
?
第二鐘
struct結構體名稱 數(shù)組名[長度];
?
結構體數(shù)組初始化struct Book book[3]={{。。。},{。。。},{。。。}}
?
結構體指針
struct Book *pt;
pt=&book;
?
通過結構體指針訪問成員
(*結構體).成員名 //*優(yōu)先級低于點.?? (*pt).title
結構體指針->成員名 ??pt->title
?
P44傳遞結構體變量和結構體指針
傳遞結構體變量
兩個相同結構體類型的結構體變量可以賦值。book1=book2;
?
#include<stdio.h>
struct Date {
???????? int year;
???????? int month;
???????? int day;
};
struct Book {
???????? char title[128];
???????? char author[40];
???????? float price;
???????? struct Date date;
???????? char publisher[40];
};
struct Book getInput(struct Book book){
???????? printf("請輸入書名:");
???????? scanf("%s",book.title);
???????? printf("請輸入作者:");
???????? scanf("%s",book.author);
???????? printf("請輸入售價:");
???????? scanf("%f",&book.price);
???????? printf("請輸入出版日期:");
???????? scanf("%d-%d-%d",&book.date.year,&book.date.month,&book.date.day);
???????? printf("請輸入出版社:");
???????? scanf("%s",book.publisher);
???????? return book;
?
}
void printBook(struct Book book){
???????? printf("書名:%s\n作者:%s\n售價:%.2f\n出版日期:%d-%d-%d\n出版社:%s\n",book.title,book.author,book.price,book.date.year,book.date.month,book.date.day,book.publisher);
}
int main(){
???????? struct Book book1,book2;
???????? printf("請輸入第一本書的信息");
???????? book1=getInput(book1);
???????? printf("請輸入第二本書的信息");
???????? book2=getInput(book2);
???????? printf("顯示第一本書的信息");
???????? printBook(book1);
???????? printf("顯示第二本書的信息");
???????? printBook(book2);
???????? return 0;
}
?
提高執(zhí)行效率,函數(shù)可以不用結構體傳址,而是傳遞指向結構體變量的指針。
#include<stdio.h>
struct Date {
???????? int year;
???????? int month;
???????? int day;
};
struct Book {
???????? char title[128];
???????? char author[40];
???????? float price;
???????? struct Date date;
???????? char publisher[40];
};
void getInput(struct Book *book){
???????? printf("請輸入書名:");
???????? scanf("%s",book->title);
???????? printf("請輸入作者:");
???????? scanf("%s",book->author);
???????? printf("請輸入售價:");
???????? scanf("%f",&book->price);
???????? printf("請輸入出版日期:");
???????? scanf("%d-%d-%d",&book->date.year,&book->date.month,&book->date.day);
???????? printf("請輸入出版社:");
???????? scanf("%s",book->publisher);
}
void printBook(struct Book *book){
???????? printf("書名:%s\n作者:%s\n售價:%.2f\n出版日期:%d-%d-%d\n出版社:%s\n",book->title,book->author,book->price,book->date.year,book->date.month,book->date.day,book->publisher);
}
int main(){
???????? struct Book book1,book2;
???????? printf("請輸入第一本書的信息");
???????? getInput(&book1);
???????? printf("請輸入第二本書的信息");
???????? getInput(&book2);
???????? printf("顯示第一本書的信息");
???????? printBook(&book1);
???????? printf("顯示第二本書的信息");
???????? printBook(&book2);
???????? return 0;
}
?
動態(tài)申請結構體
使用malloc函數(shù)為結構體分配存儲空間
修改
int main(){
???????? struct Book *book1,*book2;
???????? book1=(struct Book *)malloc(sizeof(struct Book));
???????? book2=(struct Book *)malloc(sizeof(struct Book));
???????? if(book1==NULL||book2==NULL){
????????????????? printf("內存分配失??!\n");
????????????????? exit(1);//需要stdlib.h
???????? }
???????? printf("請輸入第一本書的信息");
???????? getInput(book1);
???????? printf("請輸入第二本書的信息");
???????? getInput(book2);
???????? printf("顯示第一本書的信息");
???????? printBook(book1);
???????? printf("顯示第二本書的信息");
???????? printBook(book2);
???????? free(book1);
???????? free(book2);
???????? return 0;
}
篇幅限制,后面部分在這里——傳送門