1. 程式人生 > >C語言基礎學習筆記:day5 指標

C語言基礎學習筆記:day5 指標

注:本筆記為直接上傳,因各個markdown筆記語法的差異性,在顯示上略有區別。
如需原版請聯絡:[email protected]。(郵件主題為:學習筆記,正文需要的筆記名,可以直接複製該筆記的網址)。同時歡迎各位一起學習交流。

day5指標

文章目錄

一、指標的含義和定義

指標存放的內容是一個地址,該地址指向一個記憶體空間;

1.指標的定義

#include<stdio.h>
int
main() { int a = 0; int b = 10; char buf[10]; printf("%p,%p,%p\n",&a,&b,buf); //&即是取變數地址,陣列名錶地址 int *p = &a; //得到變數a的地址,將這個地址賦值給指標變數p //地址是一個整數,但是特殊性在於這個整數不能直接通過整數來操作 //上面的賦值等效於以下語句: //int *p; //定義一個變數,名字叫做p,它指向於一個int的地址 //p = &a; //指標變數的值一般不能賦值一個整數,而是通過取變數地址的方式進行賦值。 return
0; }

通過指標獲得指標所指記憶體的值,可以通過指標直接更改值的大小


2.指標的使用

#include<stdio.h>

int main()
{
  int a = 3;
  int *p;
  p = &a;    //定義指標之後應該初始化或者賦值,否則會成野指標

  int b = *p;  //*p 表示指標變數指向記憶體的內容
  printf("b = %d\n",b);

  *p = 10;//通過指標間接的修改變數的值,這時候a的值為10; 
  printf("a = %d\n",a);
  return 0;

}

程式的執行結果為:b = 3;

a = 10;


3.空指標與野指標的區別

  • 空指標: 指向NULL的指標,當一個指標不指向任何一個有效的記憶體地址的時候,應該把指標設定成NULL
  • 野指標 : 沒有具體指向任何變數地址的指標(程式中應該避免野指標的存在)
    • 空指標程式例子
#include<stdio.h>

int main()
{
  int i = 0;
  void *p;  //定義無型別指標,意思是這只是一個指標變數,而不指向任何具體的資料型別;
  p = NULL; //將指標賦值NULL,值為NULL的指標,我們俗稱為空指標,NULL其實就是0

  return 0;
}

二、指標與陣列的關係

1.指標的相容性

指標之間賦值要根據雙方的資料型別來判斷,原則上一定是相同型別的指標指向相同型別的變數地址,不能用一種型別的指標指向另一種型別的變數地址。

#include<stdio.h>

int main()
{
  float a = 3.14;  
  int i = a;  //這是可以的:C語言會自動進行資料型別轉換,將浮點數後面的小數部分捨棄;

  //int *p = &a;  //這個一般不可以:這是很嚴重的錯誤,指標型別不相容

  printf("i = %d\n",i);
 // printf("*p = %d\n",*p);

  return 0;
}

程式執行結果:i = 3


2.特殊的兩類指標

  • 指向常量的指標

定義: const 資料型別 指標

特點:不能通過修改指標值(*p)來改變所指向記憶體空間存放的值

#include<stdio.h>

int main()
{
  int a = 10;
  int b = 20;

  const int *p = &a;  //p這個指標只能指向一個常量,可以直接改變a的賦值來改變a的值,但是不能通過改變*p的值來改變a的值;

  //*p = 30;  //這樣不能修改,只會報錯;

  a = 30;   //這樣是可以的

  p = &b;   //這樣也是可以的

  printf("%d\n",*p);
  return 0;

}

程式執行結果:*p = 20

  • 常量指標

定義:資料型別 *const 指標名

特點:不能修改指標指向的記憶體位置

#include<stdio.h>
int main()
{
  int a = 10;
  int b = 20;
  int *const p = &a;  //定義一個常量指標,可以通過修改常量指標修改或者讀取一個變數的值
  *p = 30;
  // p = &b;   //這是錯誤的,常量指標一旦定義了,那麼就不能修改其指向的變數
  printf("a = %d\n",*p);

  return 0;

}

程式執行結果:a = 30


3.指標的加減對應指向記憶體的位置的改變

#include<stdio.h>

int main()
{
  int array[100] = {1,2,3,4,5,6,7,8,9,0};
  int *p = array;
  p += 5;   // ==這裡p在記憶體中相當於移動了5*4(8)個位元組==
  p -= 3;

  p[3] = 100;   //==這時候實際上相當於array[5] 的值為100==

  int i = 0;
  for(i = 0; i < 10; i++)
  {
  printf("array[%d] = %d\n",i,array[i]);
  
  }

  return 0;
}


程式執行的結果:
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 4
array[4] = 5
array[5] = 100
array[6] = 7
array[7] = 8
array[8] = 9
array[9] = 0


4.通過指標相減可以得到陣列之間兩個元素之間的距離差

#include<stdio.h>

int main()
{
  int array[20] = {1,23,4,78,9,2};
  int *p1 = &array[3];
  int *p2 = &array[7];
  printf("juli = %d\n",p2 -p1);
  return 0;
}


程式輸出結果:juli = 4


5.指標與陣列在地址上的關係
陣列名等效於陣列名取地址,等效於陣列首元素的地址

#include<stdio.h>

int main()
{

char buf[10] = {0,1,2,3,4};
char *p = buf;
char *p1 = &buf;
char *p2 = &buf[0];
char *p3 = &buf[1];
char *p4 = &buf[2];

p3 ++;   //改變了p3所指的地址,變成了p4指向的buf[2]
printf("%d,%d,%d,%d,%d\n",p,p1,p2,p3,p4);
return 0;

}

除錯的結果為:
343472120,343472120,343472120,343472122,343472122

可見前面三個是等效的;後面可以通過指標的加減實現指向位置的改變,從而改變輸出值的大小

示例1: 用指標來求一個字串的長度,不可以使用陣列下標的方式

#include<stdio.h>

int main()
{
  char s1[100] = "hello";
  char s2[100] = "world";
  char *p1 = s1;
  int len = 0;
  while(*p1)  //*p1的內容為0的時候,表示字串就結束了
  {
    p1++;
    len++;
  }
  printf("len = %d\n",len);
  return 0;
}

示例2: 用指標來將s1和s2合併為一個字串,結果放入s1中,不可以使用陣列下標的方式;

#include<stdio.h>

int main()
{
  char s1[100] = "hello";
  char s2[100] = "world";



  //獲取字串s1的長度
  
  char *p1 = s1;
  int len = 0;
  while(*p1)  //*p1的內容為0的時候,表示字串就結束了
  {
    p1++;
    len++;
  }
  printf("len = %d\n",len);   



  //將s2元素連線到s1後面,這時候p1已經指向了s1的最後名的\0
  
  char *p2 = s2;
  while(*p2)
  {
    *p1 = *p2; //從s1陣列最後開始,從s2的首元素開始,因為根據上面的語句,在求出s1長度的同時,p1也指向了s1的末尾位置;
    p2++;
    p1++;
  }

//以上的while語句等效為:

  /*
    while(*p2)
    {
      *p1++ = *p2++;
      //先++在取值
    }
  */


  printf("s1 = %s\n",s1);
  return 0;
}

6.使用指標來訪問陣列成員

#include<stdio.h>

int main()
{
  int array[100] = { 0 };
  int *p = array;
  p[0] = 100; //這是允許的,從語法上可以和陣列方式一樣,就是通過下標的方式進行訪問

 printf("sizeof(array) = %d\n",sizeof(array));
 printf("sizeof(p) = %d",sizeof(p));
 printf("sizeof(char *) = %d",sizeof(char *));  //char * 表示一個指向char的指標
  return 0;
} 


執行結果為:
sizeof(array) = 400
sizeof(p) = 8(在32位的作業系統中值為4),因為本質上是一個int型整數
sizeof(char *) = 8


7.遊戲外掛:針對於單機遊戲(以植物大戰殭屍為例)

  • 首先開啟遊戲,開始遊戲之後,比如現在陽光的數量為175,這時候開啟cheat Engine軟體,在值中輸入175,然後會出現所有變數值為175的變數及其地址;

  • 這時候繼續遊戲,消耗陽光之後,比如值變為75,繼續在軟體中輸入變數值為75,然後再次查詢就會得到唯一的變數地址,即為存放陽光變數的真正地址

  • 建立工程開始寫程式,將變數值進行修改

_declpec(dllexport)  //加上這個關鍵字,代表go函式是可以在其他程式中呼叫dll函式

void go()
{
  int *p = 0x1CF88808;  //得到陽光變數的地址,改地址可以通過以上的軟體得到

  while1{
    if(*p < 100)
    {
      *p = 300;   //應當避免一次性賦值很大,防止觸發系統的反作弊機制
    }
  }
}
  • 然後將專案屬性修改為動態庫.dll

  • 然後使用軟體dllinject 將這個程式新增進遊戲之後就可以了;


三、指標陣列以及多級指標

1.兩種相似定義的區別


#include<stdio.h>

int main()
{
  int *s[10] = {0};  //本質上定義了一個數組,指向int*型變數
  int (*p)[10] = 0;  //本質上僅僅定義了一個指標變數,指向int  [10]這麼大的一種資料型別
  
  printf("sizeof(s) = %d\n",sizeof(s));
  printf("sizeof(p) = %d\n",sizeof(p));
}

程式執行結果:sizeof(s) = 80
sizeof(p) = 8


2.指標陣列
定義和使用

#include<stdio.h>

int main()
{
  int *a[10];//定義了一個指標陣列,一共10個成員,其中每個成員都是int *型別;
  printf("%d,%d\n",sizeof(a),sizeof(a[0]));

  short *b[10]; //定義了一個含有十個成員的指標陣列,其中每個成員都是short *型別
  printf("%d,%d\n",sizeof(b),sizeof(b[0]));



  //具體使用方法

  int a1;
  int a2;
  a[0] = &a1;
  a[1] = &a2;

  return 0;
}

3.指向指標的指標

因為指標本身也為變數,也存在地址,相當於用指標指向指標的地址

#include<stdio.h>
int main()
{
  int a = 10;
  int *p = &a;    //定義一個指標p存放變數a的地址
  int **pp = &p;  //定義一個二級指標,指向了一個一級指標的地址
  //這裡等效於以下程式碼
  int **pp;
  pp = &p;

  //使用
  **pp = 100;  //通過二級指標修改記憶體的地址
  
  //*pp = 10;  這相當於將p指向了編號為10的這塊記憶體,pp還是正常的指標,但是p被修改成野指標了;

  printf("a = %d\n",a);

return 0;

}

程式執行結果:a = 100


4.指向多維陣列的指標
——以二維陣列為例

  • 定義:
#include<stdio.h>

int  main()

{

  int  buf[2][3]  =  {{1,2,3},{4,5,6}};
  int  (*p)[3];  //定義了一個指標,只有一個成員變數,指向int  [3]  這種資料型別,是一種指向二維陣列的指標;

  p  =  buf;  //表示指向二維陣列的第一行
  //如果想要表示陣列的第二行,可以使用P++

//同理  p[0]  =  buf的首地址

  printf("%d\n",sizeof(p));  //sizeof(p)在32為的作業系統中是4,在64位作業系統中值為8
  printf("%d,%d\n",p,p  +  1);  //這裡的p+1實際上平移了1  *  sizeof(int  [3])大小,即平移了24個位元組;

  int  i  =  0;
  for(i  =  0;  i  <  2;  i++)
    {
      printf("%d\n",*p[i]);//*p[i] 表示i行首元素的值
   }

  return  0;

}

程式執行結果:1 ,4

  • 使用:
    功能:將二維陣列中所有的元素打印出來
#include<stdio.h>

int main()
{
  int buf[2][3] = {{1,2,3},{4,5,6}};
  
  int (*p)[3];   
  p = buf; 

  int i;
  int j;
  for(i = 0; i < 2; i++)
  {
    for(j = 0; j < 3; j++)
    {
      printf("%d\n",p[i][j]);
    
    }
  }


  return 0;
}

程式執行結果:
1
2
3
4
5
6

上面程式中printf(“%d\n”,p[i][j]); 等效於 printf(“%d\n”,*(*(p+i)+j));
註釋:(p+i)表示每一行的首地址,等效於p[i]


5.各種地址的表示方法

表示方法 含義
int buf[3][5] 二維陣列名稱,buf代表陣列首地址
int (*a)[5] 定義一個指向int[5]型別的指標變數a
a[0] , (a+0) ,a 表示第0行第0列元素的地址
a+1 表示第一行首地址
a[1] , *(a +1) 表示第一行第0列元素的地址
a[1] + 2 ,*(a+1)+2 , &a[1][2] 表示第一行第二列元素的地址
(a[1] + 2) , (*(a+1)+2) , a[1][2] 表示第一行第二列元素的值

6.練習
目標:在不使用陣列下標的情況下,只能通過指向二維陣列的指標求出陣列中每行和每列的平均值

該程式不完整

#include<stdio.h>

int  main()

{

  int  buf[3][5]  =  {{2,4,3,5,3},{7,2,6,8,1},{7,3,5,0,2}};
  int  (*p)[5];
  p  =  buf;
  
  int  i;
  int  j;
  float  _aver_hang  =  0;
  float  _aver_lie  =  0;
  float  _sum_hang  =  0;
  float  _sum_lie  =  0;
  for(i  =  0;  i  <  3;  i++)
  {
    for(j  =  0;  j  <  5;  j++)
      {

        _sum_hang  +=  p[i][j];
        _aver_hang  =  _sum_hang  /  3.0;
      }

  printf("%d\n",_aver_hang);

  }

  return  0;

}

7.const保護函式引數以及返回值為指標的函式

通過函式的指標引數可以間接的實現形參修改實參的值;

  • 無法傳值的程式
#include<stdio.h>

void test(int p)
{
  p++;  
}

int main()
{
  int i = 0;
  test(i); 
  printf("i = %d\n",i);
  return 0;

}

程式執行結果:i = 0
因為形參的值不能反過來傳給實參

  • 可以傳值的程式
    想改變實參的值需要使用指標;
#include<stdio.h>

void test(int *p)
{
  (*p)++;  //*p為i的值,則對應的是i+1;

}

int main()
{
  int i = 0;
  test(&i);  //傳給形參的是實參i的地址
  printf("i = %d\n",i);
  return 0;

}

程式結果為:i=1;


8.交換兩個資料的值:

#include<stdio.h>

void swap(int *c,int *d)
{
  int tmp = *c; //*c即是a的值
  *c = *d;
  *d = tmp;
}

int main()
{
  int a = 10;
  int b = 20;
  swap(&a,&b);  //傳遞的是地址
  printf(