C語言續

  • 指針

    指針的定義
    指針的類型
    指針的指向內容
    指針的運算
    數組與指針
    指針與函數
  • 動態分配內存

  • 結構體

  • 文件讀寫

  • 頭文件與實現文件實例之計算器

  • 文件操作訓練之字符串查找

指針

指針的定義

  • 指針是一個變量
  • 指針只能存地址
  • 指針占據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;
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容