C語言學習——指針
一、指針的概念
變量在內存中是一塊一塊的,每一塊都有一個對應的編號,這個編號就是指針。
在C程序中使用指針可以
- 使程序簡潔、緊湊、高效
- 有效的表示複雜的數據結構
- 動態分配內存
- 得到多於一個的函數返回值
1.1、變量與地址
在計算機內存中,每一個字節單元,都有一個編號,這個編號就是指針。
每一個編號都對應着一個字節,我們知道int型變量佔用4個字節。
來一個簡單的程序:1.1、bianliang.c
#include<stdio.h>
int main()
{
int a=10; //變量的類型 定義變量
int *aq; //指針的類型爲int 定義指針
aq=&a; //將指針指向變量的地址
printf("%d\n%d\n",*aq,a);
printf("%p\n",aq);
return 0;
}
運行結果:
1.2、指針變量和指針的類型
指針變量就是一個變量,它存儲的內容是一個指針,例如程序中的 int *aq,aq就是指針變量,它的存儲內容是0x7ffe02d0c7ec,0x7ffe02d0c7ec實際上就是變量a的地址。
我們在定義一個變量的時候要確定他的類型,例如這裏我們定義變量a,int a,在定義指針變量時,也要定義指針的類型,int 變量的指針需要用 int 類型的指針存儲,float 變量的指針需要用 float 類型的指針存儲。
例如,當用aq儲存a的地址的時候,必須保證aq和a的類型是一致的,都是int類型。
二、變量的指針與指針變量
變量的指針就是變量的存儲地址,指針變量就是存儲指針的變量
2.1、指針變量的使用
-
取地址運算符&:單目運算符&是用來取操作對象的地址。例:&a爲取變量 a 的地址。對於常量表達式、寄存器變量不能取地址(因爲它們存儲在存儲器中,沒有地址)。
-
指針運算符*(間接尋址符):與&爲逆運算,作用是通過操作對象的地址,獲取存儲的內容。例:x = &i,x 爲 i 的地址,*x 則爲通過 i 的地址,獲取 i 的內容。
在上面程序中,
printf("%d\n%d\n",*aq,a);
aq已經是變量a的地址,aq就是通過a的地址訪問a的內容,所以 *aq就是10。
2.2、指針變量的初始化
指針變量與其它變量一樣,在定義時可以賦值,即初始化。也可以賦值“NULL”或“0”,如果賦值“0”,此時的“0”含義並不是數字“0”,而是 NULL 的字符碼值。
2.3、指針的運算
(1)賦值運算
指針變量可以互相賦值,也可以賦值某個變量的地址,或者賦值一個具體的地址
#include<stdio.h>
int main()
{
int *px,*py,a=100;
px=&a;//賦值某個變量的地址
py=px;//指針變量相互賦值
px=100;//賦值一個具體的地址
printf("%p\n%d\n",py,px);
}
(2)指針與整數的加減運算
-
指針變量的自增自減運算。指針加 1 或減 1 運算,表示指針向前或向後移動一個單元(不同類型的指針,單元長度不同)。這個在數組中非常常用。
-
指針變量加上或減去一個整形數。和第一條類似,具體加幾就是向前移動幾個單元,減幾就是向後移動幾個單元。
程序:
#include<stdio.h> int main() { int a=10; //變量的類型 定義變量 int *aq; //指針的類型爲int 定義指針 aq=&a; //將指針指向變量的地址 printf("%d\n%d\n",*aq,a); printf("%p\n",aq); printf("%p\n%p\n",aq+1,aq+2); return 0; }
輸出結果:
從運行結果可以看出,aq的值是0x7ffec8o74eec,aq+1是0x7ffec8074ef0,aq+2是0x7ffec8074ef4,我們通過計算可以看出aq+1在aq的基礎上相加了4個字節,aq+2在aq的基礎上相加了8個字節,因爲int型佔4個字節。加1就是一個int型佔4個字節,加2就是兩個兩個int型佔8個字節。
(3)關係運算
- px > py 表示 px 指向的存儲地址是否大於 py 指向的地址
- px == py 表示 px 和 py 是否指向同一個存儲單元
- px == 0 和 px != 0 表示 px 是否爲空指針
三、指針與數組
數組可以通過下標來訪問,還可以通過指針來訪問,在數組中,數組名爲數組的首地址,通過指針對整數的加減來訪問數組中的元素。
3.1、 指向數組的指針
來一個簡單的程序:
#include<stdio.h>
int main()
{
int sum[5]={1,2,3,4,5};
int *px;
px=sum;
printf("%d\n%d\n",*(px+2),*(px+4));
}
定義了一個名爲sum的數組,定義它分配5個連續的int內存空間,而一個數組的首地址即爲數組名sum,或者第一個元素的首地址也是數組的首地址。那麼有兩種方式讓指針變量 p 指向數組 sum:
所以輸出的*(px+2)=3, *(px+4)=5
1、*p = 1,此操作爲賦值操作,即將指針指向的存儲空間賦值爲 1。此時 p 指向數組 nums 的第一個元素,則此操作將 nums 第一個元素賦值爲 0,即 nums[0] = 1。
2、p + 1,此操作爲指針加整數操作,即向前移動一個單元。此時 p + 1 指向 nums[0]的下一個元素,即 nums[1]。通過p + 整數可以移動到想要操作的元素(此整數可以爲負數)。
3、如上面,p(p + 0)指向 nums[0]、p + 1 指向 nums[1]、、、類推可得,p+i 指向 nums[i],由此可以準確操作指定位置的元素。
4、在 p + 整數的操作要考慮邊界的問題,如一個數組長度爲 2,p+3 的意義對於數組操作來說沒有意義。
注:數組名不等價於指針變量,指針變量可以進行 p++和&操作,而這些操作對於數組名是非法的。數組名在編譯時是確定的,在程序運行期間算一個常量。
3.2、字符指針和指針數組
在 C 語言中本身沒有提供字符串數據類型,但是可以通過字符數組和字符指針的方式存儲字符串。
指針方式操作字符串和數組操作字符串類似。
程序:將數組fl中的內容複製到令一數組word中
#include<stdio.h>
int main()
{
char fl[]="I LOVE YOU",word[100];
char *ch = word;
int i;
for(i=0;fl[i]!='\0';i++)
{
*(ch+i)=fl[i];
}
*(ch+i)='\0';
printf("ch=%s\n,word=%s\n",ch,word);
}
注:指針變量必須初始化一個有效值才能使用
四、動態內存分配
定義數組時數組大小在程序運行時才知道 , 靜態開闢就無法實現。
在C中動態開闢空間需要用到三個函數 :
malloc(), calloc(), realloc() ,這三個函數都是向堆中申請的內存空間.
malloc()函數用法:
void * malloc(size_t size)
p=(int*)malloc(sizeof(int) * n);
calloc()函數用法:
void * calloc(size_t num,size_t size)
p=(int*)calloc(n,sizeof(int));
4.1、malloc函數
1).malloc()函數會向堆中申請一片連續的可用內存空間
2).若申請成功 ,返回指向這片內存空間的指針 ,若失敗 ,則會返回NULL, 所以我們在用malloc()函數開闢動態內存之後, 一定要判斷函數返回值是否爲NULL.
3).返回值的類型爲void型, malloc()函數並不知道連續開闢的size個字節是存儲什麼類型數據的 ,所以需要我們自行決定 ,方法是在malloc()前加強制轉 ,轉化成我們所需類型 ,如: (int)malloc(sizeof(int)*n).
4).如果size爲0, 此行爲是未定義的, 會發生未知錯誤, 取決於編譯器
來一個程序:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int n=0,i;
int *p = NULL;//定義指針
scanf("%d",&n);
p=(int*)malloc(sizeof(int) * n);
if(p != NULL) //判斷內存空間是否申請成功
{
for(i=0;i<n;i++)
{
scanf("%d",&p[i]);
}
for(i=0;i<n;i++)
{
printf("%3d",p[i]+1);
}
free(p); //釋放內存
p = NULL;
}
}
4.2、realloc函數
realloc()函數讓動態內存管理更加靈活 .在程序運行過程中動態分配內存大小, 如果分配的太大 ,則浪費空間, 如果太小, 可能還是會出現不夠用的情況 .爲了合理的利用內存,我們使用realloc() 函數對內存進行靈活的調整。
程序:將數據文件放入數組中
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *in,*out;
char ch,infile[10];
int size=0,i;//size-1代表數據的個數
char a[100];
double *data = (double*)malloc(sizeof(double));
printf("輸入文件名稱");
scanf("%s",infile); //輸入文件名稱
if((in=fopen(infile,"r"))==NULL)//判斷是否打開文件
{
printf("打開文件失敗\n");
exit(0);
}
while(fgets(a,100, in) != NULL)
{
double num = atof(a); //將字符串轉換成浮點數
data[size] = num;//把數據存入數組
size++;
data = (double*)realloc(data, (sizeof(double))*(size+1));//每讀一個字節就重新分配一次內存
}
fclose(in);
for(i=0; i<size-1; i++)
printf("%lf\n",data[i]); //打印行號和該行的數據
free(data); //釋放內存
//while(!feof(in))
//{
//ch=fgetc(in);
//putchar(ch);
//}
//fclose(in);
//return 0;
}
五、函數與指針
5.1、指針做函數參數
程序:交換兩個變量的內容
#include<stdio.h>
int swap(int *a,int *b)
{
int t;
t=*a;//指針a的內容給t
*a=*b;//指針b的內容放到指針b中
*b=t;//t中的數放到指針b中
}
int main()
{
int a=50,b=20;
swap(&a,&b);
printf("%d %d",a,b);
}
5.2、函數返回數組
C 語言不允許返回一個完整的數組作爲函數的參數。但是,**可以通過指定不帶索引的數組名來返回一個指向數組的指針。**如果您想要從函數返回一個一維數組,必須聲明一個返回指針的函數
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int *gt(int a[])
{
int i;
srand( (unsigned)time( NULL ) );//產生隨機數
for(i=0;i<10;i++)
{
a[i]=rand();
}
return a;
}
int main()
{
int *b,j;
int c[10]={1,2,3,4,5,6,7,8,9,0};
b=gt(c);
for(j=0;j<10;j++)
{
printf("%d\n",b[j]);
}
}
明一個返回指針的函數
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int *gt(int a[])
{
int i;
srand( (unsigned)time( NULL ) );//產生隨機數
for(i=0;i<10;i++)
{
a[i]=rand();
}
return a;
}
int main()
{
int *b,j;
int c[10]={1,2,3,4,5,6,7,8,9,0};
b=gt(c);
for(j=0;j<10;j++)
{
printf("%d\n",b[j]);
}
}