自定義函數
(1)在函數使用之前定義函數
(2)先聲明這個函數,然后使用,使用的代碼后面定義這個函數include <stdio.h>
void syahello(){
println("helloo");
}
int main(){
sayhello(); // 調用的函數要提前寫出來,如果要寫在后面,需要先在前面聲明
}
void syahello(); //提前聲明,沒有實現
int main(){
sayhello(); // 調用的函數要提前寫出來,如果要寫在后面,需要先在前面聲明
}
void syahello(){
println("helloo");
}
mytest1(); //如果函數沒有明確標明函數的返回值類型,則默認為返回int(不要這樣寫)
print("%d\n",mytest1());
2.形參和實參是值傳遞v,是單向傳遞,只能由實參傳遞給形參,不能由形參傳遞給實參
入股哦函數的參數是數組,則可以通過形參修改實參的值
void testarr(char s[]){
s[0] = 'a';
s[1]='b';
}
int main(){
char chs[100] = "hello world"
testarr(chs);
printf("%d",chs); // 數組變為abllo world
}
void mergearr(char arr1[],char arr2[]){
int len1 = 0;
while(arr1[len1++]);
len1--; // 記錄數組一的長度
int len2=0;
while(arrp[len2+1]);
len2--;int i;
for(i=0;i<len2;i++){arr1[len1+i] = arr2[i];
}
}
int main(){
char arr1[100]="asdad";
char arr2[100]="1111";
mergearr(arr1,arr2);
printf("%s\n",arr1); // 發現數組2已經合并了
printf("%s\n",arr2);
}
二. 多文件編譯
- <stdio.h> : 尖括號表示文件在系統路徑中
"abc/myhead.h":表示abc目錄下的myhead.h
-------------------------
myhead.h:
#define MAX 300 // #define就是簡單的字符串替換
main.c:
include <stdio.h>
include "abc/myhead.h"
int main(){
int c = MAX;
printf("%d",c)
}
三. 指針
計算機內存的最小單位使BYTE
每個BYTE的內存都有一個唯一的內存編號,這個編號就是內存地址
編號在32為系統下是一個32位的整數,編號在64位系統下是一個64位的整數
- 用指針型變量來表示這個內存地址號。
- :指針變量指向的內容。指針型變量不能向int變量一樣顯示的聲明出來,只能用變量名來聲明一個int值,則該變量是指針型變量,*變量名是一個int值
- &:得到變量對應的指針變量(內存地址)
void main{
int a=3;
int p; // p是int型變量,p是指針型變量
p = &a; // 指針型變量就是內存號,所以&符號取得a的內存號
int b = p;
printf("%d",b);
p = 100;
printf("%d",a) // 通過內存號修改變量值
char chs[10];
printf("%u,%u,%u,%u",chs,chs[0],chs[1],chs[2]) // %u:unsigned無符號整形,數組首地址,第一個元素的地址,第二個元素的地址int ints[10];
printf("%u,%u,%u,%u",ints,ints[0],ints[1],ints[2]) // %u:unsigned無符號整形,數組首地址,第一個元素
}
無類型指針:
void *p:說明p只是一個指針比那輛,而不去指向任何一個變量
-----------------------------------include <stdio.h>
void main(){
int a = 100;
int p; // int p是聲明寫法:整體代表一個地址號,這個地址號上存著整數。單獨一個p:定義寫法,也是內存號
int q;
p = &a;
//int p = 1; // p指向的地址號是1(聲明+定義寫在了一起)
printf("%u",p); // 3852910012
printf("%u",*p); // 100
printf("%d\n",sizeof(p)); //8:指針在64位系統下就是一個64位的整數,所以占8字節。無論指針指向什么類型的數據,64位下都占8字節
printf("%d\n",sizeof(a)); //4:int型占4字節int p; //聲明指針,沒有指向,就是野指針。野指針使導致程序崩潰的主要原因
p = 1; // 為地址上賦值1,會出現錯誤 [1] 6285 segmentation fault (core dumped) a.outint *p = NULL; // 空指針使合法的,野指針是非法的
}
/** int p 與p一致
/
include <stdio.h>
void main(){
int a =100;
int p = &a;
int q;
q = &a;
printf("p:%u,q:%u\n",p,q); // p:2373644580,q:2373644580
printf("p:%d,q:%d",p,q);// p:100,q:100
}
指針的兼容性
include <stdio.h>
void main(){
int a = 0X1013;
char b = 13;int p;
p=&a;
printf("%x",p); // 1013p=&b; // 10130d 用int指針指向char類型變量,因為int指針指向了連續4字節的地址,所以除了1013外后面還有3個字節的數據當做一體來指向
printf("%x",*p);int q;
char buf[] = {0x12,0x34,0x56,0x78,0x90,6,7,8,9,10};
q = buf;
printf("%x\n",q);//指針不兼容的情況
float i = 3.14;
int p = &i;
printf("%d",p); // 此時并不能打印出3,這就是指針類型不兼容
}指向常量的指針和指針常量
void main(){
int a = 100;
const int p = &a; // 指向常量的指針只能讀內存,但可以改變指向
printf("%d\n",p); // 100int const q = &a; // 常量指針可以讀寫指向的內存,但不能改變指向
q = 200;
printf("%d\n",*q); //200
printf("%d\n",a); //200
}數組與指針
void printarr(char s[]){
int i;
for(i=0;i<10;i++){
printf("s[%d]\n", %d);
}
}
void main(){
char buf[10] = {1,2,3,4,5,6,7,8,9,0};
char p = buf;
char p1 = &buf[0];
char *p2 = &buf[1];printf("%d\n", p); //1
printf("%d\n", p1); //1
printf("%d\n", *p2); //2p2 ++; // 指針是地址號,p+1
*p2 = 100; // 更改為100
printarr(buf);
}
-------------------------------------
ip地址在網絡中不是以字符串傳輸,字符串太長,而是以DWORD傳輸(4字節),飆戲那位一個int數值include<stdio.h>
void change2char(int i){
unsigned char p = &i;
printf("%d.%d.%d.%d",p,(p+1),(p+2),(p+3));
}
void main(){
int a = 0;
unsigned char p1 = &a; // 無符號使得最大值為255
*p1 = 192;
/**(p+1) = 168;
(p+2) = 0;
(p+3) = 1;/
p1++;
p1 = 168;
p1++;
p1=0;
p1++;
p1=1;
printf("ip:%d",ip);
// a已經被改變為一個ip地址
change2char(a);
}
------------------
int s2ip(char s[]){
int a = 0;
int b = 0;
int c = 0;
int d = 0;
sscanf(s,"%d.%d.%d.%d",&a,&b,&c,&d);
printf("a=%d,b=%d,c=%d,d=%d\n", a,b,c,d);
int ip=0;
char p = &ip;
p++;
p = a;
p++;
p=b;
p++;
p=c;
p++;
*p=d;
}
void main(){
char s[] = "192.168.0.1";
printf("%d\n", s);
}
eg:用指針合并數組
void main(){
char s1[] = "hello";
char s2[]="world";
char p1 = &s1;
char p2 = s2;
while(p1){ //p1指向的元素為0時跳出循環
p1 ++;
}
while(p2){
p1++ = *p2++; // 先指向,后指針移位
}
}
指針運算不是簡單地數值加減法,而是指針指向的數據類型在內存中所占字節數為倍數的運算
void main(){
int buf[10];
int *p = buf;
printf("%d\n", p); //-1670667696
p+=3;// 實際上內存地址好漲了12
printf("%d\n", p);
p = (int)p+3; // 此時把內存地址號強制轉為int型,此后加3就是數值運算:-1670667684
printf("%d\n", p); // -1670667681
char *p2 = buf;
p2 += 4;
printf("%d\n", *p2); // 0short *pp1 = &buf[1];
short *pp2 = &buf[3];
printf("%d\n", pp2 - pp1); // buf3和buf1差8個字節,但是一個short指針變量指向2個字節的連續塊,所以值位4
}
二維數組的指針
void main(){
int buf[2][3] arr = {{1,2,3},{4,5,6}};
// int p[3] ; //定義一個指針數組
int (p)[3]; // 定義了一個指針,指向int[3]這種數據類型,也叫作指向二維數組的指針
printf("%d\n",sizeof(p)) ; //8 因為指針在64位系統都是占8位
printf("%d,%d\n",p,p+1); //相差12字節
p = buf; //p指向了二維數組的第一行
p++; //p指向了二維數組的第二行// 指針遍歷
int i,j;
for(i=0;i<2;i++){for(j=0;j<3;j++){//printf("%d\n", *(*(p+i))); //打印結果1,1,1,4,4,4printf("%d\n", *(*(p+i)+j); // 遍歷每個元素printf("%d\n", p[i][j]); // 每個元素,與上面相等}
}
}
指針作為函數參數
void test(int n){
n++;
}
void test2(int p){
(p)++;
}
void main(){
int i =100;
test(i); //c語言是值傳遞,所以普通函數參數的改變是不能傳回給參數的,但如果參數是地址,改變地址上的值
printf("%d\n", i);
test2(&i);
printf("%d\n", i);
}
一維數組作為參數的指針
void swap(int a,int b){
int tmp = a;
a = b;
b = tmp;
}
// 當數組名作為參數傳遞時,數組名解析為數組首地址指針,所以多寫為void setarray(int buf)
void setarray(int buf[]){
printf("占%d位\n", sizeof(buf)); //8:64位系統指針占8字節
buf[0] = 100;
buf[1]=200;
int i;
for(i=0;i<10;i++){
printf("%d\n", buf[i]);
}
}
void setarray(int buf,int n){ // n為數組的長度
buf[0] = 100;
buf[1]=200;
int i;
for(i=0;i<n;i++){
printf("%d\n", buf[i]);
}
}
void main(){
int a = 10;
int b = 20;
swap(&a,&b);
printf("%d,%d\n", a,b);
// 因為數組名是數組的首地址,所以數組名作為行參傳遞的時候,就相當于傳遞的指針
int buf[]= {1,2,3,4,5,6,7,8,9,0};
printf("%d\n", sizeof(buf)); //40
setarray(buf,sizeof(buf)/sizeof(int)); // 數組長度寫成sizeof(buf)/sizeof(type)
}
二維數組的指針作為函數參數
void printarr2(const int (*p)[4],int m,int n){//mn分別是二維數組的第一個和第二個角標。通過const標識,保護函數不會改變數組中的值
int i,j;
for(i=0;i<m;i++){
for(j=0;j<n;j++){
printf("p[%d][%d] = %d\n", i,j,p[i][j]);
}
}
}
int main{
int buf[][4] = {{1,2,3,4},{5,6,7,8}};
printarr2(buf,sizeof(buf)/sizeof(buf[0]),sizeof(buf[0])/sizeof(int));
return 1;
} 33 min 18 s
--------------計算二維數組每一列的平均值----------------
include<stdio.h>
void main(){
int buf[2][4] = {{1,2,3,4},{5,6,7,8}};
for(int i=0;i<4;i++){
int sum = 0;
for (int j = 0; j < 2;j++)
{
sum += buf[j][i];
}
printf("%d\n", sum);
}
}
函數指針
函數也有地址,這個地址存放的是代碼
int getmax(int x, int y){ // 也可以寫為int getmax(int x, int y) 意思一樣,函數名同數組名一樣,都是即表示本身,又表示指向自身開頭的指針
return x>y?x:y;
}
int getmin(int x, int y){
return x<y?x:y;
}
int main(){
int (p)(int,int) ; //定義一個指針p指向函數,該函數反獲知是int,2個輸入參數是(int,int)
int fun = 0;
scanf("%d",&fun);
if(fun==1){
p = getmax; //函數名就是指針
}else{
p = getmin; //函數名就是指針
}
int i = p(2,4); // 通過函數指針調用函數
printf("%d\n", i);
}
31min
函數指針是指向函數的指針變量
void p(int,char) // 定義一個函數,函數名為p,參數為int和char類型,返回值為void類型
void (p)(int,char) // 定義一個指針,該指針指向函數。返回值為void, 參數為int和char類型
-----------------------------------------------
將一塊內存初始化為0最常見的方法:memset(buf,值,size(buf)):要設置的內存地址,設置的值,內存大小(單位字節)
memcpy(buf2,buf1,sizeof(buf1)):將buf1的內容全部拷貝到buf2
memmove (buf2,buf1,sizeof(buf1));
include <string.h>
include <stdio.h>
void main(){
int buf[10] = {1};
memset (buf,0,sizeof(buf));
int buf2[] = {1,2,3,4,5,6,7,8,9,0};
int buf3[];
memcpy(buf3,buf2,sizeof(buf2));
for (int i = 0; i < 10; i++)
{printf("%d\n", buf2[i]);
}
}
指針數組與多級指針
int main(){
int a[10]; //一個數組,數組內有10個元素,64為系統中,指針站8字節,64位
printf("%d,%d\n", sizeof(a),sizeof(a[0])); // 40,8
short b[10];
printf("%d,%d\n", sizeof(b),sizeof(b[0])); // 40,8
}
int main(){
int a = 10;
int p = &a;
int pp = &p; // pp指向p的地址
pp= 100; //修改a的值 如果p=100,則是把p這個地址號改為100
printf("a=%d\n",a);
int ppp = &pp;
a = ppp;
println("%d,%d",ppp,&pp); // 三級指針的內容=二級指針的地址
}
指針完成字符串操作
int mian(){
char buf[100] = "hello world";
char p = buf;
p += 5;
p = 'c';
printf("%s\n", buf); // hellocworld
return 1;
}
如果字符串中包含0字符,則打印到此位置就結束了。淚庫里面的函數都是有\0識別字符串結尾的
c語言主函數也是有輸入參數的,而且有2個,第一個表示輸入參數的個數,第二個是一個指針數組,每個指針指向char類型
輸入參數個數:永遠大于1,因為程序名本身作為一個輸入參數
指針數組:這個指針數組的長度是第一個參數,每個元素指向一個輸入參數
int main(int argc,char *args[]){
if(argc!=4){
printf("請輸入完整的數學表達式,以空格分開" );
return 0;
}
int a = atoi(args[1]);
int b = atoi(args[3]);switch(args[2][0]){case '+':printf("%d\n", a+b);break;case '-':printf("%d\n", a-b);break;case '*':printf("%d\n", a*b);break;case '/':if(b){printf("%d\n", a/b);}break;
}
return 0;
}
內存管理
作用域
(1)文件作用域:全局變量,定義在函數外面的變量
int a=10;
void main(){
int a = 20; // 函數內部可以再次定義和全局變量同名的變量,定以后a為20,沒有定義為10
printf("%d\n", a);
}
(2)extern關鍵字include "c.h" #在這個文件中定義了age=10
extern int age ;// 該變量在其他文件中定義了,連接時使用就可以了。這個另一個文件需要時.c文件,不能是.h文件。gcc編譯時后面加上兩個文件名
setage(int n){
age = n;
}
getage(){
printf("%d\n", age);
}
void main(){
setage(11);
getage(); // 11
}
(3)auto自動變量:不寫auto,就默認為auto變量
(4)register 變量:建議變量放到空閑的寄存器中
寄存器變量不能取地址操作
(5)static 變量:只初始化一次,程序運行期間一直存在 。static只是增大了變量的存在時間,卻沒增大變量的作用域
一旦全局變量被static修飾,則這個變量的作用域被限制在該文件內部,其他文件不能用extern使用
void mystatic(){
static int a = 0; // 不加static,循環打印每次打印都是0,加上static每次打印+1
printf("%d\n", a);
a++;
}
void main(){
for (int i = 0; i < 10; ++i)
{
mystatic();
}
}c語言中,函數都是全局的。
(1)如果函數前面加了static,則函數被限制在文件內部使用
(2)函數前面加不加extern一樣。而變量前面的extern是可以省略的,比如:int a; 如果其他文件中定義了int a=10,則此處的變量是聲明,如果其他文件中沒有定義a,則此處是定義
(此處也是說明c語言并不嚴謹)c語言內存管理
程序在內存中分為4個區域:
(1)堆
(2)棧:此部分的內存表現為先進后出。所有自動變量,函數行參都有編譯器自動放到棧中。當一個自動變量超出其作用域時,自動從棧中彈出。先進入棧中的變量放到大內存號中
(3)靜態區:所有的全局變量,以及程序中的靜態變量都存儲在靜態區
(4)代碼區:程序在被操作系統加載到內存中時,所有可執行代碼加載到代碼段,這塊內存不能再程序運行期間修改
int c= 0;
void main(){
int a=1;
int b=2;
static int d = 3;
printf("%d,%d,%d,%d,%d\n", &a,&b,&c,&d,main); // 1321929276,1321929272,6295612,6295604,4195632
}
上述程序的a和b在棧區,所以地址號緊挨著:1321929276,1321929272 發現a所在的內存號在高位。
c和d在靜態區:地址號緊挨著
main在代碼段
二進制文件讀寫
- fscanf:讀文件時,可以根據固定格式讀取
fprintf:寫文件,和printf一樣,可以定義輸出格式,只是輸出到文件中
include <stdio.h>
include <string.h>
include <stdlib.h>
void main(){
FILE *p = fopen("a.txt","r");
while(!feof(p)){
int a,b;
fscanf(p,"%d + %d =",&a,&b); //文本格式為a + b =,從中截取a,b
printf("%d,%d",a,b);
}
}
void main(){
FILE *p = fopen("a.txt","w");
char buf[100] = "hello world fuck ";
int a=1,b=2;
fprintf(p,"%s,%d,%d",buf,a,b);
fclose(p);
}
- fread與fwrite讀寫二進制文件
上面的函數只能操作字符文件,字符文件每次只能讀一行
void main(){
FILE *p = fopen("a.txt","rb"); // 以二進制方式讀取
char buf[100] = {0};
fread(buf,sizeof(char),1,p); // 讀取字節的緩沖區,讀取單位,一次讀取幾個單位,文件指針
printf("%s\n", buf);
fclose(p);
}
指針數組與多級指針
int main(){
int a[10]; //一個數組,數組內有10個元素,64為系統中,指針站8字節,64位
printf("%d,%d\n", sizeof(a),sizeof(a[0])); // 40,8
short b[10];
printf("%d,%d\n", sizeof(b),sizeof(b[0])); // 40,8
}
int main(){
int a = 10;
int p = &a;
int pp = &p; // pp指向p的地址
pp= 100; //修改a的值 如果p=100,則是把p這個地址號改為100. pp就是地址號的內容相當于改變p的內容,pp就是地址號中記錄的地址號的內容
printf("a=%d\n",a);
int ppp = &pp;
a = ppp;
println("%d,%d",**ppp,&pp); // 三級指針的內容=二級指針的地址
}
指針完成字符串操作
int mian(){
char buf[100] = "hello world";
char p = buf;
p += 5;
p = 'c';
printf("%s\n", buf); // hellocworld
return 1;
}
如果字符串中包含0字符,則打印到此位置就結束了。淚庫里面的函數都是有\0識別字符串結尾的
c語言主函數也是有輸入參數的,而且有2個,第一個表示輸入參數的個數,第二個是一個指針數組,每個指針指向char類型
輸入參數個數:永遠大于1,因為程序名本身作為一個輸入參數
指針數組:這個指針數組的長度是第一個參數,每個元素指向一個輸入參數
int main(int argc,char *args[]){
if(argc!=4){
printf("請輸入完整的數學表達式,以空格分開" );
return 0;
}
int a = atoi(args[1]);
int b = atoi(args[3]);switch(args[2][0]){case '+':printf("%d\n", a+b);case '-':printf("%d\n", a-b);case '*':printf("%d\n", a*b);case '/':if(b){printf("%d\n", a/b);}
}
return 0;
}
棧:不會很大,棧空間是操作系統為每個程序固定分配的大小以k為單位。棧空間是操作系統為每個程序固定分配的大
每個線程都有自己的棧
int main(){
char array[102410241024] = {0}; //定義一個超大的數組就會棧溢出
return 0;
}
堆:堆沒有大小限制,只是無力限制。但是堆內存不能由編譯器自動釋放
include <stdlib.h>
int main(){
int p = malloc(sizeof(int)10); // 在堆中申請了10個int的大小
char p2 = malloc(sizeof(char)10);
memset(p,0,sizeof(int)*10); // malloc申請的堆內存 ,在代碼的操作上相當于數組
for(int i=0;i<10;i++){ p[i] = i; // 操縱數組
}free(p) ; // 釋放通過malloc申請的堆內存
free(p2);
}
不能將一個棧變量的地址作為函數的返回值,e因為函數結束后,棧變量被釋放
eg: int *geta(){
int a = 10;
return &a;
}但是可以把堆變量的地址作為函數返回值返回
int geta(){
int p = malloc(sizeof(int));
return p;
}
int main(){
int *p = geta();
malloc(p);
}
一個經典的錯誤模型:
void getheap(int p){
p = malloc(sizeof(int)10);
}
void main(){
int *p = NULL;
getheap(p); // 此處是錯誤的,因為函數的參數是值傳遞,雖然語義是getheap(p),但是mallo的內存地址只是付給了函數的形參,函數退出后,形參的值并不能傳遞給實參(指針的值也一樣不能傳遞),導致p還是空指針,下面的p[0]=1發生空指針異常
p[0] = 1;
printf("%d\n", p[0]);
free(p);
}經典的正確模型:二級指針作為形參,對上面錯誤模型的改寫
void getheap(int **p){
p = malloc(sizeof(int)10); // 所致地址號的內容,就是指以及指針的內容
}
void main(){
int *p = NULL;
getheap(&p); // 此處是錯誤的,因為函數的參數是值傳遞,雖然語義是getheap(p),但是mallo的內存地址只是付給了函數的形參,函數退出后,形參的值并不能傳遞給實參(指針的值也一樣不能傳遞),導致p還是空指針,下面的p[0]=1發生空指針異常
p[0] = 1;
printf("%d\n", p[0]);
free(p);
}
不能用變量聲明一個數組,eg:int arr[i], 如果申請內存的大小靠程序運行期間決定,那么就要使用 malloc(sizeof(int)*i),要記得free
代碼區和靜態區的大小都是固定的,靜態區是編譯器根據代碼提前申請的。堆和棧所占大小是動態,但是棧有一個操作系統規定的棧上線
cd /proc // 每個進程號都是一個文件
cd pid
cat maps // 查看內存映射,這里列出了代碼段,堆,棧,靜態區的使用情況(棧的預設范圍)
堆的分配和
操作系統分配內存的單位不是字節,而是頁。cat smps之后,發現堆有個項是內存頁大小:4k。頁太大,浪費的內存空間太大,但操作系統維護起來很簡單,因為不用頻繁的申請釋放內存
malloc申請的堆內存上會存在舊數據,所以申請后需要用memset清空 : memset(p,p,sizeof(int)10)
這兩句話的簡寫就是calloc,這樣內存上全部是0
int main(){
char p = calloc(10,sizeof(char)); // 申請大小是10*sizeof(char)
}
relloc用于擴展已經申請的堆內存,在原有內存上,增加連續的內存,如果沒有連續空間可擴展,則會新分配一個連續空間,將原有內存拷貝到新空間,然后釋放原有內存
但是relloc只申請,不打掃(內存中有舊數據)
rello(p,新的內存大小),.如果p為NULL,則realloc等于malloc,不指定分配空間的起始位置
void main(){
char p1 = calloc(10,sizeof(char));
char p2 = realloc(p1,5); // p1指向的地址變為5
// realloc(NULL,5) = malloc(5)
}
結構體:
include <string.h>
struct student{
char name[100];
int age;
int sex;
}; // 聲明一個結構體
void main(){
struct student st; // 定義了一個結構體的變量,名字叫st
//struct student st2 = {.name="周杰倫",.sex=0}; 結構體中通過.來給屬性賦值
struct student st3 = {0}; // 定義結構體變量,并把結構體變量的所有屬性設置為0
struct student st4;
memset(&st,0,sizeof(st)); // 結構體變量初始化
st.age = 25;
st.sex = 1;
strcpy(st.name,"劉德華");
printf("%d\n", st.age );
}
- 結構體的內存對齊模式:
結構體在內存中是一個矩形,而不是一個不規則形狀,以結構體中占地最大的元素對齊。而且不同類型的屬性要跨偶數個字節
struct A
{
int a; // 4字節
char b; // 以4字節對齊
}; // 總共8字節
struct B
{
char a; // 1個字節
short b; // short不同于char類型,所以要夸2字節,從第3個字節開始對齊 , 占2個字節
char c; // 占1個字節
int d; // 第二個8字節對齊行
long long e; // 8個字節,占地最多的屬性,所以其他屬性以8字節對齊;
};
printf("%d\n", sizeof(B));
- 結構體成員的位字段
struct A
{
char a:2; // char占1個字節,但是這樣聲明的結構體中的a字段,只有前2bit能通過結構體使用,但值得注意的是此時整個結構體仍然占1個字節而不是2bit
};
void main(){
struct A a;
a.a = 3; //二進制11,有符號char,所以會打印-1
printf("%d\n", );
}
struct B
{
char a:2;
unsigned char b:4;
char c:1;
};
printf("%d\n", sizeof(struct B));
- 指針操作結構體不了的內存地址
struct H
{
char a; // 1個字節,但是后三個字節由于結構體對齊而操作不了
int b;
};
void mian(){
struct H h = {1,2};
char p1 = &h;
p1 ++;
p1 = 4;
(++p1) = 5;
(++p1) = 6;
*(++p1) = 7;
printf("%d\n", &h+4);
}
- 結構體模仿數組(用字段地址連續的結構體成員)
struct D
{
char a;
char b;
char c;
};
void main(){
struct G g;
char *p = &g;
p[0] = 'a';
p[1] = 'b';
p[2] = 'c';
printf("%d,%d,%d\n", g.a,g.b,g.c);
}
- 結構體數組
struct People
{
int age;
char name[10];
};
void main(){
struct People p[3] = {{11,"aa"},{22,"bb"},{33,"cc"}};
for (int i = 0; i < 3; ++i)
{
printf("%d,%s\n", p[i].age,p[i].name);
}
}
- 結構體可以變相實現數組的賦值
struct A
{
char arr[10];
};
void main(){
struct A a1 = {"hello"};
struct A a2= {0};
a2 = a1;
printf("%s\n", a2.arr);
}
- 結構體的嵌套
struct A{
char a;
char b;
}
struct{
struct A a;
int b;
} // 一共占8字節
結構體賦值,其實就是結構體之間內存的拷貝
struct strss
{
char s[100];
};
void main(){
struct strss s1,s2;
strcpy(s1.s,"hello");
// s2 =s1; 這句話等同于下面的memcpy,結構體是變量,所以可以賦值
memcpy(&s2,&s1,sizeof(s1));
printf("%s\n", s2.s);
}
指向結構體的指針操作結構體
void main(){
struct A a;
struct A p = &a;
// (p).a = 10; 這樣寫是對的,但是太麻煩,沒人這么寫
p -> a = 10; // 用->代表結構體的一個成員
}
指向結構體數組的指針
<string.h><stdlib.h>
void main(){
struct A *p ;
/* struct A array[10] = {0};
p = array; // 指針指向數組首地址 *///也可以在堆中創建結構體
p = malloc(sizeof(struct A) * 10);
memset(p,0,sizeof(struct A) * 10)
struct A *array = p;p->a = 10;
p->b = 11;p++; // p已經變化了,所以free時要從頭free,就是array。如果free(p),就從當前p的位置往下數10個字節,這樣就free了不屬于結構體數組的內存,運行報錯
p->a = 3
p->b = 4;for (int i = 0; i < 10; ++i)
{printf("%d,%d\n", array[i].a,array[i].b);
}free(array);
}
結構體作為函數參數時,盡量使用指針傳遞參數,不要直接使用結構體作為形參
因為結構體和int等變量一樣,作為參數傳遞時是值拷貝傳遞,如果傳遞指針,則函數只需要拷貝8字節的地址號,而且如果函數想要操作結構體的話,也只能通過指針進行參數操作
void setname(struct student a,int age){
a->age = age;
}
四。聯合體
- 聯合體所占內存的大小等于聯合體中最長屬性所占狄村的大小,聯合體的所有屬性公用一塊內存地址
union A{
int a;
char b;
}
void main(){
union A a;
a.a = 0X12345678;
printf("%d\n", sizeof(union A)); // 4 ,最長的int屬性,棧4字節
printf("%p,%p\n", &a.a,&a.b); // 2個地址一樣
printf("%d,%d\n", a.a,a.b); // 12345678, 78
}
一個經典的錯誤寫法
union A{
int a;
char *b;
}
void main(){
union A a;
a.b = malloc(100);
a.a = 100;
// 此時改變了a的值就是改變了b的值,下面free的地址就不再是原來的地址,會報錯
free(b)
}
所以:如果一個聯合體中包含了含有指針變量,那么一定要在使用完這個指針后,病free掉它,才能使用聯合體的其它成員
五。 枚舉
- 枚舉是一個int常亮,不能改變他的值
- 美劇中的每個量都相當于#define x 整數
//#define red 0 相當于下面的枚舉定義
//#define black 1
//#define yellow 2
enum A{
red,black,yellow
};
void main(){
printf("%f\n", red); // 打印0
}
六。typedef
typedef char BYTE; 自定義一個BYTE類型,作為char類型
相當于define BYTE char
typedef 通常用于簡化結構體變量的名字為一個簡短的名字
struct A{
int a;
}
typedef struct abc A; // 本來聲明變量時需要struct A a, 現在只需要abc A即可typedef定義結構體的簡寫形式
typedef struct A{
int a;
}abc; // 張揚相當于上面2句話的簡寫, A也可以不寫形成第三種寫法,如下
typedef struct {
int a;
}abc;
typedef定義函數指針
include <string.h>
include <stdio.h>
void mystrcat (char s1,char s2){
strcat(s1,s2);
}
// 定義一個函數,有三個參數,函數指針,形參1,形參2
void test(void (p)(char ,char ),char s1,char s2){//函數指針p,返回值是void ,形參有兩個,都是char *類型
p(s1,s2);
}
// 上面的test函數太復雜,因為參數里包含一個函數指針
typedef void (MYSTRCAT)(char ,char ) // 把一個函數指針定義為MYSTRCAT類型
void main(){
MYSTRCAT aa[10];
char s1[100] = "hello";
char s2[5] = "world";
test(mystrcat,s1,s2);
printf("%s\n", s1);
}
七。文件操作
寫文件
include <stdio.h>
int main(){
FILE *p = fopen("aaa.txt","w");
fputs("hello world\n",p);
fclose(p);
return 0;
}
讀文件
include <stdio.h>
include <stdlib.h>
int main(){
char s[1024] = {0};
FILE *p = fopen("aaa.txt","r");
while(!feof(p)){ // 如果文件已經到最后,則文件返回真)
memset(s,0,sizeof(s));
fgets(s,sizeof(s),p); // 從p位置讀取10個字節
printf("%s", s);
};
fclose(p);return 0;
}
如果文件不存在,則新建文件,如果存在就追加文件內容
fopen("aaa.txt","a");