中文字幕精品无码一区二区,成全视频在线播放观看方法,大伊人青草狠狠久久,亚洲一区影音先锋色资源

【千鋒教育】C語言程序設計(2022版)軟件+全冊講義(含部分章節的課后練習,pdf版)

資源下載
  1. 二一教育資源

【千鋒教育】C語言程序設計(2022版)軟件+全冊講義(含部分章節的課后練習,pdf版)

資源簡介

風千鋒教后
千鋒智能物聯網+嵌入式學科
練習l:實現strlen函數功能
unsigned int my_strlen(const char *s);
練習2:實現strcpy函數功能
char *my_strcpy(char *dest,const char *src);
練習3:實現atoi函數功能
int my_atoi(const char str)
練習4:
使用sscanf讀取"[ti:簡單愛]"":"號與"]"之間的內容
練習5:
使用sscanf讀取"[02:06.85]"02(代表分鐘)06(代表秒)到整型變量min、sec中
練習6:
使用strchr函數查找字符,統計helloworldhelloworldhelloworld'字符串中字符w的個數及位置。
做真實的自己,用良心做教有
1
倉千鋒教有
千鋒智能物聯網+嵌入式學科
做真實的自己,用良心做教育
2風干鋒教百
千鋒智能物聯網+嵌入式學科
練習:
1.輸出0999的水仙花數
水仙花數算法:一個數=它各位的立方和,例如:153=1*1*1+5*5*5+3*3*3
提示:for循環,求余(%)、取整()運算符
2任意給出一個年、月、日,判斷是這一年的第幾天:
閏年算法:能被4整除且不能被100整除,或者能被400整除
如:2012510是這一年的第131天
提示:switch
做真實的自己,用良心做教有
1
倉千鋒教有
千鋒智能物聯網+嵌入式學科
做真實的自己,用良心做教育
2風干鋒熱百
千鋒智能物聯網+嵌入式學科
練習:
1、任意給出一個年、月、日,判斷是這一年的第幾天:
閏年算法:能被4整除且不能被100整除,或者能被400整除
如:2012510是這一年的第131天
提示:使用數組的方式計算,將每月的天數放在一個數組中
2、打字游戲
1)隨機函數
A.srand(unsigned)time(NULL);以當前時間為準,設置隨機種子
注意:此函數,在每次開始游戲后調用一次即可
B.ch=rand(;
注意:rand0函數,每調用一次,產生一個隨機數字
2)獲得鍵值函數
ch=getch0;/無需按下回車,可直接獲得鍵盤上按下的鍵值
3)時間函數
start time=time(NULL);
end_time=time(NULL);
4)system("cls");/清空屏幕
做真實的自己,用良心做教有
倉千鋒教有
千鋒智能物聯網+嵌入式學科
做真實的自己,用良心做教育
2千鋒智能物聯網+嵌入式學科
第 1章 環境搭建
第 2章 c數據類型及語句
第 3章 數組
第 4章 函數
第 5章 預處理、動態庫、靜態庫
第 6章 指針
第 7章 動態內存申請
7.1 動態分配內存的概述
在數組一章中,介紹過數組的長度是預先定義好的,在整個程序中固定不變,但是在實際的編程中,
往往會發生這種情況,即所需的內存空間取決于實際輸入的數據,而無法預先確定 。為了解決上述問題,
C語言提供了一些內存管理函數,這些內存管理函數可以按需要動態的分配內存空間,也可把不再使用的
空間回收再次利用。
7.2 靜態分配、動態分配
靜態分配
1、在程序編譯或運行過程中,按事先規定大小分配內存空間的分配方式。int a [10]
2、必須事先知道所需空間的大小。
3、分配在棧區或全局變量區,一般以數組的形式。
4、按計劃分配。
動態分配
1、在程序運行過程中,根據需要大小自由分配所需空間。
2、按需分配。
1
千鋒智能物聯網+嵌入式學科
3、分配在堆區,一般使用特定的函數進行分配。
7.3 動態分配函數
stdlib.h
1、malloc函數
函數原型: void *malloc(unsigned int size);
功能說明:
在內存的動態存儲區(堆區)中分配一塊長度為 size字節的連續區域,用來存放類型說明符指定的類型。
函數原型返回 void*指針,使用時必須做相應的強制類型轉換 ,分配的內存空間內容不確定,一般使用
memset初始化。
返回值:分配空間的起始地址 ( 分配成功 )
NULL ( 分配失敗 )
注意
1、在調用 malloc之后,一定要判斷一下,是否申請內存成功。
2、如果多次 malloc申請的內存,第 1次和第 2次申請的內存不一定是連續的
char *p;
p = (char *)malloc(20);
例 1:
#include
#include
#include
int main()
{
int i,*array,n;
printf("請輸入您要申請的數組元素個數\n");
scanf("%d",&n);
array=(int *)malloc(n*sizeof(int));
if(array==NULL)
{
printf("申請內存失敗\n");
return 0;
}
memset(array,0,n*sizeof(int));
for(i=0;i{
array[i]=i;
}
2
千鋒智能物聯網+嵌入式學科
for(i=0;i{
printf("%d\n",array[i]);
}
free(array);//釋放 array 指向的內存
return 0;
}
2、 free 函數(釋放內存函數)
頭文件:#include
函數定義:void free(void *ptr)
函數說明:free函數釋放 ptr指向的內存。
注意 ptr指向的內存必須是 malloc calloc relloc動態申請的內存
例 2:
char *p=(char *)malloc(100);
free(p);//
注意
(1)、free后,因為沒有給 p賦值,所以 p還是指向原先動態申請的內存。但是內存已經不能再用了,
p變成野指針了。
(2)、一塊動態申請的內存只能 free一次,不能多次 free
3、 calloc函數
頭文件:#include
函數定義:void * calloc(size_t nmemb,size_t size);
size_t 實際是無符號整型,它是在頭文件中,用 typedef 定義出來的。
函數的功能:在內存的堆中,申請 nmemb 塊,每塊的大小為 size個字節的連續區域
函數的返回值:
返回 申請的內存的首地址(申請成功)
返回 NULL(申請失?。?br/>注意:
malloc和 calloc函數都是用來申請內存的。
區別:
1) 函數的名字不一樣
2) 參數的個數不一樣
3) malloc申請的內存,內存中存放的內容是隨機的,不確定的,而 calloc函數申請的內存中的內
容為 0
例 3:調用方法
char *p=(char *)calloc(3,100);
3
千鋒智能物聯網+嵌入式學科
在堆中申請了 3塊,每塊大小為 100個字節,即 300個字節連續的區域。
4、 realloc函數(重新申請內存)
咱們調用 malloc和 calloc函數,單次申請的內存是連續的,兩次申請的兩塊內存不一定連續。
有些時候有這種需求,即我先用 malloc或者 calloc申請了一塊內存,我還想在原先內存的基礎上挨著
繼續申請內存?;蛘呶议_始時候使用 malloc或 calloc申請了一塊內存,我想釋放后邊的一部分內存。
為了解決這個問題,發明了 realloc這個函數
頭文件#include
函數的定義:void* realloc(void *s,unsigned int newsize);
函數的功能:
在原先 s指向的內存基礎上重新申請內存,新的內存的大小為 new_size 個字節,
如果原先內存后面有足夠大的空間,就追加,如果后邊的內存不夠用,則 relloc函數會在堆區
找一個 newsize 個字節大小的內存申請,將原先內存中的內容拷貝過來,然后釋放原先的內存,最后
返回
新內存的地址。
如果 newsize 比原先的內存小,則會釋放原先內存的后面的存儲空間,只留前面的 newsize個字節
返回值:新申請的內存的首地址
例 4:
char *p;
p=(char *)malloc(100)
//咱們想在 100 個字節后面追加 50 個字節
p=(char *)realloc(p,150);//p 指向的內存的新的大小為 150 個字節
4
千鋒智能物聯網+嵌入式學科
例 5:
char *p;
p=(char *)malloc(100)
//咱們想重新申請內存,新的大小為 50個字節
p=(char *)realloc(p,50);//p 指向的內存的新的大小為 50個字節,100 個字節的后 50 個字節的存儲空間
就被釋放了
注意:malloc calloc relloc 動態申請的內存,只有在 free或程序結束的時候才釋放。
7.4 內存泄露
內存泄露的概念:
申請的內存,首地址丟了,找不了,再也沒法使用了,也沒法釋放了,這塊內存就被泄露了。
內存泄露 例 1:
int main()
{
char *p;
p=(char *)malloc(100);
//接下來,可以用 p指向的內存了
p="hello world";//p 指向別的地方了
//從此以后,再也找不到你申請的 100 個字節了。則動態申請的 100個字節就被泄露了
return 0;
}
內存泄露 例 2:
void fun()
{
char *p;
p=(char *)malloc(100);
//接下來,可以用 p指向的內存了
;
;
}
int main()
5
千鋒智能物聯網+嵌入式學科
{
fun();
fun();
return 0;
}
//每調用一次 fun泄露 100個字節
內存泄露 解決方案 1:
void fun()
{
char *p;
p=(char *)malloc(100);
//接下來,可以用 p指向的內存了
;
;
free(p);
}
int main()
{
fun();
fun();
return 0;
}
內存泄露 解決方案 2:
char * fun()
{
char *p;
p=(char *)malloc(100);
//接下來,可以用 p指向的內存了
;
;
return p;
}
int main()
6
千鋒智能物聯網+嵌入式學科
{
char *q;
q=fun();
//可以通過 q使用 ,動態申請的 100 個字節的內存了
//記得釋放
free(q);
return 0;
}
總結:申請的內存,一定不要把首地址給丟了,在不用的時候一定要釋放內存。
7千鋒智能物聯網+嵌入式學科
第 1章 環境搭建
第 2章 c數據類型及語句
c語言特點
我的第一個 c語言程序
#include
int main()//這個我的第一個 c語言程序
{
printf(“hello world\n”); //printf是輸出打印的函數
return 0;
}
1.#include 頭文件包含,一定要有
2.每一個 c語言的程序有且只有一個 main函數,這是整個程序的開始位置
3.C語言中()、[]、{}、“”、’’、都必須成對出現,必須是英文符號
4.C語言中語句要以分號結束。
5.//為注釋
/*
有志者,事竟成,破釜沉舟,百二秦關終屬楚;
苦心人,天不負,臥薪嘗膽,三千越甲可吞吳
*/
2.1 關鍵字
2.1.1 數據類型相關的關鍵字
用于定義變量或者類型
類型 變量名;
char 、short、int 、long 、 float、double、
struct、union、enum 、signed、unsigned、void
1、 char 字符型 ,用 char定義的變量是字符型變量,占 1個字節
char ch='a'; =為賦值號
char ch1= ‘1’; 正確
char ch2 = ‘1234’ ; 錯誤的
2、 short 短整型 ,使用 short 定義的變量是短整型變量,占 2個字節
1
千鋒智能物聯網+嵌入式學科
short int a=11; -32768 - ---32767
3、 int 整型 ,用 int定義的變量是整型變量,在 32位系統下占 4個字節,在 16平臺下占 2個字節
int a=44; -20 億---20 億
4、 long 長整型 用 long 定義的變量是長整型的,在 32位系統下占 4個字節
long int a=66;
5、 float 單浮點型 (實數),用 float定義的變量是單浮點型的實數,占 4個字節
float b=3.8f;
6、 double 雙浮點型 (實數),用 double定義的變量是雙浮點型的實數,占 8個字節
double b=3.8;
7、 struct 這個關鍵字是與結構體類型相關的關鍵字,可以用它來定義結構體類型,以后講結構體的時候
再講
8、 union 這個關鍵字是與共用體(聯合體)相關的關鍵字,以后再講
9、 enum 與枚舉類型相關的關鍵字 以后再講
10、signed 有符號(正負)的意思
在定義 char 、整型(short 、int、long) 數據的時候用 signed 修飾,代表咱們定義的數據是有符號
的,可以保存正數,也可以保存負數
例 :signed int a=10;
signed int b=-6;
注意:默認情況下 signed 可以省略 即 int a=-10;//默認 a就是有符號類型的數據
11、unsigned 無符號的意思
在定義 char 、整型(short 、int、long) 數據的時候用 unsigned 修飾,代表咱們定義的數據是無符
號類型的數據
只能保存正數和 0。
unsigned int a=101;
unsigned int a=-101; //錯誤
擴展:內存存儲
char ch= ‘a’; //占 1個字節,存儲的是 97
0110 0001
字節:內存的基本單位,8位為 1個字節
計算機存儲時,只能存儲 1和 0的二進制組合,1和 0都分別占 1位
字符型數據在內存中存儲的不是字符本身,而是存儲其 AsciI碼
整型變量存儲的是其值的二進制
unsigned int a = 97;
12、void 空類型的關鍵字
2
千鋒智能物聯網+嵌入式學科
char、int 、float 都可以定義變量
void 不能定義變量,沒有 void 類型的變量
void 是用來修飾函數的參數或者返回值,代表函數沒有參數或沒有返回值
例:
void fun(void)
{
}
代表 fun函數沒有返回值,fun函數沒有參數
例 2:
#include
int main()
{
char a = 'a';
short int b = 10;
int c;
long int d;
float e;
double f;
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(b));
printf("%d\n",sizeof(c));
printf("%d\n",sizeof(d));
printf("%d\n",sizeof(e));
printf("%d\n",sizeof(f));
return 0;
}
2.1.2 存儲相關關鍵字
register、static、const、auto、extern
1、register 是 寄存器的意思,用 register修飾的變量是寄存器變量,
即:在編譯的時候告訴編譯器這個變量是寄存器變量,盡量將其存儲空間分配在寄存器中。
注意:
(1):定義的變量不一定真的存放在寄存器中。
(2):cpu取數據的時候去寄存器中拿數據比去內存中拿數據要快
(3):因為寄存器比較寶貴,所以不能定義寄存器數組
(4):register只能修飾 字符型及整型的,不能修飾浮點型
register char ch;
register short int b;
register int c;
3
千鋒智能物聯網+嵌入式學科
register float d;//錯誤的
(5):因為 register修飾的變量可能存放在寄存器中不存放在內存中,所以
不能對寄存器變量取地址。因為只有存放在內存中的數據才有地址
register int a;
int *p;
p=&a;//錯誤的,a可能沒有地址
2、static 是靜態的意思
static 可以修飾全局變量、局部變量、函數
這個以后的課程中重點講解
3、const
const 是常量的意思
用 const修飾的變量是只讀的,不能修改它的值
const int a=101;//在定義 a的時候用 const 修飾,并賦初值為 101
從此以后,就不能再給 a賦值了
a=111;//錯誤的
const可以修飾指針,這個在以后課程中重點講解
4、auto int a;和 int a是等價的,auto關鍵字現在基本不用
5、extern 是外部的意思,一般用于函數和全局變量的聲明,這個在后面的課程中,會用到
2.1.3 控制語句相關的關鍵字
if 、else 、break、continue、for 、while、do、switch case
goto、default
2.1.4 其他關鍵字
sizeof、typedef、volatile
1、sizeof
使用來測變量、數組的占用存儲空間的大小(字節數)
例 3:
int a=10;
int num;
num=sizeof(a);
2、typedef 重命名相關的關鍵字
unsigned short int a = 10;
U16
關鍵字 ,作用是給一個已有的類型,重新起個類型名,并沒有創造一個新的類型
以前大家看程序的時候見過類似的變量定義方法
INT16 a;
4
千鋒智能物聯網+嵌入式學科
U8 ch;
INT32 b;
大家知道,在 c語言中沒有 INT16 U8 這些關鍵字
INT16 U8是用 typedef 定義出來的新的類型名,其實就是 short int 及 unsigned char的別名
typedef起別名的方法:
1、用想起名的類型定義一個變量
short int a;
2、用新的類型名替代變量名
short int INT16;
3、在最前面加 typedef
typedef short int INT16;
4:就可以用新的類型名定義變量了
INT16 b;和 short int b;//是一個效果
例 4:
#include
//short int b;
//short int INT16;
typedef short int INT16;
int main(int argc, char *argv[])
{
short int a=101;
INT16 c=111;
printf("a=%d\n",a);
printf("c=%d\n",c);
return 0;
}
3、volatile 易改變的意思
用 volatile定義的變量,是易改變的,即告訴 cpu每次用 volatile變量的時候,重新去內存中取
保證用的是最新的值,而不是寄存器中的備份。
volatile 關鍵字現在較少適用
volatile int a=10;
擴展知識:
命名規則:
在 c語言中給變量和函數起名的時候,由字母、數字、下劃線構成
必須以字母或者下滑線開頭
例 5:
5
千鋒智能物聯網+嵌入式學科
int a2;//正確的
int a_2;//正確的
int _b;//正確的
int 2b;// 錯誤的
int $a2;//錯誤的
注意:起名的時候要求見名知意
Linux風格
stu_num
駝峰風格
StuNum
大小寫敏感
int Num;
int num;
C語言的程序結構
一個完整的 C語言程序,是由一個、且只能有一個 main()函數(又稱主函數,必須有)
和若干個其他函數結合而成(可選)
main函數是程序的入口,即 程序從 main函數開始執行
2.2 數據類型
2.2.1 基本類型
char 、short int 、int、long int、float、double
擴展:常量和變量
常量:在程序運行過程中,其值不可以改變的量
例:100 ‘a’ “hello”
整型 100,125,-100,0
實型 3.14 , 0.125f,-3.789
字符型 ‘a’,‘b’,‘2’
字符串 “a”,“ab”,“1232”
變量:其值可以改變的量被稱為變量
int a=100;
a=101;
字符數據
字符常量:
直接常量:用單引號括起來,如:'a'、'b'、’0’等.
轉義字符:以反斜杠“\”開頭,后跟一個或幾個字符、如'\n','\t'等,分別代表換行、橫向跳格.
‘\\’表示的是\ ‘%%’ ‘\’’
6
千鋒智能物聯網+嵌入式學科
字符變量:
用 char定義,每個字符變量被分配一個字節的內存空間
字符值以 ASCII碼的形式存放在變量的內存單元中;
注:char a;
a = 'x';
a變量中存放的是字符'x'的 ASCII :120
即 a=120跟 a='x'在本質上是一致的.
例 6:
#include
int main(int argc, char *argv[])
{
char a='x';
char b=120;
printf("a=%c\n",a);
printf("b=%c\n",b);
return 0;
}
ASCII 碼表
例 7:
#include
int main(int argc, char *argv[])
{
unsigned int i;
for(i=0;i<=255;i++)
{
printf("%d %c ",i,i);
if(i%10==0)
printf("\n");
}
return 0;
}
字符串常量
是由雙引號括起來的字符序列,如“CHINA”、”哈哈哈”
“C program”,“$12.5”等都是合法的字符串常量.
字符串常量與字符常量的不同
‘a’為字符常量,”a”為字符串常量
每個字符串的結尾,編譯器會自動的添加一個結束標志位'\0',
即“a”包含兩個字符‘a’和’\0’
7
千鋒智能物聯網+嵌入式學科
整型數據
整型常量:(按進制分):
十進制: 以正常數字 1-9開頭,如 457 789
八進制: 以數字 0開頭,如 0123
十六進制:以 0x開頭,如 0x1e
a=10,b=11,c=12, d=13,e=14,f=15
整型變量:
有/無符號短整型(un/signed) short(int) 2個字節
有/無符號基本整型(un/signed) int 4個字節
有/無符號長整型(un/signed) long (int) 4個字節 (32位處理器)
實型數據(浮點型)
實型常量
實型常量也稱為實數或者浮點數
十進制形式: 由數字和小數點組成:0.0、0.12、5.0
指數形式: 123e3代表 123*10的三次方
123e-3
不以 f結尾的常量是 double類型
以 f結尾的常量(如 3.14f)是 float類型
實型變量
單精度(float)和雙精度(double)3.1415926753456
float型: 占 4字節,7位有效數字,指數-37到 38
3333.333 33
double型: 占 8字節,16位有效數字,指數-307到 308
格式化輸出字符:
%d 十進制有符號整數 %u 十進制無符號整數
%x, 以十六進制表示的整數 %o 以八進制表示的整數
%f float型浮點數 %lf double型浮點數
%e 指數形式的浮點數
%s 字符串 %c 單個字符
%p 指針的值
特殊應用:
%3d %03d %-3d %5.2f
%3d:要求寬度為 3位,如果不足 3位,前面空格補齊;如果足夠 3位,此語句無效
%03d:要求寬度為 3位,如果不足 3位,前面 0補齊;如果足夠 3位,此語句無效
%-3d: 要求寬度為 3位,如果不足 3位,后面空格補齊;如果足夠 3位,此語句無效
%.2f:小數點后只保留 2位
8
千鋒智能物聯網+嵌入式學科
2.2.2 構造類型
概念:由若干個相同或不同類型數據構成的集合,這種數據類型被稱為構造類型
例:int a[10];
數組、結構體、共用體、枚舉
2.2.3 類型轉換
數據有不同的類型,不同類型數據之間進行混合運算時必然涉及到類型的轉換問題.
轉換的方法有兩種:
自動轉換:
遵循一定的規則,由編譯系統自動完成.
強制類型轉換:
把表達式的運算結果強制轉換成所需的數據類型
自動轉換的原則:
1、占用內存字節數少(值域小)的類型,向占用內存字節數多(值域大)的類型轉換,以保證精度不降低.
2、轉換方向:
1) 當表達式中出現了 char 、short int 、int 類型中的一種或者多種,沒有其他類型了
參加運算的成員全部變成 int類型的參加運算,結果也是 int類型的
例 8:
#include
int main(int argc, char *argv[])
{
printf("%d\n",5/2);
return 0;
}
2) 當表達式中出現了帶小數點的實數,參加運算的成員全部變成 double類型的參加運算,結果
9
千鋒智能物聯網+嵌入式學科
也是 double型。
例 9:
#include
int main(int argc, char *argv[])
{
printf("%lf\n",5.0/2);
return 0;
}
3) 當表達式中有有符號數 也有無符號數,參加運算的成員變成無符號數參加運算結果也是無符
號數.(表達式中無實數)
例 10:
#include
int main(int argc, char *argv[])
{
int a=-8;
unsigned int b=7;
if(a+b>0)
{
printf("a+b>0\n");
}
else
{
printf("a+b<=0\n");
}
printf("%x\n",(a+b));
printf("%d\n",(a+b));
return 0;
}
4) 在賦值語句中等號右邊的類型自動轉換為等號左邊的類型
例 11:
#include
int main(int argc, char *argv[])
{
int a;
float b=5.8f;//5.8 后面加 f 代表 5.8 是 float 類型,不加的話,認為是 double 類型
a=b;
printf("a=%d\n",a);
10
千鋒智能物聯網+嵌入式學科
return 0;
}
5) 注意自動類型轉換都是在運算的過程中進行臨時性的轉換,并不會影響自動類型轉換的變量的
值和其類型
例 12:
#include
int main(int argc, char *argv[])
{
int a;
float b=5.8f;//5.8 后面加 f 代表 5.8 是 float 類型,不加的話,認為是 double 類型
a=b;
printf("a=%d\n",a);
printf("b=%f\n",b);//b 的類型依然是 float 類型的,它的值依然是 5.8
return 0;
}
強制轉換:通過類型轉換運算來實現
(類型說明符) (表達式)
功能:
把表達式的運算結果強制轉換成類型說明符所表示的類型
例如:
(float)a; // 把 a的值轉換為實型
(int)(x+y); // 把 x+y的結果值轉換為整型
注意:
類型說明符必須加括號
例 13:
#include
int main(int argc, char *argv[])
{
float x=0;
int i=0;
x=3.6f;
i = x;
i = (int)x;
printf("x=%f,i=%d\n",x,i);
return 0;
}
說明:
無論是強制轉換或是自動轉換,都只是為了本次運算的需要,而對變量的數據長度進行的臨時性
轉換,而不改變數據定義的類型以及它的值
11
千鋒智能物聯網+嵌入式學科
2.2.4 指針
2.3 運算符
2.3.1 運算符
用算術運算符將運算對象(也稱操作數)連接起來的、符合C語法規則的式子,稱為C算術表達式
運算對象包括常量、變量、函數等
例如: a* b / c-1.5 + 'a'
2.3.2 運算符的分類:
1、雙目運算符:即參加運算的操作數有兩個
例:+
a+b
2、單目運算符:參加運算的操作數只有一個
++自增運算符 給變量值+1
--自減運算符
int a=10;
a++;
3、三目運算符:即參加運算的操作數有 3個
() ():()
2.3.3 算數運算符
+ - * / % += -= *= /= %=
10%3 表達式的結果為 1
復合運算符:
a += 3 相當于 a=a+3
a*=6+8 相當于 a=a*(6+8)
2.3.4 關系運算符
(>、<、= =、>=、<=、!= )
!=為不等于
一般用于判斷條件是否滿足或者循環語句
2.3.5 邏輯運算符
1、&& 邏輯與
兩個條件都為真,則結果為真
if((a>b) && (aif(b2、|| 邏輯或
兩個條件至少有一個為真,則結果為真
if((a>b) || (a12
千鋒智能物聯網+嵌入式學科
3、! 邏輯非
if(!(a>b))
{
}
2.3.6 位運算符
十進制轉二進制:
方法 除 2求余法
例: 123 十進制 轉二進制
正數在內存中以原碼形式存放,負數在內存中以補碼形式存放
正數的 原碼=反碼=補碼
原碼:將一個整數,轉換成二進制,就是其原碼。
如單字節的 5的原碼為:0000 0101;-5的原碼為 1000 0101。
反碼:正數的反碼就是其原碼;負數的反碼是將原碼中,除符號位以外,每一位取反。
如單字節的 5的反碼為:0000 0101;-5的反碼為 1111 1010。
補碼:正數的補碼就是其原碼;負數的反碼+1就是補碼。
如單字節的 5的補碼為:0000 0101;-5的補碼為 1111 1011。
在計算機中,正數是直接用原碼表示的,如單字節 5,在計算機中就表示為:0000 0101。
負數用補碼表示,如單字節-5,在計算機中表示為 1111 1011。
無論是正數還是負數,編譯系統都是按照內存中存儲的內容進行位運算。
1、&按位 與
任何值與 0得 0,與 1保持不變
使某位清 0
0101 1011&
1011 0100
---------------
0001 0000
2、| 按位或
任何值或 1得 1,或 0保持不變
0101 0011 |
1011 0100
1111 0111
3、~ 按位取反
1變 0,0變 1
0101 1101 ~
13
千鋒智能物聯網+嵌入式學科
1010 0010
4、^ 按位異或
相異得 1,相同得 0
1001 1100 ^
0101 1010
1100 0110
5、位移
>>右移
<< 左移
注意右移分:邏輯右移、算數右移
(1)、右移
邏輯右移 高位補 0,低位溢出
算數右移 高位補符號位,低位溢出 (有符號數)
A)、邏輯右移
低位溢出、高位補 0
0101 1010 >>3
0000 1011
B)、算數右移:
對有符號數來說
低位溢出、高位補符號位。
1010 1101 >> 3
1111 010 1
0101 0011 >>3
0000 101 0
總結 右移:
1、邏輯右移 高位補 0,低位溢出
注:無論是有符號數還是無符號數都是高位補 0,低位溢出
2、算數右移 高位補符號位,低位溢出 (有符號數)
注:對無符號數來說,高位補 0,低位溢出
對有符號數來說,高位補符號位,低位溢出
在一個編譯系統中到底是邏輯右移動,還是算數右移,取決于編譯器
判斷右移是邏輯右移還是算數右移
#include
int main(int argc, char *argv[])
{
printf("%d\n",-1>>3);
return 0;
}
如果結果還是-1 證明是算數右移
14
千鋒智能物聯網+嵌入式學科
(2) 左移<< 高位溢出,低位補 0
5<<1
0000 0101
0000 1010
2.3.7 條件運算符號
() ():()
A B:C;
如果?前邊的表達式成立,整個表達式的值,是?和:之間的表達式的結果
否則是:之后的表達式的結果
例 14:
#include
int main(int argc, char *argv[])
{
int a;
a=(3<5) (8):(9);
printf("a=%d\n",a);
return 0;
}
2.3.8 逗號預算符 ,
(),()
例 15:
#include
int main(int argc, char *argv[])
{
int num;
num=(5,6);
printf("%d\n",num);
return 0;
}
注意逗號運算符的結果是,后邊表達式的結果
2.3.9 自增自減運算符
i++ i--
運算符在變量的后面,在當前表達式中先用 i的值,下條語句的時候 i的值改變
例 16:
#include
int main()
{
15
千鋒智能物聯網+嵌入式學科
int i=3;
int num;
num=i++;
printf("num=%d,i=%d\n",num,i);//num=3 ,i=4
return 0;
}
++i 先加 ,后用
例 17:
#include
int main()
{
int i=3;
int num;
num=++i;
printf("num=%d,i=%d\n",num,i);//num=4,i=4
return 0;
}
例 18:
#include
int main(int argc, char *argv[])
{
int i=3;
int num;
num = (i++)+(i++)+(i++);
printf("num=%d\n",num);
return 0;
}
例 19:
#include
int main(int argc, char *argv[])
{
int i=3;
int num;
16
千鋒智能物聯網+嵌入式學科
num = (++i)+(++i)+(++i);
printf("num=%d\n",num);
return 0;
}
2.3.10 運算符優先級及結合性
運算符優先級
在表達式中按照優先級先后進行運算,優先級高的先于優先級低的先運算。
優先級一樣的按結合性來運算
int a;
a=2+5+3*4-6
運算符結合性
左結合性:從左向右運算
int a;
a=2+3+9+10;
右結合性:從右向左運算
int a,b,c,d;
a=b=c=d=100;
優先級和結合性表:
17
千鋒智能物聯網+嵌入式學科
注:建議當表達式比較復雜的時候,用()括起來,括號的優先級最高,優先算括號里的。
這樣也防止寫錯表達式。
int a;
a=(2+3)*6+(9*3)+10;
2.4 控制語句
2.4.1 選擇控制語句
1、 if語句
形式:
1) if(條件表達式)
{//復合語句,若干條語句的集合
語句 1;
語句 2;
}
如果條件成立執行大括號里的所有語句,不成立的話大括號里的語句不執行
例 20:
#include
int main()
{
int a=10;
if(a>5)
{
printf("a>5\n");
}
18
千鋒智能物聯網+嵌入式學科
return 0;
}
2) if(條件表達式)
{
}
else
{
}
if else語句的作用是,如果 if的條件成立,執行 if后面{}內的語句,否則執行 else后的語句
例 21:
#include
int main()
{
int a=10;
if(a>5)
{
printf("a>5\n");
}
else
{
printf("a<=5\n");
}
return 0;
}
注意 if和 else之間只能有一條語句,或者有一個復合語句,否則編譯會出錯
例 22:
if()
語句 1;
語句 2;
else
語句 3;
語句 4;
錯誤:if和 else之間只能有一條語句,如果有多條語句的話加大括號
例 23:
if()
19
千鋒智能物聯網+嵌入式學科
{
語句 1;
語句 2;
}
else
{
語句 3;
語句 4;
}
正確
3) if(條件表達式 1)
{
}
else if(條件表達式 2)
{
}
else if(條件表達式 3)
{
}
else
{
}
在判斷的時候,從上往下判斷,一旦有成立的表達式,執行對應的復合語句,
下邊的就不再判斷了,各個條件判斷是互斥的
例 24:
#include
int main(void)
{
char ch;
float score = 0;
printf("請輸入學生分數:\n");
scanf("%f",&score);
if(score<0 || score >100)
{
printf("你所輸入的信息有錯\n");
return 0;
}
else if( score<60)
20
千鋒智能物聯網+嵌入式學科
{
ch = 'E';
}
else if ( score < 70 )
{
ch = 'D';
}
else if ( score < 80 )
{
ch = 'C';
}
else if ( score < 90 )
{
ch = 'B';
}
else
{
ch = 'A';
}
printf("成績評定為:%c\n",ch);
return 0;
}
2、 switch 語句
switch(表達式)//表達式只能是字符型或整型的(short int int long int)
{
case 常量表達式1:
語句1;
break;
case 常量表達式2:
語句2;
break;
default:語句3;break;
}
注意:break的使用
例 25:
#include
int main(int argc, char *argv[])
21
千鋒智能物聯網+嵌入式學科
{
int n;
printf("請輸入一個 1~7 的數\n");
scanf_s("%d",&n);
switch(n)
{
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五\n");
break;
case 6:
printf("星期六\n");
break;
case 7:
printf("星期天\n");
break;
default:
printf("您的輸入有誤,請輸入 1~7 的數\n");
break;
}
return 0;
}
2.4.2 循環控制語句
1、 for 循環
for(表達式 1;表達式 2;表達式 3)
{//復合語句,循環體
22
千鋒智能物聯網+嵌入式學科
}
第一次進入循環的時候執行表達式 1,表達式 1只干一次,
表達式 2,是循環的條件,只有表達式 2為真了,才執行循環體,也就是說
每次進入循環體之前要判斷表達式 2是否為真。
每次執行完循環體后,首先執行表達式 3
例 25:for 循環求 1~100 的和
#include
int main(void)
{
int i;
int sum=0;
for(i=1;i<=100;i++)
{
sum = sum+i;
}
printf("sum=%d\n",sum);
return 0;
}
例 26:
#include
23
千鋒智能物聯網+嵌入式學科
int main(int argc, char *argv[])
{
int i,j;
for(i=1;i<=9;i++)
{
for( j=1;j<=i;j++)
{
printf("%d*%d=%d ",j,i,j*i);
}
printf("\n");
}
return 0;
}
2、 while 循環
1) 形式 1:
while(條件表達式)
{//循環體,復合語句
}
進入 while循環的時候,首先會判斷條件表達式是否為真,為真進入循環體,否則退出循環
例 27:
#include
int main(void)
{
int i=1;
int sum=0;
while(i<=100)
{
sum = sum+i;
i++;
}
printf("sum=%d\n",sum);
return 0;
}
2) 形式 2 : do
do{//循環體
24
千鋒智能物聯網+嵌入式學科
}while(條件表達式);
先執行循環體里的代碼,然后去判斷條件表達式是否為真,為真再次執行循環體,否則退出循環
例 28:
#include
int main(void)
{
int i=1;
int sum=0;
do
{
sum = sum+i;
i++;
}while(i<=100);
printf("sum=%d\n",sum);
return 0;
}
形式 1和形式 2的區別是,形式 1先判斷在執行循環體,形式 2先執行循環體,再判斷
break 跳出循環
continue 結束本次循環,進入下一次循環
例 29:
#include
int main(void)
{
int i;
int sum=0;
for(i=1;i<=100;i++)
{
if(i==10)
break;//將 break 修改成 continue看效果
sum = sum+i;
}
printf("sum=%d\n",sum);
return 0;
}
return 返回函數的意思。結束 return所在的函數,
25
千鋒智能物聯網+嵌入式學科
在普通函數中,返回到被調用處,在 main函數中的話,結束程序
3、 goto
例 30:
#include
int main(int argc, char *argv[])
{
printf("test000000000000000000\n");
printf("test1111111111111111\n");
goto tmp;
printf("test222222222222222222\n");
printf("test3333333333333333\n");
printf("test444444444444444444444\n");
printf("test55555555555555555555\n");
tmp:
printf("test66666666666666666\n");
return 0;
}
26風干于鋒教百
千鋒智能物聯網+嵌入式學科
第1章環境搭建
1.1 Visual Studio軟件安裝
1、軟件下載路徑
鏈接:https:/pan.baidu.,com/s/1LaLe7 amFWF500WKc0 sTmnA
提取碼:i2sg
2、安裝
雙擊exe可執行程序
vs communi
y_7129101
96.1584880
824.ex6
Visual Studio Installer
開始之前,我們需要設置某些選項,以便你配置安裝。
若要了解有關隱私的詳細信息,請參閱Microsoft隱私聲明。
繼續即表示你同意Microsoft軟件許可條款。
繼續O)
做真實的自己,用良心做教有
風鋒教百
千鋒智能物聯網+嵌入式學科
Visual Studio Installer
稍等片刻正在提取文件。
正在下裁:2.99MB/74.25MB
860.45KB/秒
正在安裝
稍等一會
取消(q
EL一M.iammnly一1hL
表治雪
Aehr三d
安裝詳知信已
11ab女女
>Voual5udie民.心e
v快刀C+中的夏面環發
c+rT:
=60g打C+C:1年
Tn
夏=子w2生太⊥C+nG打k
52,牛右,1:3★hmI
BDOst.Tect2h
G03eT15a
a Lies Shaw
日2測m季m

Visual Studio Installer
感可用
Visual Studin Community 201
T發人員新聞
工E一8生:7M1195n
(1:
Anncurcirg NET G Frew
H性等待一時回
Miae3i.w3山u時Hn.in&cmmniy
ANLI Cete updilss in.Ns Wavie 1
安片
NETGPreeNow avable an
行明
1+=月匠黑
aierairgyaiml4
edy wcre codted to zrneunse our Re ease
4只月肛8
重Z棗巴代機
AE
衛.下M+之30712
做真實的自己,用良心做教育
2
風干鋒教百
千鋒智能物聯網+嵌入式學科
1.2注冊
A)創建一個賬戶
Visual Studio
登錄Visual Studio
巖設活同路設空
使用ireShare時r作
與A2山re深天數元
B)輸入一個電子郵箱地址
Microsoft
創建帳戶
someone@
改為使用電話號碼
獲取新的電子郵件地址
下一步
C)設置一個登錄密碼
做真實的自己,用良心做教育
3千鋒智能物聯網+嵌入式學科
第 1章 環境搭建
第 2章 c數據類型及語句
第 3章 數組
第 4章 函數
第 5章 預處理、動態庫、靜態庫
第 6章 指針
第 7章 動態內存申請
第 8章 字符串處理函數
第 9章 結構體、共用體、枚舉
9.1 結構體概念
在程序開發的時候,有些時候我們需要將不同類型的數據組合成一個有機的整體,
以便于引用。如:
一個學生有學號/姓名/性別/年齡/地址等屬性
int num;
char name[20];
char sex;
int age;
char addr[30];
1
千鋒智能物聯網+嵌入式學科
顯然單獨定義以上變量比較繁瑣,數據不便于管理,所以在 C語言中就發明了結構體類型。
結構體是一種構造數據類型。
前面學過一種構造類型——數組:
構造類型:
不是基本類型的數據結構也不是指針類型,它是若干個相同或不同類型的數據構成的集合
描述一組具有相同類型數據的有序集合,用于處理大量相同類型的數據運算--數組
結構體類型的概念:
結構體是一種構造類型的數據結構,
是一種或多種基本類型或構造類型的數據的集合。
9.2 結構體類型定義
結構體類型的定義方法
咱們在使用結構體之前必須先有類型,然后用類型定義數據結構
這個類型相當于一個模具
(1).先定義結構體類型,再去定義結構體變量
struct 結構體類型名{
成員列表
};
例 1:
struct stu{
int num;
char name[20];
char sex;
};
//有了結構體類型后,就可以用類型定義變量了
struct stu lucy,bob,lilei;//定義了三個 struct stu類型的變量
每個變量都有三個成員,分別是 num name sex
咱們可以暫時認為結構體變量的大小是它所有成員之和
(2).在定義結構體類型的時候順便定義結構體變量,以后還可以定義結構體變量
struct 結構體類型名{
成員列表;
}結構體變量 1,變量 2;
2
千鋒智能物聯網+嵌入式學科
struct 結構體類型名 變量 3,變量 4;
例 2:
struct stu{
int num;
char name[20];
char sex;
}lucy,bob,lilei;
struct stu xiaohong,xiaoming;
3.在定義結構體類型的時候,沒有結構體類型名,順便定義結構體變量,
因為沒有類型名,所以以后不能再定義相關類型的數據了
struct {
成員列表;
}變量 1,變量 2;
例 3:
struct {
int num;
char name[20];
char sex;
}lucy,bob;
以后沒法再定義這個結構體類型的數據了,因為沒有類型名
4.最常用的方法
通常咱們將一個結構體類型重新起個類型名,用新的類型名替代原先的類型
步驟 1:先用結構體類型定義變量
struct stu{
int num;
char name[20];
char sex;
}bob;
步驟 2:新的類型名替代變量名
struct stu{
int num;
char name[20];
3
千鋒智能物聯網+嵌入式學科
char sex;
}STU;
步驟 3:在最前面加 typedef
typedef struct stu{
int num;
char name[20];
char sex;
}STU;
注意:步驟 1和步驟 2,在草稿上做的,步驟 3是程序中咱們想要的代碼
以后 STU 就相當于 struct stu
STU lucy;和 struct stu lucy;是等價的。
9.3 結構體變量的定義初始化及使用
1、結構體變量的定義和初始化
結構體變量,是個變量,這個變量是若干個相同或不同數據構成的集合
注:
(1):在定義結構體變量之前首先得有結構體類型,然后再定義變量
(2):在定義結構體變量的時候,可以順便給結構體變量賦初值,被稱為結構體的初始化
(3):結構體變量初始化的時候,各個成員順序初始化
例 4:
struct stu{
int num;
char name[20];
char sex;
};
struct stu boy;
struct stu lucy={
101,
"lucy",
'f'
};
2、結構體變量的使用
定義了結構體變量后,要使用變量
(1).結構體變量成員的引用方法
4
千鋒智能物聯網+嵌入式學科
結構體變量.成員名
例 5:
struct stu{
int num;
char name[20];
char sex;
};
struct stu bob;
bob.num=101;//bob 是個結構體變量,但是 bob.num 是個 int 類型的變量
bob.name 是個字符數組,是個字符數組的名字,代表字符數組的地址,是個常量
bob.name ="bob";//是不可行,是個常量
strcpy(bob.name,"bob");
例 6:
#include
struct stu{
int num;
char name[20];
int score;
char *addr;
};
int main(int argc, char *argv[])
{
struct stu bob;
printf("%d\n",sizeof(bob));
printf("%d\n",sizeof(bob.name));
printf("%d\n",sizeof(bob.addr));
return 0;
}
(2).結構體成員多級引用
例 7:
#include
struct date{
int year;
5
千鋒智能物聯網+嵌入式學科
int month;
int day;
};
struct stu{
int num;
char name[20];
char sex;
struct date birthday;
};
int main(int argc, char *argv[])
{
struct stu lilei={101,"lilei",'m'};
lilei.birthday.year=1986;
lilei.birthday.month=1;
lilei.birthday.day=8;
printf("%d %s %c\n",lilei.num,lilei.name,lilei.sex);
printf("%d %d %d\n",lilei.birthday.year,lilei.birthday.month,lilei.birthday.day);
return 0;
}
3、相同類型的結構體變量可以相互賦值
注意:必須是相同類型的結構體變量,才能相互賦值。
例 8:
#include
struct stu{
int num;
char name[20];
char sex;
};
int main(int argc, char *argv[])
{
struct stu bob={101,"bob",'m'};
struct stu lilei;
lilei=bob;
printf("%d %s %c\n",lilei.num,lilei.name,lilei.sex);
return 0;
}
6
千鋒智能物聯網+嵌入式學科
9.4 結構體數組
結構體數組是個數組,由若干個相同類型的結構體變量構成的集合
1、結構體數組的定義方法
struct 結構體類型名 數組名[元素個數];
例 9:
struct stu{
int num;
char name[20];
char sex;
};
struct stu edu[3];//定義了一個 struct stu 類型的結構體數組 edu,
這個數組有 3個元素分別是 edu[0] 、edu[1]、edu[2]
1、結構體數組元素的引用 數組名[下標]
2、數組元素的使用
edu[0].num =101;//用 101給 edu數組的第 0個結構體變量的 num賦值
strcpy(edu[1].name,"lucy");
例 10:
#include
typedef struct student
{
int num;
char name[20];
float score;
}STU;
STU edu[3]={
{101,"Lucy",78},
{102,"Bob",59.5},
{103,"Tom",85}
};
int main()
{
int i;
float sum=0;
for(i=0;i<3;i++)
{
7
千鋒智能物聯網+嵌入式學科
sum+=edu[i].score;
}
printf("平均成績為%f\n",sum/3);
return 0;
}
9.5 結構體指針
即結構體的地址,結構體變量存放內存中,也有起始地址
咱們定義一個變量來存放這個地址,那這個變量就是結構體指針變量。
結構體指針變量也是個指針,既然是指針在 32位環境下,指針變量的占 4個字節,存放一個地址編號。
1、結構體指針變量的定義方法:
struct 結構體類型名 * 結構體指針變量名;
struct stu{
int num;
char name[20];
};
struct stu * p;//定義了一個 struct stu *類型的指針變量
變量名 是 p,p占 4個字節,用來保存結構體變量的地址編號
struct stu boy;
p=&boy;
訪問結構體變量的成員方法:
例 11:
boy.num=101;//可以,通過 結構體變量名.成員名
(*p).num=101;//可以,*p 相當于 p指向的變量 boy
p->num=101;//可以,指針->成員名
通過結構體指針來引用指針指向的結構體的成員,前提是
指針必須先指向一個結構體變量。
結構體指針應用場景:
(1):保存結構體變量的地址
例 12:
typedef struct stu{
int num;
8
千鋒智能物聯網+嵌入式學科
char name[20];
float score;
}STU;
int main()
{
STU *p,lucy;
p=&lucy;
p->num=101;
strcpy(p->name,"baby");
//p->name="baby";//錯誤,因為 p->name 相當于 lucy.name 是個字符數組的名字,是個
常量
}
(2):傳 結構體變量的地址
例 13:
#include
#include
typedef struct stu{
int num;
char name[20];
float score;
}STU;
void fun(STU *p)
{
p->num=101;
(*p).score=87.6;
strcpy(p->name,"lucy");
}
int main()
{
STU girl;
fun(&girl);
printf("%d %s %f\n",girl.num,girl.name,girl.score);
return 0;
}
(3):傳結構體數組的地址
結構體數組,是由若干個相同類型的結構體變量構成的集合。存放在內存里,
也有起始地址,其實就是第 0個結構體變量的地址。
9
千鋒智能物聯網+嵌入式學科
例 14:
#include
#include
typedef struct stu{
int num;
char name[20];
float score;
}STU;
void fun(STU *p)
{
p[1].num=101;
(*(p+1)).score=88.6;
}
int main()
{
STU edu[3];
fun(edu);
printf("%d %f\n",edu[1].num,edu[1].score);
return 0;
}
注意:
(1):結構體變量的地址編號和結構體第一個成員的地址編號相同,但指針的類型不同
例 15:
#include
struct stu{
int num;
char name[20];
int score;
};
int main(int argc, char *argv[])
{
struct stu bob;
printf("%p\n",&bob);
printf("%p\n",&(bob.num));
return 0;
10
千鋒智能物聯網+嵌入式學科
}
(2):結構體數組的地址就是結構體數組中第 0個元素的地址
例 16:
#include
struct stu{
int num;
char name[20];
int score;
};
int main(int argc, char *argv[])
{
struct stu edu[3];
printf("%p\n",edu);//struct stu *
printf("%p\n",&(edu[0]));//struct stu *
printf("%p\n",&(edu[0].num));//int *
return 0;
}
9.6 結構體內存分配
1、結構體內存分配
之前講過結構體變量大小是,它所有成員的大小之和。
因為結構體變量是所有成員的集合。
例 17:
#include
struct stu{
int num;
int age;
}lucy;
int main()
{
printf("%d\n",sizeof(lucy));//結果為 8
return 0;
}
但是在實際給結構體變量分配內存的時候,是規則的
例 18:
11
千鋒智能物聯網+嵌入式學科
#include
struct stu{
char sex;
int age;
}lucy;
int main()
{
printf("%d\n",sizeof(lucy));//結果為 8???
return 0;
}
規則 1:以多少個字節為單位開辟內存
給結構體變量分配內存的時候,會去結構體變量中找基本類型的成員
哪個基本類型的成員占字節數多,就以它大大小為單位開辟內存,
在 gcc中出現了 double類型的,例外
(1):成員中只有 char型數據 ,以 1字節為單位開辟內存。
(2):成員中出現了 short int 類型數據,沒有更大字節數的基本類型數據。
以 2字節為單位開辟內存
(3):出現了 int float 沒有更大字節的基本類型數據的時候以 4字節為單位開辟內存。
(4):出現了 double類型的數據
情況 1:
在 vc6.0和 Visual Studio中里,以 8字節為單位開辟內存。
情況 2:
在 Linux 環境 gcc 里,以 4字節為單位開辟內存。
無論是那種環境,double型變量,占 8字節。
注意:
如果在結構體中出現了數組,數組可以看成多個變量的集合。
如果出現指針的話,沒有占字節數更大的類型的,以 4字節為單位開辟內存。
在內存中存儲結構體成員的時候,按定義的結構體成員的順序存儲。
例 19:struct stu{
char sex;
int age;
}lucy;
lucy 的大小是 4的倍數。
規則 2:字節對齊
(1):char 1字節對齊 ,即存放 char型的變量,內存單元的編號是 1的倍數即可。
(2):short int 2字節對齊 ,即存放 short int 型的變量,起始內存單元的編號是 2的倍數即可。
12
千鋒智能物聯網+嵌入式學科
(3):int 4字節對齊 ,即存放 int 型的變量,起始內存單元的編號是 4的倍數即可
(4):long int 在 32位平臺下,4字節對齊 ,即存放 long int 型的變量,起始內存單元的編號是 4
的倍數即可
(5):float 4字節對齊 ,即存放 float 型的變量,起始內存單元的編號是 4的倍數即可
(6):double
a.vc6.0和 Visual Studio 環境下
8字節對齊,即存放 double型變量的起始地址,必須是 8的倍數,double變量占 8字節
b.gcc環境下
4字節對齊,即存放 double型變量的起始地址,必須是 4的倍數,double 變量占 8字節。
注意 3:當結構體成員中出現數組的時候,可以看成多個變量。
注意 4:開辟內存的時候,從上向下依次按成員在結構體中的位置順序開辟空間
例 20://temp 8個字節
#include
struct stu{
char a;
short int b;
int c;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
printf("%p\n",&(temp.c));
return 0;
}
結果分析:
a的地址和 b的地址差 2個字節
b的地址和 c的地址差 2個字節
例 21:temp 的大小為 12 個字節
#include
struct stu{
char a;
int c;
short int b;
}temp;
int main()
{
13
千鋒智能物聯網+嵌入式學科
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
printf("%p\n",&(temp.c));
return 0;
}
結果分析:
a和 c的地址差 4個字節
c和 b的地址差 4個字節
例 22:
struct stu{
char buf[10];
int a;
}temp;
//temp 占 16 個字節
例 23:
在 vc和 Visual Studio 中占 16 個字節 a和 b的地址差 8個字節
在 gcc 中占 12 個字節 a和 b的地址差 4個字節
#include
struct stu{
char a;
double b;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}
為什么要有字節對齊?
用空間來換時間,提高 cpu讀取數據的效率
struct stu{
char a;
int b;
}boy;
14
千鋒智能物聯網+嵌入式學科
存儲方式 1 存儲方式 2
指定對齊原則:
使用#pragma pack改變默認對齊原則
格式:
#pragma pack (value)時的指定對齊值value。
注意:
1.value只能是:1 2 4 8等
2.指定對齊值與數據類型對齊值相比取較小值
說明:咱們指定一個value
(1):以多少個字節為單位開辟內存
結構體成員中,占字節數最大的類型長度和value比較,
取較小值,為單位開辟內存
例 24:
#pragma pack(2)
struct stu{
char a;
int b;
} ;
以2個字節為單位開辟內存
#include
#pragma pack(2)
struct stu{
char a;
int b;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
15
千鋒智能物聯網+嵌入式學科
return 0;
}
temp的大小為6個字節
a和b的地址差2個字節
例 25:
#pragma pack(8)
struct stu{
char a;
int b;
} ;
以4個字節為單位開辟內存
#include
#pragma pack(8)
struct stu{
char a;
int b;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}
temp的大小為8個字節
a和b的地址差4個字節
(2):字節對齊
結構體成員中成員的對齊方法,各個默認的對齊字節數和value相比,
取較小值
例 26:
#include
#pragma pack(2)
struct stu{
char a;
int b;
}temp;
int main()
{
16
千鋒智能物聯網+嵌入式學科
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}
b成員是2字節對齊,a和b的地址差2個字節
例 27:
#include
#pragma pack(8)
struct stu{
char a;
int b;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}
a和b都按原先的對齊方式存儲
如:如果指定對齊值:
設為1:則short、int、float等均為1
設為 2:則 char仍為 1,short為 2,int 變為 2
9.7 位段
一、位段
在結構體中,以位為單位的成員,咱們稱之為位段(位域)。
struct stu{
unsigned int a:2;
unsigned int b:6;
unsigned int c:4;
unsigned int d:4;
unsigned int i;
} data;
注意:不能對位段成員取地址
17
千鋒智能物聯網+嵌入式學科
例 28:
#include
struct stu{
unsigned int a:2;
unsigned int b:6;
unsigned int c:4;
unsigned int d:4;
unsigned int i;
} data;
int main()
{
printf("%d\n",sizeof(data));
printf("%p\n",&data);
printf("%p\n",&(data.i));
return 0;
}
位段注意:
1、對于位段成員的引用如下:
data.a =2
賦值時,不要超出位段定義的范圍;
如段成員a定義為2位,最大值為3,即(11)2
所以data.a =5,就會取5的低兩位進行賦值 101
2、位段成員的類型必須指定為整型或字符型
3、一個位段必須存放在一個存儲單元中,不能跨兩個單元
第一個單元空間不能容納下一個位段,則該空間不用,
而從下一個單元起存放該位段
位段的存儲單元:
(1):char型位段 存儲單元是 1個字節
(2):short int型的位段存儲單元是 2個字節
(3):int的位段,存儲單元是 4字節
(4):long int的位段,存儲單元是 4字節
struct stu{
char a:7;
char b:7;
char c:2;
}temp;//占 3字節,b不能跨 存儲單元存儲
例 29:
#include
18
千鋒智能物聯網+嵌入式學科
struct stu{
char a:7;
char b:7;
char c:2;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
return 0;
}
結果為:3 ,證明位段不能跨其存儲單元存儲
注意:不能 取 temp.b的地址,因為 b可能不夠 1字節,不能取地址。
4、位段的長度不能大于存儲單元的長度
(1):char型位段不能大于 8位
(2):short int型位段不能大于 16位
(3):int的位段,位段不能大于 32位
(4):long int的位段,位段不能大于 32位
例 30:
#include
struct stu{
char a:9;
char b:7;
char c:2;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
return 0;
}
分析:
編譯出錯,位段 a不能大于其存儲單元的大小
5、如一個段要從另一個存儲單元開始,可以定義:
unsigned char a:1;
unsigned char b:2;
unsigned char :0;
unsigned char c:3;(另一個單元)
由于用了長度為 0的位段,其作用是使下一個位段從
19
千鋒智能物聯網+嵌入式學科
下一個存儲單元開始存放
將 a、b存儲在一個存儲單元中,c另存在下一個單元
例:31
#include
struct stu{
unsigned char a:1;
unsigned char b:2;
unsigned char :0;
unsigned char c:3;
};
int main()
{
struct m_type temp;
printf("%d\n",sizeof(temp));
return 0;
}
6、可以定義無意義位段,如:
unsigned a: 1;
unsigned : 2;
unsigned b: 3;
9.8 共用體
1:共用體和結構體類似,也是一種構造類型的數據結構。
既然是構造類型的,咱們得先定義出類型,然后用類型定義變量。
定義共用體類型的方法和結構體非常相似,把 struct 改成 union 就可以了。
在進行某些算法的時候,需要使幾種不同類型的變量存到同一段內存單元中,幾個變量所使用空
間相互重疊
這種幾個不同的變量共同占用一段內存的結構,在C語言中,被稱作“共用體”類型結構
共用體所有成員占有同一段地址空間
共用體的大小是其占內存長度最大的成員的大小
例 33:
typedef struct data{
short int i;
char ch;
float f;
}DATA;
DATA temp1;
20
千鋒智能物聯網+嵌入式學科
結構體變量 temp1最小占 7個字節(不考慮字節對齊)
例 34:
typedef union data{
short int i;
char ch;
float f;
}DATA;
DATA temp2;
共用體 temp2占 4個字節,即 i、ch、f共用 4個字節
#include
typedef union data{
short int i;
char ch;
float f;
}DATA;
int main()
{
DATA temp2;
printf("%d\n",sizeof(temp2));
printf("%p\n",&temp2);
printf("%p\n",&(temp2.i));
printf("%p\n",&(temp2.ch));
printf("%p\n",&(temp2.f));
return 0;
}
結果:temp2的大小為 4個字節,下面幾個地址都是相同的,證明了共用體的各個成員占用同一塊內存。
共用體的特點:
1、同一內存段可以用來存放幾種不同類型的成員,但每一瞬時只有一種起作用
2、共用體變量中起作用的成員是最后一次存放的成員,在存入一個新的成員后原有的成員的值會被
覆蓋
3、共用體變量的地址和它的各成員的地址都是同一地址
4、共用體變量的初始化
union data a={123}; 初始化共用體只能為第一個成員賦值,不能給所有成員都賦初值
例 35:
#include
typedef union data{
21
千鋒智能物聯網+嵌入式學科
unsigned char a;
unsigned int b;
}DATA;
int main()
{
DATA temp;
temp.b=0xffffffff;
printf("temp.b = %x\n",temp.b);
temp.a=0x0d;
printf("temp.a= %x\n",temp.a);
printf("temp.b= %x\n",temp.b);
return 0;
}
結果:
temp.b = ffffffff
temp.a= d
temp.b= ffffff0d
9.9 枚舉
將變量的值一一列舉出來,變量的值只限于列舉出來的值的范圍內
枚舉類型也是個構造類型的,類型定義類似結構體類型的定義。
使用枚舉的時候,得先定義枚舉類型,再定義枚舉變量
1、枚舉類型的定義方法
enum 枚舉類型名{
枚舉值列表;
};
在枚舉值表中應列出所有可用值,也稱為枚舉元素
枚舉元素是常量,默認是從 0開始編號的。
枚舉變量僅能取枚舉值所列元素
2、枚舉變量的定義方法
enum 枚舉類型名 枚舉變量名;
例 37:
定義枚舉類型 week
enum week //枚舉類型
22
千鋒智能物聯網+嵌入式學科
{
mon,tue,wed,thu,fri,sat,sun
};
enum week workday,weekday;//枚舉變量
workday 與 weekday 只能取 sun….sat 中的一個
workday = mon; //正確
weekday = tue; //正確
workday = abc; //錯誤,枚舉值中沒有 abc
① 枚舉值是常量,不能在程序中用賦值語句再對它賦值
例如:sun=5; mon=2; sun=mon; 都是錯誤的.
② 枚舉元素本身由系統定義了一個表示序號的數值
默認是從0開始順序定義為0,1,2…
如在week中,mon值為0,tue值為1, …,sun值為6
③ 可以改變枚舉值的默認值:如
enum week //枚舉類型
{
mon=3,tue,wed,thu,fri=4,sat,sun
};
mon=3 tue=4,以此類推
fri=4 以此類推
注意:在定義枚舉類型的時候枚舉元素可以用等號給它賦值,用來代表元素從幾開始編號
在程序中,不能再次對枚舉元素賦值,因為枚舉元素是常量。
23千鋒智能物聯網+嵌入式學科
第 1章 環境搭建
第 2章 c數據類型及語句
第 3章 數組
第 4章 函數
第 5章 預處理、動態庫、靜態庫
第 6章 指針
第 7章 動態內存申請
第 8章 字符串處理函數
第 9章 結構體、共用體、枚舉
第 10章 文件
10.1 文件的概念
凡是使用過文件的人對文件都不會感到陌生
1
千鋒智能物聯網+嵌入式學科
文件用來存放程序、文檔、音頻、視頻數據、圖片等數據的。
文件就是存放在磁盤上的,一些數據的集合。
在 windows下可以通過寫字板或記事本打開文本文件對文件進行編輯保存。寫字板和記事本是微軟程序員
寫的程序,對文件進行打開、顯示、讀寫、關閉。
作為一個程序員,必須掌握編程實現創建、寫入、讀取文件等操作
對文件的操作是經常要用到的知識,比如:寫飛秋軟件傳送文件 等
10.1.1 文件的分類:
磁盤文件:(我們通常認識的文件)
指一組相關數據的有序集合,通常存儲在外部介質(如磁盤)上,使用時才調入內存。
設備文件:
在操作系統中把每一個與主機相連的輸入、輸出設備看作是一個文件,把它們的輸入、輸出等同于
對磁盤文件的讀和寫。
鍵盤:標準輸入文件 屏幕:標準輸出文件
其它設備:打印機、觸摸屏、攝像頭、音箱等
在 Linux操作系統中,每一個外部設備都在/dev目錄下對應著一個設備文件,咱們在程序中要想操作設備,
就必須對與其對應的/dev下的設備文件進行操作。
標準 io庫函數對磁盤文件的讀取特點
文件緩沖區是庫函數申請的一段內存,由庫函數對其進行操作,程序員沒有必要知道存放在哪里,只需要
知道對文件操作的時候的一些緩沖特點即可。
VS中對普通文件的讀寫是全緩沖的。
全緩沖
標準 io庫函數 ,往普通文件讀寫數據的,是全緩沖的,
刷新緩沖區的情況
2
千鋒智能物聯網+嵌入式學科
1.緩沖區滿了,刷新緩沖區
2.調用函數刷新緩沖區 fflush(文件指針)
3.程序結束 會刷新緩沖區
10.1.2 磁盤文件的分類:
一個文件通常是磁盤上一段命名的存儲區
計算機的存儲在物理上是二進制的,所以物理上所有的磁盤文件本質上都是一樣的:
以字節為單位進行順序存儲
從用戶或者操作系統使用的角度(邏輯上)
把文件分為:
文本文件:基于字符編碼的文件
二進制文件:基于值編碼的文件
文本文件
基于字符編碼,常見編碼有 ASCII、UNICODE等
一般可以使用文本編輯器直接打開
例如:5678的以 ASCII存儲形式為:
ASCII碼:00110101 00110110 00110111 00111000
二進制碼文件:
基于值編碼,根據具體應用,指定某個值是什么意思
一般需要自己判斷或使用特定軟件分析數據格式
例如:數 5678的存儲形式為:
二進制碼:00010110 00101110
音頻文件(mp3):二進制文件
圖片文件(bmp)文件,一個像素點由兩個字節來描述*****######&&&&&, 5 6 5
*代表紅色的值 R
#代表綠色的值 G
&代表藍色的值 B
二進制文件以位來表示一個意思。
文本文件、二進制文件對比:
譯碼:
文本文件編碼基于字符定長,譯碼容易些;
二進制文件編碼是變長的,譯碼難一些(不同的二進制文件格式,有不同的譯碼方式,一般需要特定
軟件進行譯碼)。
空間利用率:
二進制文件用一個比特來代表一個意思(位操作);
而文本文件任何一個意思至少是一個字符。
所以二進制文件,空間利用率高。
3
千鋒智能物聯網+嵌入式學科
可讀性:
文本文件用通用的記事本工具就幾乎可以瀏覽所有文本文件
二進制文件需要一個具體的文件解碼器,比如讀 BMP文件,必須用讀圖軟件。
總結:
1、文件在硬盤上存儲的時候,物理上都是用二進制來存儲的。
2、咱們的標準 io庫函數,對文件操作的時候,不管文件的編碼格式(字符編碼、或二進制),而是
按字節對文件進行讀寫,所以咱們管文件又叫流式文件,即把文件看成一個字節流。
10.2 文件指針
文件指針在程序中用來標識(代表)一個文件的,在打開文件的時候得到文件指針,
文件指針就用來代表咱們打開的文件。
咱們對文件進行讀、寫、關閉等操作的時候,對文件指針進行操作即可,即咱們將文件指針,傳給讀、
寫、關閉等函數,那些函數就知道要對哪個文件進行操作。
定義文件指針的一般形式為:
FILE * 指針變量標識符;
FILE 為大寫,需要包含
FILE 是系統使用 typedef定義出來的有關文件信息的一種結構體類型,結構中含有文件名、文件
狀態和文件當前位置等信息
一般情況下,我們操作文件前必須定義一個文件指針標識 我們將要操作的文件
實際編程中使用庫函數操作文件,無需關心 FILE 結構體的細節 ,只需要將文件指針傳給 io庫函數,
庫函數再通過 FILE結構體里的信息對文件進行操作
FILE在 stdio.h文件中的文件類型聲明:
typedef struct
{ short level; //緩沖區“滿”或“空”的程度
unsigned flags; //文件狀態標志
char fd; //文件描述符
unsigned charhold; //如無緩沖區不讀取字符
short bsize; //緩沖區的大小
unsigned char *buffer; //數據緩沖區的位置
unsigned ar*curp; //指針,當前的指向
unsigned istemp; //臨時文件,指示器
shorttoken; //用于有效性檢查
}FILE;
在緩沖文件系統中,每個被使用的文件都要在內存中開辟一塊
FILE 類型的區域,存放與操作文件相關的信息
4
千鋒智能物聯網+嵌入式學科
對文件操作的步驟:
1、對文件進行讀寫等操作之前要打開文件得到文件指針
2、可以通過文件指針對文件進行讀寫等操作
3、讀寫等操作完畢后,要關閉文件,關閉文件后,就不能再通過此文件指針操作文件了
補充:
c語言中有三個特殊的文件指針無需定義,在程序中可以直接使用
stdin: 標準輸入 默認為當前終端(鍵盤)
我們使用的 scanf、getchar函數默認從此終端獲得數據
stdout:標準輸出 默認為當前終端(屏幕)
我們使用的 printf、puts函數默認輸出信息到此終端
stderr:標準錯誤輸出設備文件 默認為當前終端(屏幕)
當我們程序出錯使用:perror函數時信息打印在此終端
總結:
文件指針是個指針,它是個 FILE 類型結構體指針,用文件指針來標識一個文件。
10.3 打開文件 fopen
函數的聲明:
FILE *fopen(const char *path, const char *mode);
函數說明:
fopen函數的功能是打開一個已經存在的文件,并返回這個文件的文件指針(文件的標識)
或者創建一個文件,并打開此文件,然后返回文件的標識。
函數的參數:
參數 1:打開的文件的路徑
1. 絕對路徑,從根目錄開始的路徑名稱
“D:\\demo\\test\\aaa.txt”
2. 相對路徑
.\\test\\aaa.txt
參數 2:文件打開的方式,即以什么樣的方式(只讀、只寫、可讀可寫等等)打開文件
5
千鋒智能物聯網+嵌入式學科
第二個參數的幾種形式(打開文件的方式)
讀寫權限:r w a +
r:以只讀方式打開文件
文件不存在返回 NULL;
文件存在,且打開文件成功,返回文件指針,進行后續的讀操作
例 1:
FILE *fp;
fp=fopen(“test.txt”,”r”);
w:以只寫方式打開文件
1、文件不存在,以指定文件名創建此文件,并且打開文件;
2、若文件存在,清空文件內容,打開文件,然后進行寫操作;
3、如果文件打不開(比如文件只讀),返回 NULL
FILE *fp;
fp=fopen(“test.txt”,”w”);
a:以追加方式打開文件
1、文件不存在,以指定文件名創建此文件(同 w)
2、若文件存在,從文件的結尾處進行寫操作
說明:
如果不加 a的話,打開文件的時候讀寫位置在文件的開始,對文件進行讀寫的時候都是從文件開
始進行讀寫的。
如果加 a,打開已經存在的文件,讀寫位置在文件的末尾。
+:同時以讀寫打開指定文件
模 式 功 能
r或 rb 以只讀方式打開一個文本文件(不創建文件)
w或 wb 以寫方式打開文件(使文件長度截斷為 0字節,創建一個文件)
a或 ab 以追加方式打開文件,即在末尾添加內容,當文件不存在時,創建文件用于寫
6
千鋒智能物聯網+嵌入式學科
r+或 rb+ 以可讀、可寫的方式打開文件(不創建新文件)
w+或 wb+ 以可讀、可寫的方式打開文件
(使文件長度為 0字節,創建一個文件)
a+或 ab+ 以追加方式打開文件,打開文件并在末尾更改文件(如果文件不存在,則創建文件)
返回值:
成功:打開的文件對應的文件指針
失?。悍祷?NULL
以后調用 fopen函數的時候,一定要判斷一下,打開是否成功。
10.4 關閉文件 fclose
函數的頭文件:
#include
函數的聲明:
int fclose(FILE *fp);
函數的說明:
關閉 fp所代表的文件
注意一個文件只能關閉一次,不能多次關閉。關閉文件之后就不能再文件指針對文件進行讀寫等操作了。
返回值:
成功返回 0
失敗返回非 0
可以通過返回值,來判斷關閉文件是否成功。
例 6:
#include
int main()
{
FILE *fp;
int ret;
fp=fopen(".\\test.txt","r+");
if(fp==NULL)
7
千鋒智能物聯網+嵌入式學科
{
perror("fopen");
return 0;
}
printf("打開文件成功\n");
ret=fclose(fp);
if(ret==0)
printf("關閉文件成功\n");
else
printf("關閉文件失敗");
return 0;
}
10.5 一次讀寫一個字符
函數聲明:
int fgetc(FILE *stream);
函數說明:
fgetc從 stream所標識的文件中讀取一個字節,將字節值返回
返回值:
以 t的方式: 讀到文件結尾返回 EOF
以 b的方式:讀到文件結尾,使用 feof(文件指針)判斷結尾
feof 是 C語言標準庫函數,其原型在 stdio.h中,其功能是檢測流上的文件結束符,如果文件結束,
則返回非 0值,否則返回 0(即,文件結束:返回非 0值;文件未結束:返回 0值)。
函數的聲明:
int fputc(int c, FILE *stream)
函數的說明:
fputc將 c的值寫到 stream所代表的文件中。
返回值:
如果輸出成功,則返回輸出的字節值;
如果輸出失敗,則返回一個 EOF。
EOF是在 stdio.h文件中定義的符號常量,值為-1
注意:打開文件的時候,默認讀寫位置在文件的開始,如果以 a的方式打開讀寫位置在文件的末尾
咱們向文件中讀取字節或寫入字節的時候,讀寫位置會往文件的末尾方向偏移,讀寫多少個字節,讀寫
位置就往文件的末尾方向偏移多少個字節
例 7:
#include
int main(void)
8
千鋒智能物聯網+嵌入式學科
{
FILE *fp;
char ch;
fp=fopen("test.txt","r+");
if(fp==NULL)
{
printf("Cannot open the file\n");
return 0;
}
while( (ch = fgetc(fp))!=EOF )
{
fputc(ch,stdout);
}
fclose(fp);
return 0;
}
10.6 一次讀寫一個字符串
char *fgets(char *s, int size, FILE *stream);
從 stream所代表的文件中讀取字符,在讀取的時候碰到換行符或者是碰到文件的末尾停止讀取,
或者是讀取了 size-1個字節停止讀取,在讀取的內容后面會加一個\0,作為字符串的結尾
返回值:
成功返回目的數組的首地址,即 s
失敗返回 NULL
int fputs(const char *s, FILE *stream);
函數功能:
將 s指向的字符串,寫到 stream所代表的文件中
返回值:
成功返回寫入的字節數
失敗返回 -1
例 9:
#include
int main(void)
9
千鋒智能物聯網+嵌入式學科
{
FILE *fp_read,*fp_write;
char string1[100];
if((fp_read=fopen("src.txt","r+"))==NULL)
{
printf("Cannot open the file\n");
return 0;
}
if((fp_write=fopen("dest.txt","w+"))==NULL)
{
printf("Cannot open the file\n");
return 0;
}
fgets(string1, 100, fp_read);
printf("%s\n",string1);
fputs(string1,fp_write);
fclose(fp_read);
fclose(fp_write);
return 0;
}
10.7 讀文件 fread
函數的聲明:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
函數的說明:
fread 函數從 stream 所標識的文件中讀取數據,每塊是 size 個字節,共 nmemb 塊,存放到 ptr指向的
內存里
返回值:
實際讀到的塊數。
例 1:
unsigned int num;
num=fread(str,100,3,fp);
從 fp所代表的文件中讀取內容存放到 str指向的內存中,讀取的字節數為 ,每塊 100個字節,3塊。
返回值 num,
如果讀到 300個字節返回值 num為 3
如果讀到了大于等于 200個字節小于 300個字節 返回值為 2
讀到的字節數,大于等于 100個字節小于 200個字節 返回 1
不到 100個字節返回 0
10
千鋒智能物聯網+嵌入式學科
10.8 寫文件 fwrite
函數的聲明:
size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream);
函數的說明:
fwrite 函數將 ptr指向的內存里的數據,向 stream 所標識的文件中寫入數據,每塊是 size 個字節,共
nmemb 塊。
返回值:
實際寫入的塊數
例 10:
#include
struct stu
{
char name[10];
int num;
int age;
}boya[10],boyb[2];
int main()
{
FILE *fp;
int i;
if((fp=fopen("test.txt","wb+"))==NULL)
{
printf("Cannot open file!");
return 0;
}
printf("input data\n");
printf("name、num、age\n");
for(i=0;i<2;i++)
scanf("%s %d %d",boya[i].name,&boya[i].num,&boya[i].age);
fwrite(boya,sizeof(struct stu),2,fp); //將學生信息寫入文件中
rewind(fp); //文件指針經過寫操作已經到了最后,需要復位
fread(boyb,sizeof(struct stu),2,fp); //將文件中的數據讀入到內存中
for(i=0;i<2;i++)
printf("%s %d %d\n",boyb[i].name,boyb[i].num,boyb[i].age);
11
千鋒智能物聯網+嵌入式學科
fclose(fp);
return 0;
}
注意:
fwrite函數是將內存中的數據原樣輸出到文件中。
fread函數是將文件中的數據原樣讀取到內存里。
10.9 隨機讀寫
前面介紹的對文件的讀寫方式都是順序讀寫,即讀寫文件只能從頭開始,順序讀寫各個數據;
但在實際問題中常要求只讀寫文件中某一指定的部分,例如:讀取文件第 200--300個字節
為了解決這個問題可以移動文件內部的位置指針到需要讀寫的位置,再進行讀寫,這種讀寫稱為隨機
讀寫
實現隨機讀寫的關鍵是要按要求移動位置指針,這稱為文件的定位.
完成文件定位的函數有:
rewind、fseek函數
1、 rewind 復位讀寫位置
rewind函數
void rewind(文件指針);
函數功能:
把文件內部的位置指針移到文件首
調用形式:
rewind(文件指針);
例 12:
fwrite(pa,sizeof(struct stu),2,fp );
rewind(fp);
fread( pb,sizeof(struct stu),2,fp);
2、 ftell 測文件讀寫位置距文件開始有多少個字節
定義函數:
long ftell(文件指針);
函數功能:
取得文件流目前的讀寫位置.
返回值:
返回當前讀寫位置(距離文件起始的字節數),出錯時返回-1.
例如:
long int length;
length = ftell(fp);
12
千鋒智能物聯網+嵌入式學科
3、 fseek 定位位置指針(讀寫位置)
fseek函數(一般用于二進制文件即打開文件的方式需要帶 b)
函數聲明:
int fseek(FILE *stream, long offset, int whence);
//int fseek(文件類型指針,位移量,起始點);
函數功能:
移動文件流的讀寫位置.
參數:
whence起始位置
文件開頭 SEEK_SET 0
文件當前位置 SEEK_CUR 1
文件末尾 SEEK_END 2
位移量:
以起始點為基點,向前、后移動的字節數,正數往文件末尾方向偏移,負數往文件開頭方向
偏移。
例 13:
fseek(fp,50,SEEK_SET)
fseek(fp,-50,SEEK_END);
fseek(fp,0,SEEK_END);
fseek(fp,20,SEEK_CUR);
練習:
將一個未知大小的文件(文本文件)全部讀入內存,并顯示在屏幕上
參考:fseek ftell rewind fread malloc
1、打開文件 fopen ,注意用 b的方式打開
2、定位文件的讀寫位置到文件的末尾 fseek
3、測文件的字節數 len ftell
4、復位讀寫位置到文件的開始 rewind
5、根據第 3步得到的字節數,申請內存 malloc 注意多申請一個字節存放’\0’
6、從文件中讀取內容,存到申請的空間里 fread
7、最后一個字節變成 ‘\0’
8、打印讀出來的內容到屏幕上 ,printf
9、關閉文件 fclose
10、釋放內存 free
13千鋒智能物聯網+嵌入式學科
第 1 章 環境搭建
第 2 章 c 數據類型及語句
第 3 章 數組
3.1 數組的概念
數組是若干個相同類型的變量在內存中有序存儲的集合。
int a[10];//定義了一個整型的數組 a,a是數組的名字,數組中有 10個元素,每個元素的類型
都是 int類型,而且在內存中連續存儲。
這十個元素分別是 a[0] a[1] …. a[9]
a[0]~a[9]在內存中連續的順序存儲
3.2 數組的分類
3.2.1 按元素的類型分類
1)字符數組
即若干個字符變量的集合,數組中的每個元素都是字符型的變量
char s[10]; s[0],s[1]....s[9];
2)短整型的數組
short int a[10]; a[0] ,a[9]; a[0]=4;a[9]=8;
3)整型的數組
int a[10]; a[0] a[9]; a[0]=3;a[0]=6;
4) 長整型的數組
lont int a[5];
5)浮點型的數組(單、雙)
float a[6]; a[4]=3.14f;
double a[8]; a[7]=3.115926;
6)指針數組
char *a[10]
int *a[10];
7)結構體數組
struct stu boy[10];
1
千鋒智能物聯網+嵌入式學科
3.2.2 按維數分類
一維數組
int a[30];
類似于一排平房
二維數組
int a[2][30];
可以看成一棟樓房 有多層,每層有多個房間,也類似于數學中的矩陣
二維數組可以看成由多個一維數組構成的。
有行,有列,
多維數組
int a[4][2][10];
三維數組是由多個相同的二維數組構成的
int a[5][4][2][10];
3.3 數組的定義
定義一個數組,在內存里分配空間
3.3.1 一維數組的定義
格式:
數據類型 數組名 [數組元素個數];
int a [10];
char b [5];定義了 5個 char類型變量的數組 b
5個變量分別為 b[0] ,b[1],b[2],b[3],b[4];
在數組定義的時候可以不給出數組元素的個數,根據初始化的個數來定數組的大小
例 1:
#include
int main(int argc, char *argv[])
{
int a[]={1,2,3,4,5};
printf("%d\n",sizeof(a));
return 0;
}
3.3.2 二維數組的定義
格式:
數據類型 數組名 [行的個數][列的個數];
int a [4][5];
定義了 20個 int類型的變量 分別是
a[0][0] ,a[0][1],a[0][2] ,a[0][3] ,a[0][4];
2
千鋒智能物聯網+嵌入式學科
a[1][0] ,a[1][1],a[1][2] ,a[1][3] ,a[1][4];
a[2][0] ,a[2][1],a[2][2] ,a[2][3] ,a[2][4];
a[3][0] ,a[3][1],a[3][2] ,a[3][3] ,a[3][4];
多維數組定義:
int a[3][4][5]
int a[8][3][4][5];
擴展:
二維數組在定義的時候,可以不給出行數,但必須給出列數,二維數組的大小根據初始化的行數來定
例 2:
#include
int main(int argc, char *argv[])
{
int a[][3]={
{1,2,3},
{4,5,6},
{7,8,9},
{10,11,12}
};
printf("%d\n",sizeof(a));
return 0;
}
3.4 數組的初始化
定義數組的時候,順便給數組的元素賦初值,即開辟空間的同時并且給數組元素賦值
3.4.1 一維數組的初始化
a、全部初始化
int a[5]={2,4,7,8,5};
代表的意思: a[0]=2; a[1]=4;a[2]=7;a[3] = 8;a[4]=5;
b、部分初始化
int a[5]={2,4,3};初始化賦值不夠后面補 0
a[0] = 2; a[1]= 4;a[2]=3;a[3]=0;a[4]=0;
注意:只能省略后面元素,可以不初始化,不能中間的不初始化
例 3:
#include
int main(int argc, char *argv[])
3
千鋒智能物聯網+嵌入式學科
{
int a[5]={2,3,5};
int i;
for(i=0;i<5;i++)
{
printf("a[%d]=%d\n",i,a[i]);
}
return 0;
}
3.4.2 二維數組的定義并初始化
按行初始化:
a、全部初始化
int a[2][2]={{1,2},{4,5}};
a[0][0] =1; a[0][1] = 2; a[1][0] = 4,a[1][1]=5;
b、部分初始化
int a[3][3]={{1,2},{1}};
a[0][0] = 1;a[0][2] =0;
逐個初始化:
全部初始化:
int a [2][3]={2,5,4,2,3,4};
部分初始化:
int a[2][3]={3,5,6,8};
3.5 數組元素的引用方法
3.5.1 一維數組元素的引用方法
數組名 [下標];//下標代表數組元素在數組中的位置
int a[5];
a[0] a[1] a[2] a[3] a[4];
3.5.2 二維數組元素的引用方法
數組名[行下標][列下標];
int a [4][5];
a[0][0] ,a[0][1],a[0][2] ,a[0][3] ,a[0][4];
a[1][0] ,a[1][1],a[1][2] ,a[1][3] ,a[1][4];
a[2][0] ,a[2][1],a[2][2] ,a[2][3] ,a[2][4];
a[3][0] ,a[3][1],a[3][2] ,a[3][3] ,a[3][4];
4
千鋒智能物聯網+嵌入式學科
例 4:
#include
int main(int argc, char *argv[])
{
int a[3][4]={{1,2,3,4},{5,6},{5}};
int b[3][4]={11,12,13,14,15,16,17,18,19};
int i,j;
for(i=0;i<3;i++)//遍歷所有行
{
for( j=0;j<4;j++)//遍歷一行的所有列
{
printf("a[%d][%d]=%d ",i,j,a[i][ j]);
}
printf("\n");
}
for(i=0;i<3;i++)//遍歷所有行
{
for( j=0;j<4;j++)//遍歷一行的所有列
{
printf("b[%d][%d]=%d ",i,j,b[i][ j]);
}
printf("\n");
}
return 0;
}
3.5.3 字符數組
char c1[] ={‘c’,’ ’,’p’,’r’,’o’,’g’};
char c2[] = “c prog”;
char a[][5] = {
{‘B’,’A’,’S’,’I’,’C’},
{‘d’,’B’,’A’,’S’,’E’}
};
char a[][6] = {“hello”,“world”};
字符數組的引用
1.用字符串方式賦值比用字符逐個賦值要多占 1
個字節,用于存放字符串結束標志‘\0’;
5
千鋒智能物聯網+嵌入式學科
2.上面的數組 c2在內存中的實際存放情況為:
注:'\0'是由 C編譯系統自動加上的
3.由于采用了'\0'標志,字符數組的輸入輸出將
變得簡單方便.
例 5:
int main( )
{
char str[15];
printf("input string:\n");
scanf_s("%s",str);//hello
printf("output:%s\n",str);
return 0;
}
6千鋒智能物聯網+嵌入式學科
第 1章 環境搭建
第 2章 c數據類型及語句
第 3章 數組
第 4章 函數
4.1 函數的概念
函數是 c語言的功能單位,實現一個功能可以封裝一個函數來實現。
定義函數的時候一切以功能為目的,根據功能去定函數的參數和返回值。
4.2 函數的分類
1、從定義角度分類(即函數是誰實現的)
1.庫函數 (c庫實現的)
2.自定義函數 (程序員自己實現的函數)
3.系統調用 (操作系統實現的函數)
2、從參數角度分類
1.有參函數
函數有形參,可以是一個,或者多個,參數的類型隨便
完全取決于函數的功能
int fun(int a,float b,double c)
{
}
int max(int x,int y)
{
}
2.無參函數
函數沒有參數,在形參列表的位置寫個 void或什么都不寫
int fun(void)
{
1
千鋒智能物聯網+嵌入式學科
}
int fun()
{
}
3、從返回值角度分類
(1).帶返回值的函數
在定義函數的時候,必須帶著返回值類型,在函數體里,必須有 return
如果沒有返回值類型,默認返回整型。
例 1:
char fun()//定義了一個返回字符數據的函數
{
char b='a';
return b;
}
例 2:
fun()
{
return 1;
}
如果把函數的返回值類型省略了,默認返回整型
注:在定義函數的時候,函數的返回值類型,到底是什么類型的,取決于函數的功能。
(2).沒返回值的函數
在定義函數的時候,函數名字前面加 void
void fun(形參表)
{
;
;
return ;
;
}
在函數里不需要 return
如果想結束函數,返回到被調用的地方, return ;什么都不返回就可以了
例 3:
#include
int max(int x,int y)
{
2
千鋒智能物聯網+嵌入式學科
int z;
if(x>y)
z=x;
else
z=y;
return z;
}
void help(void)
{
printf("*********************\n");
printf("********幫助信息*****\n");
printf("*********************\n");
}
int main(int argc, char *argv[])
{
int num;
help();
num = max(10,10+5);
printf("num=%d\n",num);
return 0;
}
4.3 函數的定義
什么叫做函數的定義呢?即函數的實現
1、函數的定義方法
返回值類型 函數名字(形參列表)
{//函數體,函數的功能在函數體里實現
}
例 4:
int max(int x, int y)
{
int z;
if(x>y)
z=x;
else
z=y;
3
千鋒智能物聯網+嵌入式學科
return z;
}
注:形參必須帶類型,而且以逗號分隔
函數的定義不能嵌套,即不能在一個函數體內定義另外一個函數,
所有的函數的定義是平行的。
例 5:
void fun(void)
{
;
;
;
void fun2(void)
{
;
}
}
這個程序是錯誤的,不能再 fun的函數體中,定義 fun2函數。
例 6:
void fun(void)
{
;
;
;
}
void fun2(void)
{
;
}
這個程序是正確的,fun和 fun2是平行結構
注:在一個程序中,函數只能定義一次
給函數起名字的時候,盡量的見名知意,符合 c語言的命名規則
4
千鋒智能物聯網+嵌入式學科
4.4 函數的聲明
1、概念
對已經定義的函數,進行說明
函數的聲明可以聲明多次。
2、為什么要聲明
有些情況下,如果不對函數進行聲明,編譯器在編譯的時候,可能不認識這個函數,
因為編譯器在編譯 c程序的時候,從上往下編譯的。
3、聲明的方法
什么時候需要聲明
1)主調函數和被調函數在同一個.c文件中的時候
1] 被調函數在上,主調函數在下
例 7:
void fun(void)
{
printf("hello world\n");
}
int main()
{
fun();
}
這種情況下不需要聲明
2] 被調函數在下,主調函數在上
例 8:
int main()
{
fun();
}
void fun(void)
{
printf("hello world\n");
}
編譯器從上往下編譯,在 main函數(主調函數),不認識 fun,需要聲明
怎么聲明 呢?
1] 直接聲明法
將被調用的函數的第一行拷貝過去,后面加分號
例 9:
void fun(void);
5
千鋒智能物聯網+嵌入式學科
int main()
{
fun();
}
void fun(void)
{
printf("hello world\n");
}
2] 間接聲明法
將函數的聲明放在頭文件中,.c程序包含頭文件即可
例 10:
a.c
#include”a.h”
int main()
{
fun();
}
void fun(void)
{
printf("hello world\n");
}
a.h
extern void fun(void);
2)主調函數和被調函數不在同一個.c文件中的時候
一定要聲明
聲明的方法:
直接聲明法
將被調用的函數的第一行拷貝過去,后面加分號,前面加 extern
間接聲明法
將函數的聲明放在頭文件中,.c程序包含頭文件即可
4.5 函數的調用
函數的調用方法
變量= 函數名(實參列表);//帶返回值的
函數名(實參列表);//不帶返回值的
6
千鋒智能物聯網+嵌入式學科
1、有無返回值
1).有返回值的,根據返回值的類型,需要在主調函數中定義一個對應類型的變量,接返回值
例 11:
int max(int x,int y)// x、y 形參,是個變量
{
}
int main()
{
int num;//需要定義一個 num接收max 函數的返回值
num=max(4,8);//4 和 8 就是實參
}
2).沒有返回值的函數,不需要接收返回值。
例 12:
void fun(void)
{
printf("hello world\n");
}
int main()
{
fun();
}
2、有無形參
函數名(實參列表);//帶形參的
函數名();//沒有形參的
注意:實參,可以常量,可以是變量,或者是表達式
形參是變量,是被調函數的局部變量。
4.6 函數總結
在定義函數的時候,關于函數的參數和返回值是什么情況,完全取決于函數的功能。
使用函數的好處?
1、定義一次,可以多次調用,減少代碼的冗余度。
2、使咱們代碼,模塊化更好,方便調試程序,而且閱讀方便
7
千鋒智能物聯網+嵌入式學科
4.7 變量的存儲類別
4.7.1 內存的分區:
1、內存:物理內存、虛擬內存
物理內存:實實在在存在的存儲設備
虛擬內存:操作系統虛擬出來的內存。
操作系統會在物理內存和虛擬內存之間做映射。
在 32位系統下,每個進程的尋址范圍是 4G,0x00 00 00 00 ~0xff ff ff ff
在寫應用程序的,咱們看到的都是虛擬地址。
2、在運行程序的時候,操作系統會將 虛擬內存進行分區。
1).堆
在動態申請內存的時候,在堆里開辟內存。
2).棧
主要存放局部變量。
3).靜態全局區
1:未初始化的靜態全局區
靜態變量(定義變量的時候,前面加 static 修飾),或全局變量 ,沒有初始化的,存在此區
2:初始化的靜態全局區
全局變量、靜態變量,賦過初值的,存放在此區
4).代碼區
存放咱們的程序代碼
5).文字常量區
存放常量的。
4.7.2 普通的全局變量
概念:
在函數外部定義的變量
int num=100;//num就是一個全局變量
int main()
{
return 0;
}
8
千鋒智能物聯網+嵌入式學科
作用范圍:
全局變量的作用范圍,是程序的所有地方。
只不過用之前需要聲明。聲明方法 extern int num;
注意聲明的時候,不要賦值。
生命周期:
程序運行的整個過程,一直存在,直到程序結束。
注意:定義普通的全局變量的時候,如果不賦初值,它的值默認為 0
4.7.3 靜態全局變量 static
概念:
定義全局變量的時候,前面用 static 修飾。
static int num=100;//num就是一個靜態全局變量
int main()
{
return 0;
}
作用范圍:
static 限定了靜態全局變量的,作用范圍
只能在它定義的.c(源文件)中有效
生命周期:
在程序的整個運行過程中,一直存在。
注意:定義靜態全局變量的時候,如果不賦初值,它的值默認為 0
4.7.4 普通的局部變量
概念:
在函數內部定義的,或者復合語句中定義的變量
int main()
{
int num;//局部變量
{
int a;//局部變量
}
}
作用范圍:
在函數中定義的變量,在函數中有效
在復合語句中定義的,在復合語句中有效。
9
千鋒智能物聯網+嵌入式學科
生命周期:
在函數調用之前,局部變量不占用空間,調用函數的時候,
才為局部變量開辟空間,函數結束了,局部變量就釋放了。
在復合語句中定義的亦如此。
#include
void fun()
{
int num=3;
num++;
printf("num=%d\n",num);
}
int main()
{
fun();
fun();
fun();
return 0;
}
4.7.5 靜態的局部變量
概念:
定義局部變量的時候,前面加 static 修飾
作用范圍:
在它定義的函數或復合語句中有效。
生命周期:
第一次調用函數的時候,開辟空間賦值,函數結束后,不釋放,
以后再調用函數的時候,就不再為其開辟空間,也不賦初值,
用的是以前的那個變量。
void fun()
{
static int num=3;
num++;
printf("num=%d\n",num);
}
int main()
{
10
千鋒智能物聯網+嵌入式學科
fun();
fun();
fun();
}
注意:
1:
定義普通局部變量,如果不賦初值,它的值是隨機的。
定義靜態局部變量,如果不賦初值,它的值是 0
2:普通全局變量,和靜態全局變量如果不賦初值,它的值為 0
4.7.6 外部函數
咱們定義的普通函數,都是外部函數。
即函數可以在程序的任何一個文件中調用。
4.7.7 內部函數
在定義函數的時候,返回值前面加 static 修飾。這樣的函數
被稱為內部函數。
static 限定了函數的作用范圍,在定義的.c中有效。
內部函數,和外部函數的區別:
外部函數,在所有地方都可以調用,
內部函數,只能在所定義的.c中的函數調用。
擴展:
在同一作用范圍內,不允許變量重名。
作用范圍不同的可以重名。
局部范圍內,重名的全局變量不起作用。(就近原則)
11千鋒智能物聯網+嵌入式學科
第 1章 環境搭建
第 2章 c數據類型及語句
第 3章 數組
第 4章 函數
第 5章 預處理、動態庫、靜態庫
5.1 c語言編譯過程
1:預編譯
將.c 中的頭文件展開、宏展開
生成的文件是.i文件
2:編譯
將預處理之后的.i 文件生成 .s 匯編文件
3、匯編
將.s匯編文件生成.o 目標文件
4、鏈接
將.o 文件鏈接成目標文件
Linux 下 GCC編譯器編譯過程
gcc -E hello.c -o hello.i 1、預處理
gcc -S hello.i –o hello.s 2、編譯
gcc -c hello.s -o hello.o 3、匯編
gcc hello.o -o hello_elf 4、鏈接
預處理有幾種啊?
5.2 include
#include<>//用尖括號包含頭文件,在系統指定的路徑下找頭文件
#include "" //用雙引號包含頭文件,先在當前目錄下找頭文件,找不到,
1
千鋒智能物聯網+嵌入式學科
再到系統指定的路徑下找。
注意:include 經常用來包含頭文件,可以包含 .c 文件,但是大家不要包含.c
因為 include包含的文件會在預編譯被展開,如果一個.c 被包含多次,展開多次,會導致函數重復定義。
所以不要包含.c 文件。
注意:預處理只是對 include 等預處理操作進行處理并不會進行語法檢查
這個階段有語法錯誤也不會報錯,第二個階段即編譯階段才進行語法檢查。
例 1:
main.c:
#include "max.h"
int main(int argc, char *argv[])
{
int num;
num=max(10,20);
return 0;
}
max.h
int max(int x,int y);
編譯:gcc –E main.c –o main.i
5.3 define
定義宏用 define 去定義
宏是在預編譯的時候進行替換。
1、不帶參宏
#define PI 3.14
在預編譯的時候如果代碼中出現了 PI 就用 3.14去替換。
宏的好處:只要修改宏定義,其他地方在預編譯的時候就會重新替換。
注意:宏定義后邊不要加分號。
例 2:
#define PI 3.1415926
int main()
{
double f;
printf("%lf\n",PI);
f=PI;
return 0;
2
千鋒智能物聯網+嵌入式學科
}
宏定義的作用范圍,從定義的地方到本文件末尾。
如果想在中間終止宏的定義范圍
#undef PI //終止 PI的作用
例 3:
#define PI 3.1415926
int main()
{
double f;
printf("%lf\n",PI);
#undef PI
#define PI 3.14
f=PI;
return 0;
}
2、帶參宏
#define S(a,b) a*b
注意帶參宏的形參 a和 b沒有類型名,
S(2,4) 將來在預處理的時候替換成 實參替代字符串的形參,其他字符保留,2 * 4
例 4:
#define S(a,b) a*b
int main(int argc, char *argv[])
{
int num;
num=S(2,4);
return 0;
}
S(2+4,3)被替換成 2+4 * 3
注意:帶參宏,是在預處理的時候進行替換
解決歧義方法
3
千鋒智能物聯網+嵌入式學科
例 5:
#define S(a,b) (a)*(b)
int main(int argc, char *argv[])
{
int num;
num=S(2+3,5);//(2+3)*(5)
return 0;
}
3、帶參宏和帶參函數的區別
帶參宏被調用多少次就會展開多少次,執行代碼的時候沒有函數調用的過程,不需要壓棧彈棧。所以
帶參宏,是浪費了空間,因為被展開多次,節省時間。
帶參函數,代碼只有一份,存在代碼段,調用的時候去代碼段取指令,調用的時候要,壓棧彈棧。有
個調用的過程。
所以說,帶參函數是浪費了時間,節省了空間。
帶參函數的形參是有類型的,帶參宏的形參沒有類型名。
5.4 選擇性編譯
1、
#ifdef AAA
代碼段一
#else
代碼段二
#endif
如果在當前.c ifdef 上邊定義過 AAA ,就編譯代碼段一,否則編譯代碼段二
注意和 if else語句的區別,if else 語句都會被編譯,通過條件選擇性執行代碼
而 選擇性編譯,只有一塊代碼被編譯
例:6:
#define AAA
int main(int argc, char *argv[])
{
#ifdef AAA
4
千鋒智能物聯網+嵌入式學科
printf("hello world!!\n");
#else
printf("hello China\n");
#endif
return 0;
}
2、
#ifndef AAA
代碼段一
#else
代碼段二
#endif
和第一種互補。
這種方法,經常用在防止頭文件重復包含。
防止頭文件重復包含:
3、
#if 表達式
程序段一
#else
程序段二
#endif
如果表達式為真,編譯第一段代碼,否則編譯第二段代碼
選擇性編譯都是在預編譯階段干的事情。
5.5 靜態庫
一:動態編譯
動態編譯使用的是動態庫文件進行編譯
gcc hello.c -o hello
默認的咱們使用的是動態編譯方法
二:靜態編譯
靜態編譯使用的靜態庫文件進行編譯
gcc -static hello.c -o hello
5
千鋒智能物聯網+嵌入式學科
三:靜態編譯和動態編譯區別
1:使用的庫文件的格式不一樣
動態編譯使用動態庫,靜態編譯使用靜態庫
注意:
1:靜態編譯要把靜態庫文件打包編譯到可執行程序中。
2:動態編譯不會把動態庫文件打包編譯到可執行程序中,
它只是編譯鏈接關系
例 7:
mytest.c
#include
#include "mylib.h"
int main(int argc, char *argv[])
{
int a=10,b=20,max_num,min_num;
max_num=max(a,b);
min_num=min(a,b);
printf("max_num=%d\n",max_num);
printf("min_num=%d\n",min_num);
return 0;
}
mylib.c
int max(int x,int y)
{
return (x>y) x:y;
}
int min(int x,int y)
{
return (x}
mylib.h
extern int max(int x,int y);
extern int min(int x,int y);
6
千鋒智能物聯網+嵌入式學科
制作靜態態庫:
gcc -c mylib.c -o mylib.o
ar rc libtestlib.a mylib.o
注意:靜態庫起名的時候必須以 lib開頭以.a結尾
編譯程序:
方法 1:
gcc -static mytest.c libtestlib.a -o mytest
方法 2:可以指定頭文件及庫文件的路徑
比如咱們講 libtestlib.a mylib.h 移動到/home/edu下
mv libtestlib.a mylib.h /home/edu
編譯程序命令:
gcc –static mytest.c –o mytest -L/home/edu -ltestlib -I/home/edu
注意:-L是指定庫文件的路徑
-l 指定找哪個庫,指定的只要庫文件名 lib后面 .a 前面的部分
-I 指定頭文件的路徑
方法 3:
咱們可以將庫文件及頭文件存放到系統默認指定的路徑下
庫文件默認路徑是 /lib 或者是/usr/lib
頭文件默認路徑是/usr/include
sudo mv libtestlib.a /usr/lib
sudo mv mylib.h /usr/include
編譯程序的命令
gcc -static mytest.c –o mytest -ltestlib
5.6 動態庫
制作動態鏈接庫:
gcc -shared mylib.c -o libtestlib.so
//使用 gcc編譯、制作動態鏈接庫
動態鏈接庫的使用:
方法 1:庫函數、頭文件均在當前目錄下
gcc mytest.c libtestlib.so -o mytest
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
./mytest
方法 2:庫函數、頭文件假設在/opt目錄
gcc mytest.c -o mytest -L/home/edu -ltestlib -I/home/edu
7
千鋒智能物聯網+嵌入式學科
編譯通過,運行時出錯,編譯時找到了庫函數,但鏈接時找不到庫,執行以下操作,把當前目錄加入
搜索路徑
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
#./mytest 可找到動態鏈接庫
方法 3:庫函數、頭文件均在系統路徑下
cp libtestlib.so /usr/lib
cp mylib.h /usr/include
gcc mytest.c -o mytest -ltestlib
#./mytest
問題:有個問題出現了?
我們前面的靜態庫也是放在/usr/lib 下,那么連接的到底是動態庫還是靜態庫呢?
當靜態庫與動態庫重名時,系統會優先連接動態庫,或者我們可以加入-static 指定使用靜態庫
8千鋒智能物聯網+嵌入式學科
第 1章 環境搭建
第 2章 c數據類型及語句
第 3章 數組
第 4章 函數
第 5章 預處理、動態庫、靜態庫
第 6章 指針
6.1 指針
6.1.1 關于內存那點事
存儲器:存儲數據器件
外存
外存又叫外部存儲器,長期存放數據,掉電不丟失數據
常見的外存設備:硬盤、flash、rom、u盤、光盤、磁帶
內存
內存又叫內部存儲器,暫時存放數據,掉電數據丟失
常見的內存設備:ram、DDR
物理內存:實實在在存在的存儲設備
虛擬內存:操作系統虛擬出來的內存。
1
千鋒智能物聯網+嵌入式學科
32bit 32根尋址總線
0x00 00 00 00
0xff ff ff ff
操作系統會在物理內存和虛擬內存之間做映射。
在 32位系統下,每個進程(運行著的程序)的尋址范圍是 4G,0x00 00 00 00 ~0xff ff ff ff
在寫應用程序的,咱們看到的都是虛擬地址。
在運行程序的時候,操作系統會將 虛擬內存進行分區。
1.堆
在動態申請內存的時候,在堆里開辟內存。
2.棧
主要存放局部變量(在函數內部,或復合語句內部定義的變量)。
3.靜態全局區
1):未初始化的靜態全局區
2
千鋒智能物聯網+嵌入式學科
靜態變量(定義的時候,前面加 static 修飾),或全局變量 ,沒有初始化的,存在此區
2):初始化的靜態全局區
全局變量、靜態變量,賦過初值的,存放在此區
4.代碼區
存放咱們的程序代碼
5.文字常量區
存放常量的。
內存以字節為單位來存儲數據的,咱們可以將程序中的虛擬尋址空間,看成一個很大的一維的字符數

6.1.2 指針的概念
系統給虛擬內存的每個存儲單元分配了一個編號,從 0x00 00 00 00 ~0xff ff ff ff
這個編號咱們稱之為地址
指針就是地址
指針變量:是個變量,是個指針變量,即這個變量用來存放一個地址編號
在 32位平臺下,地址總線是 32位的,所以地址是 32位編號,所以指針變量是 32位的即 4個字節。
注意:1:
無論什么類型的地址,都是存儲單元的編號,在 32位平臺下都是 4個字節,
即任何類型的指針變量都是 4個字節大小
2:對應類型的指針變量,只能存放對應類型的變量的地址
舉例:整型的指針變量,只能存放整型變量的地址
擴展:
字符變量 char ch=‘b’; ch占 1個字節,它有一個地址編號,這個地址編號就是 ch的地址
整型變量 int a=0x12 34 56 78; a占 4個字節,它占有 4個字節的存儲單元,有 4個地址編號。
3
千鋒智能物聯網+嵌入式學科
6.1.3 指針變量的定義方法
1.簡單的指針變量
數據類型 * 指針變量名;
int * p;//定義了一個指針變量 p
在 定義指針變量的時候 * 是用來修飾變量的,說明變量 p是個指針變量。
變量名是 p
2.關于指針的運算符
& 取地址 、 *取值
例 1:
int a=0x1234abcd;
int *p;//在定義指針變量的時候*代表修飾的意思,修飾 p是個指針變量。
p=&a;//把 a 的地址給 p 賦值 ,&是取地址符,
p 保存了 a的地址,也可以說 p指向了 a
p和 a的關系分析:a的值是 0x1234abcd,假如 a的地址是:0xbf e8 98 68
4
千鋒智能物聯網+嵌入式學科
int num;
num=*p;
分析:
1、在調用的時候 *代表取值得意思 ,*p就相當于 p指向的變量,即 a,
2、故 num=*p 和 num =a 的效果是一樣的。
3、所以說 num的值為 0x1234abcd。
擴展:如果在一行中定義多個指針變量,每個指針變量前面都需要加*來修飾
int *p,*q;//定義了兩個整型的指針變量 p和 q
int * p,q;//定義了一個整型指針變量 p,和整型的變量 q
例 2:
int main()
{
int a= 100, b = 200;
int *p_1, *p_2 = &b; //表示該變量的類型是一個指針變量,指針變量名是 p_1 而不是*p_1.
//p_1 在定義的時候沒有賦初值,p_2 賦了初值
p_1=&a; //p_1 先定義后賦值
printf("%d\n", a);
printf("%d\n", *p_1);
5
千鋒智能物聯網+嵌入式學科
printf("%d\n", b);
printf("%d\n", *p_2);
return 0;
}
注意:
在定義 p_1的時候,因為是個局部變量,局部變量沒有賦初值,它的值是隨機的,p_1指向哪里不一定,
所以 p_1就是個野指針。
3.指針大小
例 3:在 32 位系統下,所有類型的指針都是 4個字節
#include
int main(int argc, char *argv[])
{
char *p1;
short int *p2;
int *p3;
long int *p4;
float *p5;
double *p6;
printf("%d\n",sizeof(p1));
printf("%d\n",sizeof(p2));
printf("%d\n",sizeof(p3));
printf("%d\n",sizeof(p4));
printf("%d\n",sizeof(p5));
printf("%d\n",sizeof(p6));
return 0;
}
例 4:
#include
int main(int argc, char *argv[])
{
int a=0x1234abcd;
int *p;
p=&a;
printf("&a=%p\n",&a);
printf("p=%p\n",p);
6
千鋒智能物聯網+嵌入式學科
return 0;
}
6.1.4 指針的分類
按指針指向的數據的類型來分
1:字符指針
字符型數據的地址
char *p;//定義了一個字符指針變量,只能存放字符型數據的地址編號
char ch;
p= &ch;
2:短整型指針
short int *p;//定義了一個短整型的指針變量 p,只能存放短整型變量的地址
short int a;
p =&a;
7
千鋒智能物聯網+嵌入式學科
3:整型指針
int *p;//定義了一個整型的指針變量 p,只能存放整型變量的地址
int a;
p =&a;
注:多字節變量,占多個存儲單元,每個存儲單元都有地址編號,
c語言規定,存儲單元編號最小的那個編號,是多字節變量的地址編號。
4:長整型指針
long int *p;//定義了一個長整型的指針變量 p,只能存放長整型變量的地址
long int a;
p =&a;
5:float 型的指針
float *p;//定義了一個 float型的指針變量 p,只能存放 float型變量的地址
float a;
p =&a;
6:double 型的指針
double *p;//定義了一個 double型的指針變量 p,只能存放 double型變量的地址
double a;
p =&a;
7:函數指針
8、結構體指針
9、指針的指針
10、數組指針
11、通用指針 void *p;
總結:無論什么類型的指針變量,在 32位系統下,都是 4個字節。
指針只能存放對應類型的變量的地址編號。
6.1.5 指針和變量的關系
指針可以存放變量的地址編號
int a=100;
int *p;
p=&a;
在程序中,引用變量的方法
1:直接通過變量的名稱
int a;
a=100;
2:可以通過指針變量來引用變量
int *p;//在定義的時候,*不是取值的意思,而是修飾的意思,修飾 p是個指針變量
p=&a;//取 a的地址給 p賦值,p保存了 a的地址,也可以說 p指向了 a
*p= 100;//在調用的時候*是取值的意思,*指針變量 等價于指針指向的變量
注:指針變量在定義的時候可以初始化
int a;
8
千鋒智能物聯網+嵌入式學科
int *p=&a;//用 a的地址,給 p賦值,因為 p是指針變量
指針就是用來存放變量的地址的。
*+指針變量 就相當于指針指向的變量
例 5:
#include
int main()
{
int *p1,*p2,temp,a,b;
p1=&a;
p2=&b;
printf("請輸入:a b 的值:\n");
scanf_s("%d %d",p1,p2);//給 p1 和 p2 指向的變量賦值
temp = *p1; //用 p1 指向的變量(a)給 temp賦值
*p1 = *p2; //用 p2 指向的變量(b)給 p1 指向的變量(a)賦值
*p2 = temp;//temp給 p2 指向的變量(b)賦值
printf("a=%d b=%d\n",a,b);
printf("*p1=%d *p2=%d\n",*p1,*p2);
return 0;
}
運行結果:
輸入 100 200
輸出結果為:
a=200 b=100
*p1=200 *p2=100
擴展:
對應類型的指針,只能保存對應類型數據的地址,
如果想讓不同類型的指針相互賦值的時候,需要強制類型轉換
void * p;
例 6:
#include
int main()
{
int a=0x12345678,b=0xabcdef66;
char *p1,*p2;
printf("%0x %0x\n",a,b);
p1=(char *)&a;
p2=(char *)&b;
printf("%0x %0x\n",*p1,*p2);
9
千鋒智能物聯網+嵌入式學科
p1++;
p2++;
printf("%0x %0x\n",*p1,*p2);
return 0;
}
結果為:
0x 78 0x66
0x56 0xef
注意:
1:*+指針 取值,取幾個字節,由指針類型決定的指針為字符指針則取一個字節,
指針為整型指針則取 4個字節,指針為 double型指針則取 8個字節。
2:指針++ 指向下個對應類型的數據
字符指針++ ,指向下個字符數據,指針存放的地址編號加 1
整型指針++,指向下個整型數據,指針存放的地址編號加 4
10
千鋒智能物聯網+嵌入式學科
6.1.6 指針和數組元素之間的關系
1、
變量存放在內存中,有地址編號,咱們定義的數組,是多個相同類型的變量的集合,
每個變量都占內存空間,都有地址編號
指針變量當然可以存放數組元素的地址。
例 7:
int a[5];
//int *p =&a[0];
int *p;
p=&a[0];//
指針變量 p 保存了數組 a 中第 0個元素的地址,即 a[0]的地址
2、數組元素的引用方法
方法 1: 數組名[下標]
int a[5];
a[2]=100;
方法 2:指針名加下標
int a[5];
int *p;
p=a;
p[2]=100;//相當于 a[2]=100;
補充:c語言規定:數組的名字就是數組的首地址,即第 0個元素的地址,就是&a[0],是個常量。
11
千鋒智能物聯網+嵌入式學科
注意:p和 a的不同,p是指針變量,而 a是個常量。所以可以用等號給 p賦值,但不能給 a賦值。
p=&a[3];//正確
a=&a[3];//錯誤
方法 3:通過指針變量運算加取值的方法來引用數組的元素
int a[5];
int *p;
p=a;
*(p+2)=100;//也是可以的,相當于 a[2]=100
解釋:p是第 0個元素的地址,p+2是 a[2]這個元素的地址。
對第二個元素的地址取值,即 a[2]
方法 4:通過數組名+取值的方法引用數組的元素
int a[5];
*(a+2)=100;//也是可以的,相當于 a[2]=100;
注意:a+2 是 a[2]的地址。這個地方并沒有給 a賦值。
例 8:
#include
int main(int argc, char *argv[])
{
int a[5]={0,1,2,3,4};
int *p;
p=a;
printf("a[2]=%d\n",a[2]);
printf("p[2]=%d\n",p[2]);
printf("*(p+2)%d\n",*(p+2));
printf("*(a+2)%d\n",*(a+2));
printf("p=%p\n",p);
printf("p+2=%p\n",p+2);
return 0;
}
3、指針的運算
1:指針可以加一個整數,往下指幾個它指向的變量,結果還是個地址
前提:指針指向數組元素的時候,加一個整數才有意義
例 9:
int a[5];
int *p;
p=a;
p+2;//p 是 a[0]的地址,p+2 是&a[2]
假如 p保存的地址編號是 2000的話,p+2 代表的地址編號是 2008
例 10:
12
千鋒智能物聯網+嵌入式學科
char buf[5];
char *q;
q=buf;
q+2 //相當于&buf [2]
假如:q中存放的地址編號是 2000的話,q+2代表的地址編號是 2002
2:兩個相同類型指針可以比較大小
前提:只有兩個相同類型的指針指向同一個數組的元素的時候,比較大小才有意義
指向前面元素的指針 小于 指向后面 元素的指針
例 11:
#include
int main(int argc, char *argv[])
{
int a[10];
int *p,*q,n;//如果在一行上定義多個指針變量的,每個變量名前面加*
//上邊一行定義了兩個指針 p 和 q ,定義了一個整型的變量 n
p=&a[1];
q=&a[6];
if(p{
printf("p}
else if(p>q)
{
printf("p>q\n");
}
else
{
printf("p == q\n");
}
return 0;
}
結果是 p3.兩個相同類型的指針可以做減法
前提:必須是兩個相同類型的指針指向同一個數組的元素的時候,做減法才有意義
做減法的結果是,兩個指針指向的中間有多少個元素
例 12:
13
千鋒智能物聯網+嵌入式學科
#include
int main(int argc, char *argv[])
{
int a[5];
int *p,*q;
p=&a[0];
q=&a[3];
printf("%d\n",q-p);
return 0;
}
結果是 3
4:兩個相同類型的指針可以相互賦值
注意:只有相同類型的指針才可以相互賦值(void *類型的除外)
int *p;
int *q;
int a;
p=&a;//p 保存 a的地址,p指向了變量 a
q=p; //用 p給 q賦值,q也保存了 a的地址,指向 a
注意:如果類型不相同的指針要想相互賦值,必須進行強制類型轉換
注意:c語言規定數組的名字,就是數組的首地址,就是數組第 0個元素的地址,是個常量
int *p;
int a[5];
p=a; p=&a[0];這兩種賦值方法是等價的
6.1.7 指針數組
1、指針和數組的關系
1:指針可以保存數組元素的地址
2:可以定義一個數組,數組中有若干個相同類型指針變量,這個數組被稱為指針數組 int *p[5]
指針數組的概念:
指針數組本身是個數組,是個指針數組,是若干個相同類型的指針變量構成的集合
2、指針數組的定義方法:
類型說明符 * 數組名 [元素個數];
int * p[5];//定義了一個整型的指針數組 p,有 5個元素 p[0]~p[4],
每個元素都是 int *類型的變量
int a;
p[0]=&a;
int b[10];
p[1]=&b[5];
14
千鋒智能物聯網+嵌入式學科
p[2]、*(p+2)是等價的,都是指針數組中的第 2個元素。
例 13:
#include
int main(int argc, char *argv[])
{
char *name[5] = {"hello","China","beijing","project","Computer"};
int i;
for(i=0;i<5;i++)
{
printf("%s\n",name[i]);
}
return 0;
}
“hello”、“China”“beijing” “project” “computer” 這 5個字符串存放在文字常量區。
假設:
“hello ”首地址是 0x00002000
“China”首地址是 0x00003000
“beijing”首地址是 0x00004000
“project”首地址是 0x00005000
“Computer”首地址是 0x00006000
則:
name[0]中存放內容為 0x00002000
name[1]中存放內容為 0x00003000
name[2]中存放內容為 0x00004000
name[3]中存放內容為 0x00005000
name[4]中存放內容為 0x00006000
注意:name[0] name[1] name[2] name[3] name[4] 分別是 char * 類型的指針變量,分別存放一個地址編號。
3、指針數組的分類
字符指針數組 char *p[10]、短整型指針數組、整型的指針數組、長整型的指針數組
15
千鋒智能物聯網+嵌入式學科
float型的指針數組、double型的指針數組
結構體指針數組、函數指針數組
6.1.8 指針的指針
指針的指針,即指針的地址,
咱們定義一個指針變量本身指針變量占 4個字節,指針變量也有地址編號。
例:
int a=0x12345678;
假如:a的地址是 0x00002000
int *p;
p =&a;
則 p中存放的是 a的地址編號即 0x00002000
因為 p也占 4個自己內存,也有它自己的地址編號,及指針變量的地址,即指針的指針。
假如:指針變量 p的地址編號是 0x00003000,這個地址編號就是指針的地址
我們定義一個變量存放 p的地址編號,這個變量就是指針的指針
int **q;
q=&p;//q 保存了 p的地址,也可以說 q指向了 p
則 q里存放的就是 0x00003000
int ***m;
m=&q;
16
千鋒智能物聯網+嵌入式學科
p q m都是指針變量,都占 4個字節,都存放地址編號,只不過類型不一樣而已
6.1.9 字符串和指針
字符串的概念:
字符串就是以’\0’結尾的若干的字符的集合:比如“helloworld”。
字符串的地址,是第一個字符的地址。如:字符串“helloworld”的地址,其實是字符串中字符’h’的地址。
我們可以定義一個字符指針變量保存字符串的地址,比如:char *s =”helloworld”;
字符串的存儲形式: 數組、文字常量區、堆
1、字符串存放在數組中
其實就是在內存(棧、靜態全局區)中開辟了一段空間存放字符串。
char string[100] = “I love C!”
定義了一個字符數組 string,用來存放多個字符,并且用”I love C!”給 string數組初始化
,字符串“I love C!”存放在 string中。
注:普通全局數組,內存分配在靜態全局區
普通局部數組,內存分配在棧區。
靜態數組(靜態全局數組、靜態局部數組),內存分配在靜態全局區
2、字符串存放在文字常量區
在文字常量區開辟了一段空間存放字符串,將字符串的首地址付給指針變量。
char *str = “I love C!”
定義了一個指針變量 str,只能存放字符地址編號,
I love C! 這個字符串中的字符不是存放在 str指針變量中。
str只是存放了字符 I的地址編號,“I love C!”存放在文字常量區
3、字符串存放在堆區
使用 malloc等函數在堆區申請空間,將字符串拷貝到堆區。
char *str =(char*)malloc(10);//動態申請了 10個字節的存儲空間,
首地址給 str賦值。
strcpy(str,"I love C");//將字符串“Ilove C!”拷貝到 str指向的內存里
字符串的可修改性:
字符串內容是否可以修改,取決于字符串存放在哪里
17
千鋒智能物聯網+嵌入式學科
1. 存放在數組中的字符串的內容是可修改的
char str[100]=”I love C!”;
str[0]=‘y’;//正確可以修改的
注:數組沒有用 const修飾
2. 文字常量區里的內容是不可修改的
char *str=”I love C!”;
*str =’y’;//錯誤,I存放在文字常量區,不可修改
注:
1、str指向文字常量區的時候,它指向的內存的內容不可被修改。
2、str是指針變量可以指向別的地方,即可以給 str重新賦值,讓它指向別的地方。
3. 堆區的內容是可以修改的
char *str =(char*)malloc(10);
strcpy(str,"I love C");
*str=’y’;//正確,可以,因為堆區內容是可修改的
注:
1、str指向堆區的時候,str指向的內存內容是可以被修改的。
2、str是指針變量,也可以指向別的地方。即可以給 str重新賦值,讓它指向別的地方
注意:str指針指向的內存能不能被修改,要看 str指向哪里。
str指向文字常量區的時候,內存里的內容不可修改
str指向數組(非 const修飾)、堆區的時候,它指向內存的內容是可以修改
初始化:
1.字符數組初始化:
char buf_aver[20]="hello world";
2.指針指向文字常量區,初始化:
char *buf_point="hello world";
3、指針指向堆區,堆區存放字符串。
不能初始化,只能先給指針賦值,讓指針指向堆區,再使用 strcpy、scanf等方法把字符串拷貝到堆區。
char *buf_heap;
buf_heap=(char *)malloc(15);
strcpy(buf_heap,"hello world");
scanf(“%s”,buf_heap);
使用時賦值
1. 字符數組:使用 scanf或者 strcpy
18
千鋒智能物聯網+嵌入式學科
char buf[20]=”hello world”
buf="hello kitty"; 錯誤,因為字符數組的名字是個常量,不能用等號給常量賦值。
strcpy(buf,"hello kitty"); 正確,數組中的內容是可以修改的
scanf("%s",buf); 正確,數組中的內容是可以修改的
2. 指針指向文字常量區
char *buf_point = “hello world”;
1) buf_point="hello kitty"; 正確,buf_point指向另一個字符串
2) strcpy(buf_point,"hello kitty");錯誤,這種情況,buf_point指向的是文字常量區,內容只讀。
當指針指向文字常量區的時候,不能通過指針修改文字常量區的內容。
3.指針指向堆區,堆區存放字符串
char *buf_heap;
buf_heap=(char *)malloc(15);
strcpy(buf_heap,"hello world");
scanf(“%s”,buf_heap);
字符串和指針總結:
1、指針可以指向文字常量區
1)指針指向的文字常量區的內容不可以修改
2)指針的指向可以改變,即可以給指針變量重新賦值,指針變量指向別的地方。
2、指針可以指向堆區
1)指針指向的堆區的內容可以修改。
2)指針的指向可以改變,即可以給指針變量重新賦值,指針變量指向別的地方。
3、指針也可以指向數組(非 const修飾)
例:
char buf[20]="hello world";
char *str=buf;
這種情況下
1.可以修改 buf數組的內容。
2.可以通過 str修改 str指向的內存的內容,即數組 buf的內容
3.不能給 buf賦值 buf=“hello kitty”;錯誤的。
4.可以給 str賦值,及 str指向別處。 str=“hello kitty”
6.1.10 數組指針
1、二維數組
二維數組,有行,有列。二維數組可以看成有多個一維數組構成的,是多個一維數組的集合,可以認
為二維數組的每一個元素是個一維數組。
例:
19
千鋒智能物聯網+嵌入式學科
int a[3][5];
定義了一個 3行 5列的一個二維數組。
可以認為二維數組 a由 3個一維數組構成,每個元素是一個一維數組。
回顧:
數組的名字是數組的首地址,是第 0個元素的地址,是個常量,數組名字加 1指向下個元素
二維數組 a中 ,a+1 指向下個元素,即下一個一維數組,即下一行。
例 14:
#include
int main(int argc, char *argv[])
{
int a[3][5];
printf("a=%p\n",a);
printf("a+1=%p\n",a+1);
return 0;
}
2、數組指針的概念:
本身是個指針,指向一個數組,加 1跳一個數組,即指向下個數組。
3、數組指針的定義方法:
指向的數組的類型(*指針變量名)[指向的數組的元素個數]
int (*p)[5];//定義了一個數組指針變量 p,p指向的是整型的有 5個元素的數組
p+1 往下指 5個整型,跳過一個有 5個整型元素的數組。
例 15:
#include
int main()
{
int a[3][5];//定義了一個 3 行 5 列的一個二維數組
int(*p)[5];//定義一個數組指針變量 p,p+1跳一個有 5 個元素的整型數組
printf("a=%p\n",a);//第 0 行的行地址
printf("a+1=%p\n",a+1);//第 1 行的行地址,a 和 a +1差 20 個字節
p=a;
printf("p=%p\n",p);
printf("p+1=%p\n",p+1);//p+1跳一個有 5 個整型元素的一維數組
return 0;
}
例 16:數組指針的用法 1
20
千鋒智能物聯網+嵌入式學科
#include
void fun(int(*p)[5],int x,int y)
{
p[0][1]=101;
}
int main()
{
int i,j;
int a[3][5];
fun(a,3,5);
for(i=0;i<3;i++)
{
for(j=0;j<5;j++)
{
printf("%d ",a[i][j]);
}
printf("\n");
}
}
4、各種數組指針的定義:
(1)、一維數組指針,加 1后指向下個一維數組
int(*p)[5] ; //
配合每行有 5個 int型元素的二維數組來用
int a[3][5]
int b[4][5]
int c[5][5]
int d[6][5]
…..
p=a;
p=b;
p=c;
p=d;
都是可以的~~~~
(2)、二維數組指針,加 1后指向下個二維數組
int(*p)[4][5];
配合三維數組來用,三維數組中由若干個 4行 5列二維數組構成
int a[3][4][5];
int b[4][4][5];
21
千鋒智能物聯網+嵌入式學科
int c[5][4][5];
int d[6][4][5];
這些三維數組,有個共同的特點,都是有若干個 4行 5的二維數組構成。
p=a;
p=b;
p=c;
p=d;
例 17:
#include
int main()
{
int a[3][4][5];
printf("a=%p\n",a);
printf("a+1=%p\n",a+1);//a 和 a+1 地址編號相差 80 個字節
//驗證了 a+1 跳一個 4行 5列的一個二維數組
int(*p)[4][5];
p=a;
printf("p=%p\n",p);
printf("p+1=%p\n",p+1);//p 和 p+1 地址編號相差也 80 個字節
return 0;
}
5、三維數組指針,加 1后指向下個三維數組
int(*p)[4][5][6];
p+1跳一個三維數組;
什么樣的三維數組啊?
由 4個 5行 6列的二維數組構成的三維數組
配合:
int a[7][4][5][6];
6、四維數組指針,加 1后指向下個四維數組,以此類推。。。。
7、注意:
容易混淆的概念:
指針數組:是個數組,有若干個相同類型的指針構成的集合
int *p[10];
數組 p有 10個 int *類型的指針變量構成,分別是 p[0] ~p[9]
數組指針:本身是個指針,指向一個數組,加 1跳一個數組
int (*p)[10];
22
千鋒智能物聯網+嵌入式學科
P是個指針,p是個數組指針,p加 1指向下個數組,跳 10個整形。
指針的指針:
int **p;//p是指針的指針
int *q;
p=&q;
8、數組名字取地址:變成 數組指針
一維數組名字取地址,變成一維數組指針,即加 1跳一個一維數組
int a[10];
a+1 跳一個整型元素,是 a[1]的地址
a和 a+1 相差一個元素,4個字節
&a就變成了一個一維數組指針,是 int(*p)[10]類型的。
(&a) +1 和&a相差一個數組即 10個元素即 40個字節。
例 18:
#include
int main()
{
int a[10];
printf("a=%p\n",a);
printf("a+1=%p\n",a+1);
printf("&a=%p\n",&a);
printf("&a +1=%p\n",&a+1);
return 0;
}
a是個 int *類型的指針,是 a[0]的地址。
&a 變成了數組指針,加 1跳一個 10個元素的整型一維數組
在運行程序時,大家會發現 a和&a所代表的地址編號是一樣的,即他們指向同一個存儲單元,但是 a
和&a的指針類型不同。
例 19:
int a[4][5];
a+1 跳 5 個整型
(&a)+1 跳 4 行 5列(80 個字節)。
總結:c語言規定,數組名字取地址,變成了數組指針。加 1跳一個數組。
23
千鋒智能物聯網+嵌入式學科
9、數組名字和指針變量的區別:
int a[5];
int *p;
p=a;
相同點:
a是數組的名字,是 a[0]的地址,p=a即 p保存了 a[0]的地址,即 a和 p都指向 a[0],所以在引用數組
元素的時候,a和 p等價
引用數組元素回顧:
a[2]、*(a+2)、p[2]、*(p+2) 都是對數組 a中 a[2]元素的引用。
#include
int main()
{
int a[5] = { 0,1,2,3,4 };
int* p;
p = a;
printf("a[2]=%d\n",a[2]);
printf(" * (a + 2) = % d\n",*(a+2));
printf("p[2]=%d\n", p[2]);
printf(" * (p + 2) = % d\n", *(p + 2));
return 0;
}
不同點:
1、 a是常量、p是變量
可以用等號’=’給 p賦值,但是不能用等號給 a賦值
2、對 a取地址,和對 p取地址結果不同
因為 a是數組的名字,所以對 a取地址結果為數組指針。
p是個指針變量,所以對 p取地址(&p)結果為指針的指針。
例:int a[5]={0,1,2,3,4};
int *p=a;
假如 a[0]的地址為 0x00002000,p的地址為 0x00003000
24
千鋒智能物聯網+嵌入式學科
1、&p是指針的指針,為 int **類型,結果為 0x00003000,&p +1,往后指向一個 int* 類型的指
針,地址編號差 4
2、&a結果是數組指針,為 int(* )[5]類型,結果還是 0x00002000,&a +1 ,往后指一個數組(有 5
個整型元素的一維數組),地址編號差 20
例 20:
#include
int main(int argc, char *argv[])
{
int a[5];
int *p;
p=a;
25
千鋒智能物聯網+嵌入式學科
printf("a=%p\n",a);
printf("&a=%p\n",&a);
printf("&a +1 =%p\n",&a +1);
printf("p=%p\n",p);
printf("&p=%p\n",&p);
printf("&p +1=%p\n",&p +1);
return 0;
}
10、數組指針取*
數組指針取 * ,并不是取值的意思,而是指針的類型發生變化:
一維數組指針取* ,結果為它指向的一維數組第 0個元素的地址,它們還是指向同一個地方。
二維數組指針取 *,結果為一維數組指針,它們還是指向同一個地方。
三維數組指針取*,結果為二維數組指針,它們還是指向同一個地方。
多維以此類推
例 21:
#include
int main()
{
int a[3][5];
int(*p)[5];
p = a;
printf("a=%p\n", a);//a是一維數組指針,指向第 0個一維數組,即第 0 行
printf("*a=%p\n", *a);//*a 是 第 0 行第 0個元素的地址,即 &a[0][0]
printf("*a +1=%p\n", *a + 1);//*a +1 是第 0 行第 1 個元的地址,即&a[0][1]
printf("p=%p\n",p);//p是一維數組指針,指向第 0 個一維數組,即第 0行
printf("*p=%p\n",*p);//*p是第 0行第 0 個元素的地址,即 &a[0][0]
printf("*p +1=%p\n", *p + 1);//*p +1 是第 0 行第 1 個元的地址,即&a[0][1]
return 0;
}
6.1.11 指針和函數的關系
6.1.11.1 指針作為函數的參數
咱們可以給一個函數傳一個 整型、字符型、浮點型的數據,也可以
26
千鋒智能物聯網+嵌入式學科
給函數傳一個地址。
例:
int num;
scanf("%d",&num);
函數傳參:
(1)、傳數值:
例 22:
void swap(int x,int y)
{
int temp;
temp=x;
x=y;
y=temp;
}
int main()
{
int a=10,b=20;
swap(a,b);
printf("a=%d b=%d\n",a,b);//a=10 b=20
}
實參:調用函數時傳的參數。
形參:定義被調函數時,函數名后邊括號里的數據
結論:給被調函數傳數值,只能改變被調函數形參的值,不能改變主調函數實參的值
(2)、傳地址:
例 23:
void swap(int *p1,int *p2)
{
int temp;
temp= *p1;
*p1=*p2;// p2 指向的變量的值,給 p1 指向的變量賦值
*p2=temp;
}
int main()
{
int a=10,b=20;
swap(&a,&b);
printf("a=%d b=%d\n",a,b);//結果為 a=20 b=10
}
27
千鋒智能物聯網+嵌入式學科
結論:調用函數的時候傳變量的地址,在被調函數中通過*+地址來改變主調函數中的變量的值
例 24:
void swap(int *p1,int *p2)//&a &b
{
int *p;
p=p1;
p1=p2;//p1 =&b 讓 p1 指向 main 中的 b
p2=p;//讓 p2 指向 main 函數中 a
}//此函數中改變的是 p1 和 p2 的指向,并沒有給 main 中的 a 和 b 賦值
int main()
{
int a=10,b=20;
swap(&a,&b);
printf("a=%d b=%d\n",a,b);//結果為 a=10 b=20
}
總結:要想改變主調函數中變量的值,必須傳變量的地址,
而且還得通過*+地址 去賦值。
例 25:
void fun(char *p)
{
p="hello kitty";
}
int main()
{
char *p="hello world";
fun(p);
printf("%s\n",p);//結果為: hello world
}
答案分析:
在 fun中改變的是 fun函數中的局部變量 p,并沒有改變 main函數中的變量 p,所以 main函數中的,
變量 p還是指向 hello world。
例 26:
void fun(char **q)
{
*q="hello kitty";
}
28
千鋒智能物聯網+嵌入式學科
int main()
{
char *p="hello world";
fun(&p);
printf("%s\n",p);//結果為:hello kitty
}
總結一句話:要想改變主調函數中變量的值,必須傳變量的地址,而且還得通過*+地址 去賦值。無
論這個變量是什么類型的。
(3) 給函數傳數組:
給函數傳數組的時候,沒法一下將數組的內容作為整體傳進去。
只能傳數組名進去,數組名就是數組的首地址,即只能把數組的地址傳進去。
也就是說,只能傳一個 4個字節大小的地址編號進去
例 27:傳一維數組的地址
//void fun(int p[])//形式 1
void fun(int *p)//形式 2
{
printf("%d\n",p[2]);
printf("%d\n",*(p+3));
}
int main()
{
int a[10]={1,2,3,4,5,6,7,8};
fun(a);
return 0;
}
例 28:傳二維數組的地址
//void fun( int p[][4] )//形式 1
void fun( int (*p)[4] )//形式 2
{
}
int main()
{
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
fun(a);
29
千鋒智能物聯網+嵌入式學科
return 0;
}
例 29:傳指針數組
void fun(char **q) // char *q[]
{
int i;
for(i=0;i<3;i++)
printf("%s\n",q[i]);
}
int main()
{
char *p[3]={"hello","world","kitty"}; //p[0] p[1] p[2] char *
fun(p);
return 0;
}
6.1.11.2 指針作為函數的返回值
一個函數可以返回整型數據、字符數據、浮點型的數據,也可以返回一個指針。
例 30:
char * fun()
{
char str[100]="hello world";
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);//
}
//總結:返回地址的時候,地址指向的內存的內容不能釋放
如果返回的指針指向的內容已經被釋放了,返回這個地址,也沒有意義了。
例 31:返回靜態局部數組的地址
char * fun()
30
千鋒智能物聯網+嵌入式學科
{
static char str[100]="hello world";
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);//hello world
}
原因是,靜態數組的內容,在函數結束后,亦然存在。
例 32:返回文字常量區的字符串的地址
char * fun()
{
char *str="hello world";
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);//hello world
}
原因是文字常量區的內容,一直存在。
例 33:返回堆內存的地址
char * fun()
{
char *str;
str=(char *)malloc(100);
strcpy(str,"hello world");
return str;
}
int main()
{
char *p;
31
千鋒智能物聯網+嵌入式學科
p=fun();
printf("%s\n",p);//hello world
free(p);
}
原因是堆區的內容一直存在,直到 free才釋放。
總結:返回的地址,地址指向的內存的內容得存在,返回的地址才有意義。
6.1.11.3 指針保存函數的地址(函數指針)
1、函數指針的概念:
咱們定義的函數,在運行程序的時候,會將函數的指令加載到內存
的代碼段。所以函數也有起始地址。
c語言規定:函數的名字就是函數的首地址,即函數的入口地址
咱們就可以定義一個指針變量,來存放函數的地址。
這個指針變量就是函數指針變量。
2、函數指針的用處:
函數指針用來保存函數的入口地址。
在項目開發中,我們經常需要編寫或者調用帶函數指針參數的函數。
比如 Linux系統中創建多線程的函數,它有個參數就是函數指針,接收線程函數的入口地址,即創建線程
成功后,新的任務執行線程函數。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
void *thread_fun1(void *arg)
{
}
void * thread_fun2(void *arg)
{
}
int main()
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,thread_fun1,NULL);
pthread_create(&tid2,NULL,thread_fun2,NULL);
32
千鋒智能物聯網+嵌入式學科
。。。。
}
3、函數指針變量的定義
返回值類型(*函數指針變量名)(形參列表);
int(*p)(int,int);//定義了一個函數指針變量 p,p指向的函數
必須有一個整型的返回值,有兩個整型參數。
int max(int x,int y)
{
}
int min(int x,int y)
{
}
可以用這個 p存放這類函數的地址。
p=max;
p=min;
4、調用函數的方法
1.通過函數的名字去調函數(最常用的)
int max(int x,int y)
{
}
int main()
{
int num;
num=max(3,5);
}
2.可以通過函數指針變量去調用
int max(int x,int y)
{
}
int main()
{
33
千鋒智能物聯網+嵌入式學科
int num;
int (*p)(int ,int);
p=max;
num=(*p)(3,5);
}
5、函數指針數組
概念:由若干個相同類型的函數指針變量構成的集合,在內存中連續的順序存儲。
函數指針數組是個數組,它的每個元素都是一個函數指針變量。
函數指針數組的定義:
類型名(*數組名[元素個數])(形參列表)
int(*p[5])(int,int);
定義了一個函數指針數組,數組名是 p,有 5個元素 p[0] ~p[4],每個元素都是函數指針變量,
每個函數指針變量指向的函數,必須有整型的返回值,兩個整型參數。
例:
#include
int max(int x, int y)
{
int temp;
if (x > y)
temp = x;
else
temp = y;
return temp;
}
int min(int x, int y)
{
int temp;
if (x < y)
temp = x;
else
temp = y;
return temp;
}
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
34
千鋒智能物聯網+嵌入式學科
{
return x - y;
}
int mux(int x, int y)
{
return x * y;
}
int main()
{
int(*p[5])(int, int) = {mux,min,add,sub,mux};
int num;
num = (*p[2])(10,20);
printf("num=%d\n", num);
return 0;
}
6、函數指針應用舉例
給函數傳參
#include
int add(int x,int y)
{
return x+y;
}
int sub(int x,int y)
{
return x-y;
}
int mux(int x,int y)
{
return x*y;
}
int dive(int x,int y)
{
return x/y;
}
int process(int (*p)(int ,int),int a,int b)
{
int ret;
35
千鋒智能物聯網+嵌入式學科
ret = (*p)(a,b);
return ret;
}
int main()
{
int num;
num = process(add,2,3);
printf("num =%d\n",num);
num = process(sub,2,3);
printf("num =%d\n",num);
num = process(mux,2,3);
printf("num =%d\n",num);
num = process(dive,2,3);
printf("num =%d\n",num);
return 0;
}
6.1.12 經常容易混淆的指針概念
第一組:
1、 int *a[10];
這是個指針數組,數組 a中有 10個整型的指針變量
a[0]~a[9] ,每個元素都是 int *類型的指針變量
2、int (*a)[10];
數組指針變量,它是個指針變量。它占 4個字節,存地址編號。
它指向一個數組,它加 1的話,指向下個數組。
3、 int **p;
這個是個指針的指針,保存指針變量的地址。
它經常用在保存指針的地址:
常見用法 1:
int **p
int *q;
p=&q;
常見用法 2:
int **p;
36
千鋒智能物聯網+嵌入式學科
int *q[10];
分析:q是指針數組的名字,是指針數組的首地址,是 q[0]的地址。
q[0]是個 int *類型的指針。 所以 q[0]指針變量的地址,是 int **類型的
p=&q[0]; 等價于 p=q;
例 34:
void fun(char**p)
{
int i;
for(i=0;i<3;i++)
{
printf("%s\n",p[i]);//*(p+i)
}
}
int main()
{
char *q[3]={"hello","world","China"};
fun(q);
return 0;
}
第二組:
1、int *f(void);
注意:*f沒有用括號括起來
它是個函數的聲明,聲明的這個函數返回值為 int *類型的。
2、int (*f)(void);
注意*f用括號括起來了,*修飾 f說明,f是個指針變量。
f是個函數指針變量,存放函數的地址,它指向的函數,
必須有一個 int型的返回值,沒有參數。
6.1.13 特殊指針
1、空類型的指針(void *)
char * 類型的指針變量,只能保存 char型的數據的地址
int * 類型的指針變量,只能保存 int型的數據的地址
float* 類型的指針變量,只能保存 float型的數據的地址
void * 難道是指向 void型的數據嗎?
不是,因為沒有 void類型的變量
void* 通用指針,任何類型的地址都可以給 void*類型的指針變量賦值。
int *p;
37
千鋒智能物聯網+嵌入式學科
void *q;
q=p 是可以的,不用強制類型轉換
舉例:
有個函數叫 memset
void * memset(void *s,int c,size_t n);
這個函數的功能是將 s指向的內存前 n個字節,全部賦值為 c。
memset可以設置字符數組、整型數組、浮點型數組的內容,所以第一個參數,就必須是個通用指針
它的返回值是 s指向的內存的首地址,可能是不同類型的地址。所以返回值也得是通用指針
注意:void*類型的指針變量,也是個指針變量,在 32為系統下,占 4個字節
2、NULL
空指針:
char *p=NULL;
咱們可以認為 p哪里都不指向,也可以認為 p指向內存編號為 0的存儲單位。
在 p的四個字節中,存放的是 0x00 00 00 00
一般 NULL用在給指針變量初始化。
main函數傳參:
例 35:
#include
int main(int argc, char *argv[])
{
int i;
printf("argc=%d\n",argc);
for(i=0;i{
printf("argv[%d]=%s\n",i,argv[i]);
}
return 0;
}
38風干鋒熱百
千鋒智能物聯網+嵌入式學科
第1章環境搭建
第2章數據類型及語句
第3章數組
第4章函數
第5章預處理、動態庫、靜態庫
第6章指針
第7章動態內存申請
第8章字符串處理函數
pragma指令的作用是:用于指定計算機或操作系統特定的編譯器功能
#pragma warning(disable:4996)在c文件開始處寫上這句話,即告訴編譯器忽略4996警
告,strcpy、.scanf等一些不安全的標準c庫函數在vs中可以用了。
8.1測字符串長度函數
頭文件:#include
函數定義:size t strlen(const char*s);
函數功能:
測字符指針s指向的字符串中字符的個數,不包括0
返回值:字符串中字符個數
例1:
#include
做真實的自己,用良心做教育
風干鋒教百
千鋒智能物聯網+嵌入式學科
#include
int main(
char str1[20]="hello";
char *str2 ="hello";
printf("%d\n",sizeof(str1));//20
printf("%d\n",sizeof(str2));//4
printf("%d\n",strlen(str1))://5
printf("%d\n",strlen(str2))://5
return 0;
sizeof是個關鍵字,測量數據的占用內存空間大小。
如果測量的是數組的名字,則測的是數組占多少個字節
如果sizeof測的是指針變量,則測的是指針變量本身占幾個字節,32平臺下結果為4
strler是個庫函數,它測的是字符指針指向的字符串中字符的個數,不管指針是數組的名字,還是個指針
變量。
8.2字符串拷貝函數
頭文件:include
函數的聲明:char*strcpy(char*dest,const char*src);
函數的說明:
拷貝src指向的字符串到dest指針指向的內存中,0'也會拷貝
函數的返回值:
目的內存的地址
注意:在使用此函數的時候,必須保證dst指向的內存空間足夠大,否則會出現內存污染。
char *strncpy(char *dest,const char *src,size_t n);
函數的說明:
將src指向的字符串前n個字節,拷貝到dest指向的內存中
返回值:目的內存的首地址
注意:
1、strncpy不拷貝0
2、如果n大于src指向的字符串中的字符個數,則在dest后面填充n-strlen(src)個0'
例2:
#include
#include
做真實的自己,用良心做教育風干于鋒教百
千鋒智能物聯網+嵌入式學科
第1章環境搭建
1.1 Visual Studio軟件安裝
1、軟件下載路徑
鏈接:https:/pan.baidu.,com/s/1LaLe7 amFWF500WKc0 sTmnA
提取碼:i2sg
2、安裝
雙擊exe可執行程序
vs communi
y_7129101
96.1584880
824.ex6
Visual Studio Installer
開始之前,我們需要設置某些選項,以便你配置安裝。
若要了解有關隱私的詳細信息,請參閱Microsoft隱私聲明。
繼續即表示你同意Microsoft軟件許可條款。
繼續O)
做真實的自己,用良心做教有
1
風鋒教百
千鋒智能物聯網+嵌入式學科
Visual Studio Installer
稍等片刻正在提取文件。
正在下裁:2.99MB/74.25MB
860.45KB/秒
正在安裝
稍等一會
取消(q
EL一M.iammnly一1hL
表治雪
Aehr三d
安裝詳知信已
11ab女女
>Voual5udie民.心e
v快刀C+中的夏面環發
c+rT:
=60g打C+C:1年
Tn
夏=子w2生太⊥C+nG打k
52,牛右,1:3★hmI
BDOst.Tect2h
G03eT15a
a Lies Shaw
日2測m季m

Visual Studio Installer
感可用
Visual Studin Community 201
T發人員新聞
工E一8生:7M1195n
(1:
Anncurcirg NET G Frew
H性等待一時回
Miae3i.w3山u時Hn.in&cmmniy
ANLI Cete updilss in.Ns Wavie 1
安片
NETGPreeNow avable an
行明
1+=月匠黑
aierairgyaiml4
edy wcre codted to zrneunse our Re ease
4只月肛8
重Z棗巴代機
AE
衛.下M+之30712
做真實的自己,用良心做教育
2
風干鋒教百
千鋒智能物聯網+嵌入式學科
1.2注冊
A)創建一個賬戶
Visual Studio
登錄Visual Studio
巖設活同路設空
使用ireShare時r作
與A2山re深天數元
B)輸入一個電子郵箱地址
Microsoft
創建帳戶
someone@
改為使用電話號碼
獲取新的電子郵件地址
下一步
C)設置一個登錄密碼
做真實的自己,用良心做教育
3

展開更多......

收起↑

資源列表

<pre id="tfb94"><li id="tfb94"></li></pre>

<bdo id="tfb94"><rt id="tfb94"></rt></bdo>
  • <menu id="tfb94"><dl id="tfb94"></dl></menu><i id="tfb94"><acronym id="tfb94"><sub id="tfb94"></sub></acronym></i>

    1. 主站蜘蛛池模板: 腾冲县| 樟树市| 株洲县| 乐至县| 东乡县| 资阳市| 郴州市| 株洲市| 铅山县| 华亭县| 梧州市| 佳木斯市| 郑州市| 卢龙县| 奉化市| 青神县| 新竹县| 康平县| 武宁县| 柳江县| 宜州市| 江达县| 咸丰县| 平顶山市| 吴川市| 德庆县| 卓资县| 江孜县| 会昌县| 武山县| 乐业县| 新昌县| 凌云县| 横山县| 星子县| 黄山市| 固始县| 无为县| 张家口市| 浦东新区| 马公市|