指針
指針的定義
指針的類型
指針的指向內容
指針的運算
數組與指針
指針與函數
動態分配內存
結構體
文件讀寫
頭文件與實現文件實例之計算器
文件操作訓練之字符串查找
指針
指針的定義
- 指針是一個變量
- 指針只能存地址
- 指針占據8個字節空間
總結:
指針是一種保存變量地址的變量
int main(){ int *a; char *b; printf("a的大小:%d\n", sizeof(a)); printf("a的地址:%p\n",a); printf("%d\n", sizeof(b)); } 輸出: a的大小:8 a的地址:0000000000000001 8
- 指針的聲明
int *p;//聲明一個int類型的指針p char *p;//聲明一個char類型的指針p int *arr[5];//聲明一個指針數組,數組內5個元素,每個元素都是一個指向 int 類型對象的指針 int **p;//聲明一個指針p,指針指向一個int類型的指針
- 指針的聲明相對于普通變量的聲明多了一個一元運算符 “
*
”。- 運算符 “
*
” 是間接尋址或者間接引用運算符。當它作用于指針時,將訪問指針所指向的對象。- p 是一個指針,保存著一個地址,該地址指向內存中的一個變量;*p 則會訪問這個地址所指向的變量。
- 聲明一個指針變量并不會自動分配任何內存。
- 在對指針進行間接訪問之前,指針必須進行初始化:或是使他指向現有的內存,或者給他動態分配內存,否則這個指針會變成野指針。
- 指針初始化
/* 方法1:使指針指向現有的內存 */ int a = 5; int *p = &a;
*
: 定義的時候表明是一個指針變量,使用的時候表示取地址的值
&
: 取某一個變量地址指針初始化/* 方法2:動態分配內存給指針 */ int *p; p = (int *)malloc(sizeof(int) * 10); // malloc 函數用于動態分配內存 free(p); // free 函數用于釋放一塊已經分配的內存
指針的初始化實際上就是給指針一個合法的地址,讓程序能夠清楚地知道指針的指向,而不至于變為野指針
指針的類型
判斷指針類型的方法:
去掉星號*和變量名就是指針的類型
int p;//P是一個普通的整型變量*
int *p;//P是一個返回整型數據的指針
P與*結合,所以說明P 是一個指針,然后再與int 結合,說明指針所指向的內容的類型為int型.
int p[3]; //所以P 是一個由整型數據組成的數組
P與[]結合,說明P 是一個數組,然后與int 結合,說明數組里的元素是整型的.
int *p[3]; //P 是一個由返回整型數據的指針所組成的數組
P與[]結合,因為其優先級比高,所以P 是一個數組,然后再與結合,說明數組里的元素是指針類型,然后再與int 結合,說明指針所指向的內容的類型是整型的.
int (*p)[3]; //P 是一個指向由整型數據組成的數組的指針
P與*結合,說明P 是一個指針然后再與[]結合(與"()"這步可以忽略,只是為了改變優先級),說明指針所指向的內容是一個數組,然后再與int 結合,說明數組里的元素是整型的.
int **p;//P是一個指向整型數的指針的指針(二級指針)
P與結合,說是P 是一個指針,然后再與結合,說明指針所指向的元素是指針,然后再與int 結合,說明該指針所指向的元素是整型數據.
int p(int);//P是一個參數和返回值都為int的一個函數
P與()結合,說明P 是一個函數,然后進入()里分析,說明該函數有一個整型變量的參數,然后再與外面的int 結合,說明函數的返回值是一個整型數據.
int (*p)(int);//P 是一個指向有一個整型參數且返回類型為整型的函數的指針
P與指針結合,說明P 是一個指針,然后與()結合,說明指針指向的是一個函數,然后再與()里的int 結合,說明函數有一個int 型的參數,再與最外層的int 結合,說明函數的返回類型是整型.
指針的指向內容
指針存儲的內容為變量的地址,也就是說指針的是一個指向作用,指向變量所存儲的內容
int main(){ int a = 5; int *p = &a; return 0; }
指針指向指針的運算
指針+(-)整數
可以對指針變量 p 進行 p++、p--、p + i 等操作,所得結果也是一個指針,只是指針所指向的內存地址相比于 p 所指的內存地址前進或者后退了 i (對應指針指向類型對應大小)個操作數。
int main(){ char a = '1'; char *p = &a; printf("p:%p\n",p); p++; printf("p++之后結果:%p\n",p); p--; printf("p--之后結果:%p\n",p); p+=5; printf("p+5之后結果:%p\n",p); return 0; } 輸出: p:000000000062FE17 p++之后結果:000000000062FE18 p--之后結果:000000000062FE17 p+5之后結果:000000000062FE1C
p 是一個 char 類型的指針,指向內存地址0062FE17處。則 p++ 將指向與 p 相鄰的下一個內存地址,由于 int 型數據占 4 個字節,因此 p++ 所指的內存地址為 1000000b。其余類推。不過要注意的是,這種運算并不會改變指針變量 p 自身的地址,只是改變了它所指向的地址
數組與指針
- 數組的內存空間:
數組
數組的數組名其實可以看作一個指針,因為數組名是指向數組的第一個元素,上面num數組指向的也就是第一個元素1,數組名本身是沒有占有內存的int array[10]={0,1,2,3,4,5,6,7,8,9},value; value=array[0]; //也可寫成:value=*array; value=array[3]; //也可寫成:value=*(array+3); value=array[4]; //也可寫成:value=*(array+4);
另外一種解釋是將數組名指向數組的第0個單元,那么(array+n)也就是一個指向數組里的第n個單元的指針
int main(){ int num[9] = {1,2,3,4,5,6,7,8,9}; int *p = num; *p++; int a = (*p)++; //2 int b = *(p++); //3 printf("%d\n%d\n",a, b); 輸出: 2 3
p指向的是數組的首地址,也就是數組的第一個元素,那么p++之后也就是對指針p前進了4(int類型)個操作數,而數組是分配了連續空間,所以相對地址是加減也就是數組元素的位置變換
指針數組
指針數組, 是一個數組,數組中的每一個元素都是指針
int *data[10]={NULL};//注意,一定要初始化 for(int i = 0; i < 10; ++ i){ data[i] = (int*)malloc(sizeof(int) * 10); } data[1][2] = 1;
對于上面的定義和初始化, data是指針數組的名字, 也就是指向指針數組首元素的指針. (指針的指針). data[i] 是data這一個數組的第i個元素, 也就是一個指向int的指針
指針可以當成數組來使用,data[i][j]
和*(data[i]+j)
是等價經過上述代碼創建的一個指針數組data的使用和int data[10][10]基本相同, 區別在于后者保證數組和數組之間的內存地址是連續的. data[0][9] 和 data[1][0] 是連續的, 而如果使用指針數組方式創建的data, 不能保證 data[0][9] 和 data[1][0] 在內存上連續
數組指針
數組指針,是一個指針,它指向一個數組
int (*)data[10] = NULL;//一個指向長度為10的int數組(int [10])的指針 //一般, 我們并不會使用到數組指針 //一般使用: int func(int data[][20]){ }
數組作為參數傳入函數的時候, 對于被調用的函數參數就是指針. 因此, 這里參數是一個"元素為
int[20]
"的數組(數組的數組), 因此, 在函數內部, data實際上就是一個"指向int[20]
"的指針(int(*)[20])
- 盡量不要對數組和指針使用
sizeof
- 當且僅當如
malloc(10*sizeof(int))
時使用sizeof
指針與函數
- 函數指針是指向函數的指針變量
- 通常我們說的指針變量是指向一個整型、字符型或數組等變量,而函數指針是指向函數
- 函數指針可以像一般函數一樣,用于調用函數、傳遞參數
函數指針聲明typedef int (*fun_ptr)(int,int); // 聲明一個指向同樣參數、返回值的函數指針類型
下面實例聲明了函數指針變量 p,指向函數 max:
#include <stdio.h> int max(int x, int y){ return x > y ? x : y; } int main(void){ /* p 是函數指針 */ int (* p)(int, int) = & max; // &可以省略 int a, b, c, d; printf("請輸入三個數字:"); scanf("%d %d %d", & a, & b, & c); /* 與直接調用函數等價,d = max(max(a, b), c) */ d = p(p(a, b), c); printf("最大的數字是: %d\n", d); return 0; } 輸出 請輸入三個數字:1 2 3 最大的數字是: 3
函數指針變量可以作為某個函數的參數來使用的,回調函數就是一個通過函數指針調用的函數。也就是說回調函數是由別人的函數執行時調用你實現的函數
- 下面事例中 populate_array 函數定義了三個參數,其中第三個參數是函數的指針,通過該函數來設置數組的值。
- 實例中我們定義了回調函數 getNextRandomValue,它返回一個隨機值,它作為一個函數指針傳遞給 populate_array 函數。
- populate_array 將調用 10 次回調函數,并將回調函數的返回值賦值給數組
#include <stdlib.h> #include <stdio.h> // 回調函數 void populate_array(int *array, size_t arraySize, int (*getNextValue)(void)){ for (size_t i=0; i<arraySize; i++) array[i] = getNextValue(); } // 獲取隨機值 int getNextRandomValue(void){ return rand(); } int main(void){ int myarray[10]; populate_array(myarray, 10, getNextRandomValue); for(int i = 0; i < 10; i++) { printf("%d ", myarray[i]); } printf("\n"); return 0; } 輸出: 41 18467 6334 26500 19169 15724 11478 29358 26962 24464
動態分配內存
- 動態分配內存的原因
1.存儲的數據 需要延長生命周期
2.一個指針變量需要存儲數據,變量本身只能存地址,不能存數據,需要分配內存空間來存儲數據
- C 語言為內存的分配和管理提供了幾個函數(導入庫為<stdlib.h>)
提供的函數- 內存分配
如果使用指針變量接收數據,必須先為這個指針變量分配一片指向的內存空間
char *name ;
用malloc(memory alloc)申請內存空間
name = (char *)malloc(10*sizeof(char));
使用realloc動態改變已經分配內存的大小
name = (char *)realloc(name, 20*sizeof(char));
使用完畢必須自己手動釋放內存
free(name);
結構體
結構體的優勢
可以存儲多種數據數據的變量
結構體定義
struct student{//定義一個學生結構體 int age; char sex; char name[10]; }:
- student是結構體名稱
- int age等是標準的變量定義
結構體定義變量
struct student LiMing;//struct student是一種結構體類型類似于int,float類型等 struct student *p = &LiMing;
結構體的訪問
LiMing.age = 18; LiMing.sex ='m'; LiMing.name ="李明"; //指針使用->訪問元素 p->age = 29; p->sex = 'f';
結構體內存大小計算
對齊方式
- 內存小的數據類型向內存大的數據類型對齊
int main(){ struct A{ char a; int b; }; struct B{ double a; int b; char c; }; struct Person{ char *name; double score; int age; }; struct Student{ char name[10]; int age; double score; }; printf("%d %d\n",sizeof(struct A),sizeof(struct B)); printf("%d %d\n",sizeof(struct Person),sizeof(struct Student)); return 0; } 輸出: 8 16 24 24
- 在結構體A當中,char類型向int類型靠齊
結構體A- 在結構體B當中,char ,int類型為double類型靠齊,由上自下的補齊,因為int類型占8位之后任有4位空著,這時候char會類型會自動補齊,占據剩下的4位
結構體B- 在結構體Person當中,字符型指針和double相同大小,int類型向double靠齊,自上而下,沒有空位讓int類型補齊
結構體Person- 在結構體Student當中,int類型和char類型向double靠齊,int類型分配8個字節,但前4位空著,char類型數組最后兩位補齊,剩余兩個空位
結構體Student
文件讀寫
- 打開文件
//fopen函數 FILE *fopen( const char * filename, const char * mode ); //fopen函數使用 FILE *fp = fopen("/Users/pengxiaodong/Desktop/test.txt", "r");
mode的值
- 寫入文件
//fputc函數 int fputc( int c, FILE *fp ); //函數使用 fputc('a', fp); //fputs函數 int fputs( const char *s, FILE *fp );//按照一定的格式寫入內容 //函數使用 fputs("jack", fp);
- 讀取文件
//fgetc函數 int fgetc( FILE * fp ); //函數使用 fgetc(fp);
- 關閉文件
//關閉文件 int fclose( FILE *fp ); //fclose函數使用 fclose(fp);
頭文件與實現文件實例之計算器
- 計算器的頭文件Calculator.h
#include <stdio.h> //頭文件里面聲明函數 //加法 int add(int a, int b); //減法 int minus(int a, int b); //乘法 int multiply(int a, int b); //除法 int devide(int a, int b);
- 計算器的實現函數Calculator.cpp
//實現文件 //1. 先導入需要實現的頭文件 //2. 實現這個頭文件里面的所有方法 #include "Calculator.h" //加法 i>nt add(int a, int b){ return a + b; } //減法 int minus(int a, int b){ return a - b; } //乘法 int multiply(int a, int b){ return a * b; } //除法 int devide(int a, int b){ if (b == 0){ return 0; }else{ return a / b; } }
- 計算器main函數入口
#include <stdio.h> //1.程序的入口函數 //main.cpp 為了讓閱讀者 //知道我這里面寫的是入口函數 //2. 將不同的功能模塊用不用的.h .cpp來封裝 //.h 頭文件 函數聲明 (不能實現) //.cpp .c 實現文件 函數的具體實現{} //3.導入頭文件進行使用 #include <stdio.h> //頭文件里面聲明函數 //加法 int add(int a, int b); //減法 int minus(int a, int b); //乘法 int multiply(int a, int b); //除法 int devide(int a, int b); /* 1.預編譯 */ int main(){ int result = add(1,1); printf("1 + 1 = %d\n", result); printf("1 + 1 = %d\n", add(1,1)); printf("2 - 1 = %d\n", minus(2,1)); printf("2 * 2 = %d\n", multiply(2,2)); printf("2 / 2 = %d\n", devide(2,2)); return 0; } void test(){ }
文件操作訓練之字符串查找
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> //從終端接收字符串 返回這個字符串的首地址 char *inputName(){ //1.定義一個指針變量 指向字符串的首地址 char *pName = NULL; //2.接收輸入 int i = 0; //3.提示操作 printf("請輸入人名:"); while (1) { //接收一個字符 char c = getchar(); //判斷這個字符是不是\n if (c == '\n') { //輸入結束 break; } //判斷是不是第一個字符 if(i == 0){ //使用malloc分配內存 pName = (char *)malloc(1*sizeof(char)); //判斷是否分配成功 if(pName == NULL){ exit(EXIT_FAILURE); } pName[0] = c; }else{ //使用realloc在之前的基礎上加一個 pName = realloc(pName, (i+1)*sizeof(char)); //判斷是否分配成功 if(pName == NULL){ exit(EXIT_FAILURE); } pName[i] = c; } i++; } //將當前的字符串首地址返回 return pName; } //是否繼續 bool isContinue(){ printf("是否繼續(y/n)?:"); while (1) { char c = getchar(); getchar(); if (c == 'y'){ return true; }else if(c == 'n'){ return false; }else{ printf("輸入格式不對,請重新輸入:"); } } } //初始化整個數組 char **initNames(int *pNum){ //1.定義指針變量指向每個名字的首地址的內存 char **pHead = NULL; //2.記錄元素個數 int i = 0; while (1) { //判斷是不是第一個 //第一個使用malloc分配內存 if (i == 0) { pHead = malloc(1*sizeof(char *)); if (pHead == NULL) { exit(EXIT_FAILURE); } //輸入人名 將地址放到pHead對應位置 pHead[0] = inputName(); }else{ //使用realloc重新再增加一個元素 pHead = realloc(pHead, (i+1)*sizeof(char *)); if (pHead == NULL) { exit(EXIT_FAILURE); } //輸入人名 將地址放到pHead對應位置 pHead[i] = inputName(); } i++; //是否繼續 bool result = isContinue(); if (result == false) { break; } } *pNum = i; return pHead; } void show(char **pHead, int num){ printf("輸入%d個名字:\n",num); int i; for ( i = 0; i < num; i++) { printf("%s\n",pHead[i]); } printf("\n"); } int main(int argc, const char * argv[]) { char **pHead = NULL; int count = 0; pHead = initNames(&count); show(pHead, count); return 0; }