指針(復(fù)習(xí))
指針(pointer) 或 指針變量(pointer variable)。是“儲(chǔ)存地址的變量”。
它存儲(chǔ)的是某一塊內(nèi)存的地址,而其它變量直接存儲(chǔ)的是值。
int *p; //變量p是個(gè)指向整數(shù)型變量?jī)?nèi)存空間的指針變量,存的是整數(shù)型變量a的內(nèi)存地址。
p = &a;
指針作為函數(shù)參數(shù)(復(fù)習(xí))
而內(nèi)存地址做函數(shù)參數(shù)時(shí)void swap(int *a, int *b);
,a
,b
都是指針變量,形參a和b也是兩個(gè)指向整數(shù)型變量?jī)?nèi)存空間的指針變量。
當(dāng)函數(shù)被調(diào)用,兩個(gè)相應(yīng)的地址就會(huì)被作為實(shí)際地址傳入,分別被保存在指針變量a和指針變量b中,并在函數(shù)內(nèi)使用。
指針與數(shù)組 (復(fù)習(xí))
int a[3] = {1, 2, 3};
int *p;
p = &a[0]; //p保存了數(shù)組a首元素地址,指向數(shù)組a首元素。
//通過(guò)在地址p上+1或-1,可使指針內(nèi)存儲(chǔ)地址改變,使指針指向前一或后一元素。
字符串和指針
char *string2 = "Hello";
這里string2
實(shí)際是一種字符指針。在string2
中存儲(chǔ)了一個(gè)程序運(yùn)行時(shí)"Hello"
這個(gè)字面量在內(nèi)存中一個(gè)“字面量池區(qū)”的地址。
(注意,“字面量池區(qū)”與“棧區(qū)”、“堆區(qū)”和“全局區(qū)”都沒(méi)包含關(guān)系)
指針保存的是內(nèi)存的地址。C 語(yǔ)言編譯器會(huì)根據(jù)指針類(lèi)型決定指針指向的對(duì)象長(zhǎng)度。
-
int *
類(lèi)型指針會(huì)從指定地址向后尋找4
個(gè)字節(jié)作為變量的儲(chǔ)存單元;- -
double *
類(lèi)型指針會(huì)從指定地址向后尋找8
個(gè)字節(jié)作為變量的儲(chǔ)存單元。
程序本身會(huì)被加載到內(nèi)存,程序的字面量和變量在程序運(yùn)行時(shí)也都存儲(chǔ)在內(nèi)存中。指針讓人更靈活地進(jìn)行涉及內(nèi)存的操作,也為程序設(shè)計(jì)中的一些復(fù)雜結(jié)構(gòu)設(shè)計(jì)提供了方便。
動(dòng)態(tài)分配內(nèi)存
棧區(qū) for 局部變量
C 語(yǔ)言程序在編譯時(shí)會(huì)被分配到內(nèi)存上的一片有限的連續(xù)區(qū)域,這部分內(nèi)存會(huì)被用于存儲(chǔ)局部變量(在某一個(gè)函數(shù)內(nèi)聲明的變量)的值。我們平時(shí)聲明局部變量、給局部變量賦值的時(shí)候就在使用這部分的內(nèi)存。這部分的內(nèi)存區(qū)域被我們稱(chēng)為——棧區(qū)(stack)。
- 棧上的內(nèi)存并不需要程序員進(jìn)行管理。
堆區(qū) for 程序主動(dòng)申請(qǐng)
這部分的內(nèi)存是我們通過(guò)程序手動(dòng)地向系統(tǒng)申請(qǐng)的。棧區(qū)內(nèi)存大小編譯時(shí)就已經(jīng)被限制,如果使用超過(guò)限制的內(nèi)存就會(huì)出現(xiàn)“溢出”的情況,而堆區(qū)的內(nèi)存可以一直被申請(qǐng)、使用,直到操作系統(tǒng)中的有效內(nèi)存無(wú)法再被申請(qǐng)為止,相比之下,堆區(qū)控制起來(lái)更為靈活。
- 堆區(qū)上內(nèi)存由程序員自己管理,如果不釋放可能會(huì)造成內(nèi)存泄露的嚴(yán)重后果。
全局區(qū)(或靜態(tài)區(qū),static storage area) for 全局靜態(tài)變量
程序中的全局變量和靜態(tài)變量都被存儲(chǔ)在這塊內(nèi)存區(qū)域中。這塊內(nèi)存我們既不說(shuō)它是“棧區(qū)”,也不說(shuō)它是“堆區(qū)”。
動(dòng)態(tài)申請(qǐng)內(nèi)存
正因棧區(qū)上內(nèi)存大小受限,在內(nèi)存需要比較大的(超出棧區(qū)限制)的情況下,需要申請(qǐng)堆區(qū)上的內(nèi)存。但堆區(qū)被申請(qǐng)后,在使用過(guò)程中若不釋放,就可能“內(nèi)存泄漏(memory leak)”。很多企業(yè)級(jí)應(yīng)用,都因內(nèi)存泄漏而在“正常”運(yùn)轉(zhuǎn)很長(zhǎng)時(shí)間后,突然崩潰。
若需使用堆上內(nèi)存,需將malloc.h或stdlib.h引入程序。這兩個(gè)標(biāo)準(zhǔn)庫(kù)都定義了申請(qǐng)、管理堆區(qū)上內(nèi)存的函數(shù)。引入后,可通過(guò)
int *p;
p = (int *) malloc(sizeof(int));
的方式聲明一個(gè)整數(shù)型的指針p
,向系統(tǒng)申請(qǐng)堆區(qū)上sizeof(int)
(表示一個(gè)整數(shù)型變量所需的內(nèi)存空間大小)的一塊內(nèi)存空間,并將指針p
賦值為這片空間所在的起始地址,使得p指向這片空間。
在這里,malloc
返回默認(rèn)為void *
(無(wú)類(lèi)型指針)類(lèi)型,在malloc
之前添加的(int *)
會(huì)將這片內(nèi)存空間的起始地址標(biāo)記為整數(shù)型的地址,使之與整數(shù)型的指針變量相匹配,否則編譯器就會(huì)說(shuō)出現(xiàn)了類(lèi)型不匹配的問(wèn)題,不讓你的程序通過(guò)編譯。
如果要修改棧區(qū)內(nèi)存空間中的值應(yīng)該怎么做?
可以直接通過(guò)p = 4;
這樣的語(yǔ)句實(shí)現(xiàn)嗎?不能。因?yàn)?code>p是個(gè)指針變量,寫(xiě)
p = 4;
的話我們將修改存儲(chǔ)地址。
要對(duì)值進(jìn)行修改,需使用之取值符號(hào)*
,用*p = 4;
修改它指向的空間內(nèi)存儲(chǔ)的值。這點(diǎn)堆區(qū)空間與棧區(qū)空間相同。
相關(guān)語(yǔ)句
int *arr = (int*)malloc( n * sizeof (int));//這只有一個(gè)參數(shù)
或int *arr = (int*)calloc( n , sizeof (int));這有兩個(gè)參數(shù)
- calloc與malloc主要有兩點(diǎn)不同:
calloc函數(shù)申請(qǐng)的內(nèi)存空間是經(jīng)過(guò)初始化的,全部被設(shè)成了0,而不是像malloc所申請(qǐng)的空間那樣都是未經(jīng)初始化的。
calloc函數(shù)適合為數(shù)組申請(qǐng)空間,我們可以將第二個(gè)參數(shù)設(shè)置為數(shù)組元素的空間大小,將第一個(gè)參數(shù)設(shè)置為數(shù)組的元素?cái)?shù)量。
棧內(nèi)存的釋放
只申請(qǐng)空間而不釋放,在工程上不安全。
一般要在main
函數(shù)結(jié)束返回之前,使用free(arr);
釋放arr
數(shù)組指向的被分配的堆區(qū)上的空間。同時(shí)避免錯(cuò)誤地使用arr指針,再次觸碰到那個(gè)已經(jīng)不應(yīng)該再被訪問(wèn)的地址對(duì)應(yīng)的內(nèi)存,要將這個(gè)指針置為空,即arr = NULL;
.
指向指針的指針
在給出的代碼中,我們聲明了一個(gè)指針變量p
并且讓它指向了整數(shù)型變量a
。我們有沒(méi)有可能聲明另一個(gè)指針,讓它指向我們已經(jīng)有的指針p呢?
從內(nèi)存上來(lái)想,指針變量p
也是保存在內(nèi)存里的,所以它的地址也應(yīng)該是可以通過(guò)&p
獲得到的。
可以再賦給它指針
int **q;
q = &p;
#include <stdio.h>
int main() {
int a = 4;
int *p;
p = &a;
int **q;
q = &p;
printf("p in %p, q =%p\n",&p,q);
printf("a in %p, p = %p, *q = %p\n",&a, p, *q); //以p格式輸出成0x十六進(jìn)制數(shù)
printf("a = %d, *p = %d, **q = %d\n", a, *p, **q);
return 0;
}
運(yùn)行結(jié)果:
p in 0x7ffd60bf0f58, q =0x7ffd60bf0f58
a in 0x7ffd60bf0f54, p = 0x7ffd60bf0f54, *q = 0x7ffd60bf0f54
a = 4, *p = 4, **q = 4
無(wú)類(lèi)型指針以及類(lèi)型轉(zhuǎn)化
malloc函數(shù)的返回值類(lèi)型為void *,是一種特殊的指針類(lèi)型,任何一個(gè)其他指針變量都可以被直接賦值給void *類(lèi)型的指針,例如:
void *vp;
int *p;
p = &"123";
vp = p;
寫(xiě)法合法。
強(qiáng)制類(lèi)型轉(zhuǎn)換
但如果反過(guò)來(lái),直接將無(wú)類(lèi)型指針賦給一個(gè)其他類(lèi)型指針變量,就必須要在前面加上被賦值指針變量的類(lèi)型,如(int *)
。通過(guò)(int *)
這種方式使值的類(lèi)型發(fā)生改變,稱(chēng)為 強(qiáng)制類(lèi)型轉(zhuǎn)換(explicit type conversion)。
例如:
float num = 2.3f;
printf("%d\n", (int) num);
會(huì)將一個(gè)浮點(diǎn)型的變量在輸出時(shí)強(qiáng)制轉(zhuǎn)換為整數(shù)型的變量。這里包圍了int
的圓括號(hào)()
被稱(chēng)為 類(lèi)型轉(zhuǎn)換運(yùn)算符,可將之后的數(shù)值(變量、字面量或者函數(shù)的返回值等)強(qiáng)制變?yōu)槔ㄌ?hào)內(nèi)說(shuō)明的類(lèi)型。
隱式類(lèi)型轉(zhuǎn)換
除了強(qiáng)制類(lèi)型轉(zhuǎn)換,在混合多種不同類(lèi)型的運(yùn)算中,還存在一種 隱式類(lèi)型轉(zhuǎn)換(implicit type conversion 或 type coercion)。
- 如果一個(gè)運(yùn)算(和一個(gè)運(yùn)算符關(guān)聯(lián))中,參與運(yùn)算的數(shù)值類(lèi)型不同,則會(huì)先轉(zhuǎn)成同一類(lèi)型,然后再進(jìn)行運(yùn)算。
- 隱式轉(zhuǎn)換有一個(gè)固定的轉(zhuǎn)換方向,盡可能保證數(shù)據(jù)的精度。例如,int型會(huì)被轉(zhuǎn)為long型,float型會(huì)被轉(zhuǎn)為double型。
- 賦值時(shí),類(lèi)型會(huì)以賦值號(hào)左側(cè)為準(zhǔn),右側(cè)的表達(dá)式的結(jié)果類(lèi)型會(huì)被轉(zhuǎn)為左邊變量的類(lèi)型。如果右側(cè)表達(dá)式其實(shí)精度高于左側(cè)的變量類(lèi)型的精度時(shí),一部分超出精度的數(shù)據(jù)將會(huì)丟失。
位運(yùn)算
我們不僅可以把數(shù)值按照其聲明類(lèi)型看待,也可以把他們當(dāng)成最本質(zhì)的內(nèi)存中的二進(jìn)制存儲(chǔ)看待。
C 語(yǔ)言提供了直接對(duì)內(nèi)存中每一個(gè)按位進(jìn)行運(yùn)算的操作符,使用按位運(yùn)算可以將一個(gè)存儲(chǔ)單位中的各個(gè)二進(jìn)制位左移或右移一位,也可以將一個(gè)存儲(chǔ)單位中所有的二進(jìn)制位取反,這些操作多數(shù)要比直接進(jìn)行數(shù)值上的運(yùn)算來(lái)得要高效。
- 按位異或:
異或操作符“^
”,“a^b
”的位運(yùn)算規(guī)則是:當(dāng)且僅當(dāng)按位運(yùn)算兩數(shù)位不同則該結(jié)果為1
,否則位運(yùn)算結(jié)果為0
舉例:輸入一個(gè)數(shù) 以及取反。
#include <stdio.h>
int main() {
int number;
scanf("%d", &number);
printf("%08x %08x" , number ,~number);
return 0;
}
這里使用了%08x進(jìn)行占位,其中%x表示以 1616 進(jìn)制形式作為數(shù)字格式,在中間插入的08可以使得輸出不足8格數(shù)字時(shí),在左側(cè)補(bǔ)齊知道達(dá)到8位數(shù)字。
#include <stdio.h> //取反
int main() {
int number;
scanf("%d", &number);
printf("%08x %08x" , number ,~number);
return 0;
}
#include <stdio.h>
int main() {
int number;
number = 3;
printf("%d\n", number<<1); //左移
printf("%d\n", number>>1); //右移
return 0;
}
左移一位時(shí),實(shí)際相當(dāng)于被乘以 2;
右移一位時(shí),相當(dāng)于被除以 2(若二進(jìn)制形式最右位是 1,則相當(dāng)于減 1 再除以 2);負(fù)數(shù)右移是最高位補(bǔ)1的。
題目:
代碼:
#include <stdio.h>
int plus(char operator_ , int c ){
if ((c == 1) && (operator_ == '+'))
return 1;
if ((c == 0) && (operator_ == '+'))
return 1;
if ((c == 1) && (operator_ == '-'))
return 0;
if ((c == 0) && (operator_ == '-'))
return 0;
}
int main() {
char t,operator_;
int r_pri = 0;
int w_pri = 0;
int x_pri = 0;
int output,i;
i = 0;
while(i<3){
scanf("%c",&t);
switch (t){
case 'r': r_pri = 1;
continue;
case 'w': w_pri = 1;
continue;
case 'x': x_pri = 1;
continue;
case '\n':
i = 3;
break; //switch里的break是終止switch的,要想跳出while得另設(shè)別的條件。
}
}
//printf("begin:rwx %d%d%d\n",r_pri,w_pri,x_pri);
while(scanf("%c",&t)!=EOF){
switch(t){
case '+': operator_ = '+'; continue;
case '-': operator_ = '-'; continue;
case 'r': r_pri = plus(operator_,r_pri); continue;
case 'w': w_pri = plus(operator_,w_pri); continue;
case 'x': x_pri = plus(operator_,x_pri); continue;
case '\n': //printf("%d%d%d\n",r_pri,w_pri,x_pri);
continue;
}
}
output = 1*x_pri + 2*w_pri + 4*r_pri;
printf("%d",output);
return 0;
}
踩坑的題目:
一個(gè)班級(jí) n個(gè)學(xué)生,每學(xué)生有一個(gè)名字。班主任希望知道學(xué)生中名最長(zhǎng)(名字中的一個(gè)空格長(zhǎng)度計(jì)為 11)的學(xué)生是誰(shuí)。
提示 1:
帶有空格的輸入,可以使用 scanf 讀入時(shí)可以逐字符讀入,第一個(gè)參數(shù)使用 "%c",每行讀入以 \n 字符被讀入來(lái)判斷結(jié)束。對(duì)于是否還有新的行沒(méi)有讀入的情況,可以用:while (scanf(/* 這部分省略*/) != EOF) {}
的方式進(jìn)行。
提示 2:
由于 scanf 之后使用 "%c" 格式,讀入 n 之后的 \n 一定要在之前進(jìn)行處理。
- 練習(xí)對(duì)字符的操作和使用
- 練習(xí)使用循環(huán)
- 練習(xí) strlen 函數(shù)使用
- 鼓勵(lì)使用 strcpy 函數(shù)
- 練習(xí)使用 EOF 表示讀到文件末尾
- 輸入格式
程序接受的輸入的第一行是一個(gè)整數(shù) n,表示學(xué)生的總數(shù)。之后的 n行,每行會(huì)接受一個(gè)學(xué)生的名字(可能有空格)。學(xué)生的名字不超過(guò) 100個(gè)字符。
輸出 n位學(xué)生中最長(zhǎng)的學(xué)生名字(如果有多個(gè)名字一樣長(zhǎng)的學(xué)生,輸出第一個(gè))。
樣例輸入
3
Steve Jobs
Bill Ma
Sunny Fei
樣例輸出
Steve Jobs
解:這道題就非常惡心了,與其說(shuō)是惡心,不如說(shuō)是知識(shí)儲(chǔ)備不夠,這里需要我做的是讀入字符串,但是反復(fù)會(huì)有溢出/段錯(cuò)誤的提示,因此怎么樣使得字符串不溢出是需要考慮的問(wèn)題。踩過(guò)的坑:https://wenda.jisuanke.com/course/604?question_id=6054#discussion-3887
代碼釋讀
#include <stdio.h>
#include <string.h>
int main() {
int n,i,j;
char string[101] ; //為啥是101呢?100字符串+一格=個(gè)結(jié)尾字符
scanf("%d\n", &n); //這里如果不寫(xiě)\n的話,光標(biāo)位置實(shí)際上就還在\n以前
char t;
char arr[n][101]; //為啥是101呢?100字符串+一格=個(gè)結(jié)尾字符
i = 0;
j = 0;
while (scanf("%c",&t)!=EOF){
if (t != '\n'){
arr[i][j] = t;
j++;
}else{
arr[i][j] = '\0';
j = 0;
i++;//檢測(cè)到行末的\n,數(shù)組就換行讀入下個(gè)名字,
//還記得第一個(gè)scanf的\n吧,不寫(xiě)的話,本個(gè)while第一步就會(huì)讀入第一行數(shù)字的\n,觸發(fā)i++
}
}
strcpy(string,arr[0]);
for (i = 1; i< n; i++){
//printf("%s\n",arr[i]);
if (strlen(arr[i]) > strlen(string)){
strcpy(string,arr[i]);
}
}
printf("%s",string);
return 0;
}
遇到問(wèn)題的習(xí)題:哈希函數(shù):
小明設(shè)計(jì)了一個(gè)哈希函數(shù),將一個(gè)長(zhǎng)度為 k 的字符串轉(zhuǎn)成個(gè)長(zhǎng)度為 32的字符串。設(shè)計(jì)如下:
聲明一個(gè)長(zhǎng)度為 32 的數(shù)組 arr,并將其中元素全部初始化為 0。
取出每一位的 ASCII 值,將長(zhǎng)度為 k 的字符串中第 ii位的 ASCII 碼加入到 arr[i % 32] 中(1≤i≤k)。
聲明一個(gè)長(zhǎng)度為 32 的數(shù)組 bits,令 bits[j] 為 arr[31 - j] 與 arr[j] << 1 的值進(jìn)行了按位異或運(yùn)算后得到的結(jié)果(0≤j≤31)。
計(jì)算出 bits[j] % 85 + 34 并將該十進(jìn)制數(shù)在 ASCII 碼中對(duì)應(yīng)的字符輸出到結(jié)果字符串的第 j + 1j+1 位(0≤j≤31)。
請(qǐng)實(shí)現(xiàn)一個(gè)程序,當(dāng)輸入一個(gè)字符串 s 后,輸出哈希函數(shù)的結(jié)果 f(s)。
輸入格式
輸入為一行,包括一個(gè)長(zhǎng)度為 kk 的字符串(32 < k < 50032<k<500),這個(gè)字符串由大寫(xiě)字母、小寫(xiě)字母和數(shù)字組成(不含空格)。
輸出格式
輸出為一行,為一個(gè)長(zhǎng)度為 3232 的字符串哈希結(jié)果
樣例輸入
123456789012345678901234567890123
樣例輸出
"p*+,)&'ebst*+,)&'ebst*+,)&'eb&r
代碼:
#include <stdio.h>
#include <string.h>
int main(){
char s[501];
scanf("%s",s);
int k,i,j;
char l,m;
int arr[32];
for (i = 0 ;i <32 ;i++){
arr[i] = 0;
}
k = strlen(s);
for (i = 1;i <= k;i++){
arr[i%32] = (int)(s[i-1]) + arr[i%32];
}
int bits[32];
for (j = 0;j < 32;j++){
bits[j] = (arr[31 - j] ^ (arr[j]<<1));
//printf("arr(31-%d) = %d ; arr[%d]<<1 = %d; bits[%d] = %d\n",j,arr[31-j],j,arr[j]<<1,j,bits[j]);
}
for (j = 0; j < 32 ; j++){
bits[j] = ( bits[j] % 85 + 34);
printf("%c",(unsigned char)(bits[j]));
}
return 0;
}
踩過(guò)的坑:https://wenda.jisuanke.com/course/604?question_id=6108#answer-7544