如何訪問、如何引用、如何存儲????
問題:
1、如何訪問變量?
通過變量名稱來訪問變量
2、如何訪問指針變量?
通過指針變量名
問題一:如何通過指針訪問值?
#include <stdio.h>
int main()
{
int number = 15;
int *pointer = &number;
int result = 0;
printf("%d\n",*pointer);
result = *pointer + 5;
printf("%d\n",result);
return 0;
}
7.1 指針初探
指針是C語言中最強的工具
int number = 5;
這條語句會分配一塊內(nèi)存來存儲一個整數(shù),使用變量number的名稱可以訪問這個整數(shù),
值5存儲在這個區(qū)域中。
計算機用一個地址引用這個區(qū)域。
存儲地址的變量稱為指針(pointers),存儲在指針中的地址通常是另一個變量,
指針的工作原理
int number = 99;
int *pnumber = &number;
指針pnumber含有另一個變量number的地址,變量number是一個值為99的整數(shù)變量。
指針只是一個存儲內(nèi)存位置的地址。
存儲在pnumber中的地址是number第一個字節(jié)的地址。
編譯器需要知道指針 指向的變量類型。
char 類型值的指針指向占有一個字節(jié)的值
而long類型值的指針指向占有4個字節(jié)的值
- 給定類型的指針寫成type*,其中type 是任意給定的類型。
類型名void表示沒有指定類型,所以void類型的指針可以包含任意類型的數(shù)據(jù)項地址。類型void常常用作參數(shù)類型,或以獨立于類型的方式處理數(shù)據(jù)的函數(shù)的返回值類型。任意類型的指針都可以傳送為void*類型的值,在使用它是,再將其轉(zhuǎn)換為合適的類型。
7.1.1 聲明指針
(1)聲明指針變量時,指針變量前一定要有“ * ”符號
(2)定義指針時,要加上類型標示符。
類型 *指針變量名
聲明一個指向int類型的變量指針
int *pnumber; &&&& int* pnumber
以上兩條語句完全相同,但使用時最好始終使用其中一種,沒有初始化的指針是非常危險的,所以應總是在聲明指針時對他進行初始化。
初始化pnumber,使他不指向任何對象。
int *pnumber = NULL;
尋址運算符&:獲取變量的地址。
取消引用運算符*:獲取指針指向的地址中的內(nèi)容,并對該地址中的內(nèi)容進行賦值操作。
p = &a;
NULL是在標準庫中定義的一個常量,對于指針它表示0。NULL是一個不指向任何內(nèi)存位置的值。
NULL在頭文件< stddef.h> 、<stdlib.h>、<stdio.h>、<string.h>、< time.h>、<wchar.h>和<locale.h>中定義
只要編譯器不能識別NULL,就應在源文件中包含<stddef.h>頭文件
如果用已聲明的變量地址初始化pointer變量,可以使用尋址運算符。如下所例:
int number = 99;
int *pnumber = &number;
pnumber的初值是number變量的地址。注意,number的聲明必須在pnumber的生命之前。
用相同的語句聲明一般的變量和指針
double value, *pVal, fnum;
這條語句聲明了兩個變量,以及一個指向double的變量pVal
int *p , q;
聲明一個指針和一個變量
7.1.2 通過指針訪問值
使用間接運算符*可以訪問指針所指的變量值
取消引用運算符(dereferencing operator )取消對指針的引用。
int number = 15;
int *pointer = &number;
int result = 0;
7.1.3 使用指針
星號*表示訪問指針變量所指向的內(nèi)容
在算術語句中使用取消引用的指針
*pnumber += 25;
將變量pnumber所指向的地址中的值增加25
int value = 999;
pnumber = &value;
指針可以包含同一類型的任意變量的地址,所以使用一個指針變量可以改變其他很多變量的值,只要它們的類型和指針相同。
運算符++和一元運算符* (&)的優(yōu)先級相同,且都是從右往左計算的。
pvalue 和 value 是相同的所以用任何一個都可以
7.14 指向常量的指針
常量指針與指針常量的指針的區(qū)別是
-
常量指針是指在指針中存儲的地址不發(fā)生改變。
指向常量的指針是指針指向的值不發(fā)生改變。
使用const關鍵字聲明指針時,該指針指向的值不能改變。
long value = 9999L;
const long *pvalue = &value;
指針本身不是常量,所以可以改變指針指向的值
long number = 8888L;
pvalue = &number;
把變量number的地址賦值給指針變量pvalue,指針變量是一個含有地址的變量,所以可以使用指針變量名作為參數(shù)即
(&value 等價于pvalue)
指針是另外一個變量的地址
- 指針可以改變指針中儲存的地址,但不能改變指針的指向的值
7.1.5 常量指針
什么是常量指針(常量指針的概念是什么)?
- 常量指針是指在指針中存儲的地址不發(fā)生改變。
下面的語句可以是指針總是指向相同的對象:
int count = 43;
int *const pcount = &count;
可以創(chuàng)建一個常量指針,它指向一個常量值:
int item = 25;
const int *const pitem = &item;
7.1.6 指針的命名
最好將P作為指針名的第一個字母
7.2 數(shù)組和指針
數(shù)組是相同類型的(對象)元素集合,在內(nèi)存中占據(jù)著一塊連續(xù)的存儲空間,每一個元素都有一個確定的地址值。因此可以利用指針對數(shù)組中的每一個元素進行操作。
指針是一個變量,它的值是給定類型的另一個變量或常量的地址。
數(shù)組名可表示數(shù)組的首地址,即數(shù)組第一個元素所在的位置
數(shù)組名等于數(shù)組第一個字節(jié)的地址,&multiple[0] 等于數(shù)組第一個元素的第一個字節(jié)
有三種方式可以實現(xiàn)對數(shù)組元素的訪問
(1)通過下標訪問 x[i](2)通過地址訪問 (3)通過指針訪問
用scanf_s輸入一個字符,可以用一下語句
char single = 0;
scanf_s("%c", &single, sizeof(single));
如果讀入字符串,可以編寫如下代碼
char multiple[10];
scanf_s("%s", multiple, sizeof(multiple));
數(shù)組和指針有一個重要的區(qū)別:可以改變指針包含的地址,但不能改變數(shù)組名稱引用的地址。
編譯器知道,給地址值加1時,就表示要訪問該類型的的下一個變量,這就是為什么聲明一個指針時,必須要指定的該指針指向的變量類型。
數(shù)組名稱是一個固定的地址,而不是一個指針,可以在表達式中使用數(shù)組名及其引用的地址,但不能修改它。
7.3 多維數(shù)組
board 是char型二維數(shù)組的地址,board[0]是char型1??維子數(shù)組的地址,它是board的一個子數(shù)組,&board[0][0]是char型數(shù)組元素的地址。
如果使用board獲取第一個元素的值,就需要使用兩個間接運算符,
如果只使用一個*,只會得到子數(shù)組的第一個元素。
board引用子數(shù)組中第一個元素的地址
board[0]board[0]board[0]引用對應子數(shù)組中第一個元素的地址。
用兩個索引值訪問存儲在數(shù)組元素中的值。
7.3.1 多維數(shù)組和指針
7.3.2 訪問數(shù)組元素
問題:如何實現(xiàn)(board)
訪問數(shù)組元素的指針表達式
board | 0 | 1 | 2 |
---|---|---|---|
0 | board[0][0] | board[0][1] | board[0][2] |
0 | * board[0] | *(board[0] + 1) | * ( board[0] +2) |
0 | **board | (board+1) | (board + 2 |
1 | board[1][0] | board[1][1] | board[1][2] |
1 | * board[1 ] | *(board[1 ] + 1) | * ( board[1] +2) |
1 | (board+3) | (board +4) | (board+5) |
2 | board[2][0] | board[2][1] | board[2][2] |
2 | *board[2] | (board[2]+1) | (board[2]+2) |
board | 0 | 1 | 2 |
---|---|---|---|
0 | board[0][0] | board[0][1] | board[0][2] |
1 | board[1][0] | board[1][1] | board[1][2] |
2 | board[2][0] | board[2][1] | board[2][2] |
board | 0 | 1 | 2 |
---|---|---|---|
0 | * board[0] | *(board[0] + 1) | * ( board[0] +2) |
1 | * board[1] | *(board[1] + 1) | * ( board[1] +2) |
2 | *board[2] | (board[2]+1) | (board[2]+2) |
board | 0 | 1 | 2 |
---|---|---|---|
0 | **board | * ( * board+1) | (board + 2 |
1 | (board+3) | (board +4) | (board+5) |
7.4 內(nèi)存的使用
指針是一個強大的編程工具
c語言還有一個功能:動態(tài)內(nèi)存分配
在程序的執(zhí)行期間分配內(nèi)存時,內(nèi)存區(qū)域中的這個空間稱為堆(heap),還有另一個內(nèi)存區(qū)域,稱為堆棧(stack),其中的空間分配給函數(shù)的參數(shù)和本地變量。在執(zhí)行完該函數(shù)后,存儲參數(shù)和本地變量的內(nèi)存空間就會釋放。
堆中的內(nèi)存是由程序猿控制的
7.4.1 動態(tài)內(nèi)存分配:malloc()函數(shù)
動態(tài)內(nèi)存分配(dynamic memory allocation)
在運行時分配內(nèi)存的最簡單的標準庫函數(shù)是malloc()函數(shù),
使用malloc函數(shù)需要指定要分配分內(nèi)存字節(jié)數(shù)作為參數(shù)。malloc函數(shù)返回所分配內(nèi)存的第一個字節(jié)的地址。
int *pNumber = (int*)malloc(100);
int *pNumber = (int*)malloc(25*sizeof(int));
類型轉(zhuǎn)換(int*)將函數(shù)返回的地址轉(zhuǎn)換成int類型的指針
int *pNumber = (int*)malloc(25*sizeof(int));
if(pNumber)
{
//code to deal with memory allocation failure
}
7.4.2釋放動態(tài)分配的內(nèi)存
動態(tài)分配了一些內(nèi)存時,沒有保留對它們的引用,就會出現(xiàn)內(nèi)存泄露,此時無法釋放內(nèi)存。
必須能訪問引用內(nèi)存塊的地址
free(pNumber)
pNumber = NULL;
free()函數(shù)的形參是void*類型,所有的指針類型都可以自動的轉(zhuǎn)換為這個類型,所以可以把任意類型的指針作為參數(shù)傳送給free()函數(shù)
在指針指向的內(nèi)存釋放后,應總是把指針設置為NULL;
在釋放內(nèi)存后,應總是把指向堆內(nèi)存的指針設置為NULL,這樣就不會使用不再可用的內(nèi)存了使用不再可用的內(nèi)存總是很危險的。
malloc函數(shù)的參數(shù)是size_t類型
如果size_t對應4字節(jié)的無符號的整數(shù),則一次至多可以分配4294967295個字節(jié)
7.4.3 用calloc函數(shù)分配內(nèi)存
它把內(nèi)存分配為給定大小的數(shù)組,
它初始化了所分配的內(nèi)存,所有的位都是0
數(shù)組的元素個數(shù)和數(shù)組元素占用的字節(jié)數(shù),這兩個參數(shù)的類型都是size_t,
calloc函數(shù)不知道數(shù)組的類型,所以分配的內(nèi)存區(qū)域地址返回為void*類型。
int *pNumber =(int*)calloc(75,sizeof(int));
如果不能分配所請求的內(nèi)存,返回值就是NULL;
int *pNumber = calloc(75,sizeof(int));
pPrimes = calloc((size_t)total,sizeof(unsigned long long));
if(pPrimes == NULL)
{
printf(" Not enough memory.It's the end I am afraid.\n");
return 1;
}
7.4.4 擴展動態(tài)分配的內(nèi)存
realloc函數(shù)需要兩個參數(shù):包含地址的指針。要分配的新內(nèi)存的字節(jié)數(shù)
如果realloc的第一個參數(shù)是NULL,就分配第二個參數(shù)制定的新內(nèi)存。如果第一個參數(shù)不是NULL,但不指向以前分配的內(nèi)存,或者指向已釋放的內(nèi)存,結果就不確定了。
7.5使用指針處理字符串
存儲字符和引用字符串
- 1、char類型的數(shù)組元素存儲字符串
- 2、char類型的指針變量引用字符串
- 3、聲明指針變量時只是創(chuàng)建了字符串變量并沒有指定一個存儲字符串的地方
要存儲字符串,需要分配一些內(nèi)存,指針變量中存儲其地址。
因此使用指針變量存儲字符串地址是用動態(tài)內(nèi)存分配功能非常有效。
指針首先是一個變量,指針只是存儲了另一個內(nèi)存位置的地址的變量。
- 聲明一個char類型的指針變量 即只創(chuàng)建了指針,沒有指定一個存儲字符串的地方,
char *pString = NULL;
- 指針只是一個存儲另一個內(nèi)存位置的地址的變量。
const size_t BUF_SIZE = 100;
char buffer[BUF_SIZE]
scanf_s("%s", buffer, BUF_SIZE);
size_t length = strnlen_s(buffer, BUF_SIZE) + 1;
char *pString = malloc(length);
if(!pString)
{
printf("memory allocation failed.\n");
return 1;
}
strcpy_s(pString,length,buffer);
printf(" %s ", pString);
free(pString);
pString = NULL;
這段代碼把一個字符串讀入一個char數(shù)組中,給讀入的字符串分配堆上的內(nèi)存,在將字符串復制到pString引用的內(nèi)存中。就允許重用buffer來讀取更多的數(shù)據(jù)。
7.5.1使用指針數(shù)組
- 創(chuàng)建一個指針數(shù)組,存儲字符串的位置。
char *pS[10] = { NULL };
這個語句聲明一個數(shù)組pS,數(shù)組包含10個char*類型的元素。
- 數(shù)組中的每一個元素都可以存儲字符串的地址。
- 初始化列表中只有一個NULL,NULL將任意大小的指針數(shù)組中的所有元素都初始化為NULL。
#define STR_COUNT 10
const size_t BUF_SIZE = 100;
char buffer[BUF_SIZE];
char *pS[STR_COUNT] = { NULL };
size_t str_size = 0;
for(size_t i = 0; i < STR_COUNT; ++i)
{
scanf_s("%s",buffer, BUF_SIZE);
str_size = strnlen_s(buffer, BUF_SIZE) + 1;
pS[i] = malloc(str_size);
if(!pS[i] return 1;
strcpy_s(pS[i],str_size,buffer);
}
for(size_t i =0; i < STR_COUNT ; ++i)
{
free(pS[i]);
pS[i] = NULL;
}
- 數(shù)組記號
使用指針名和索引值共同構成
數(shù)組記號來存儲相同類型的幾個數(shù)據(jù)類型 - 指針數(shù)組是指針記號
指針名和索引值構成
數(shù)組記號和指針記號的區(qū)別是什么呢?
指向一塊推內(nèi)存的指針變量不僅可以用指針記號還可以用數(shù)組記號
int count = 100;
double *data = calloc(count,sizeof(count));
for(int i = 0; i<count; ++i)
{
data[i] = double(i+1)*(i+1);
}
- pS數(shù)組的每個元素都保存從鍵盤讀取的一個字符串的地址
問題:
1、字符串的存儲與引用方式有幾種類型。
2、 如何處理任意長度的字符串
3、如果不知道要輸入多少個字符串,該怎么辦?