指針(復(fù)習(xí))
指針(pointer) 或 指針變量(pointer variable)丹诀。是“儲(chǔ)存地址的變量”钝的。
它存儲(chǔ)的是某一塊內(nèi)存的地址,而其它變量直接存儲(chǔ)的是值铆遭。
int *p; //變量p是個(gè)指向整數(shù)型變量內(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ù)型變量內(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首元素蛮原。
//通過在地址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ū)”都沒包含關(guān)系)
指針保存的是內(nèi)存的地址车海。C 語言編譯器會(huì)根據(jù)指針類型決定指針指向的對(duì)象長度。
-
int *
類型指針會(huì)從指定地址向后尋找4
個(gè)字節(jié)作為變量的儲(chǔ)存單元隘击;- -
double *
類型指針會(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 語言程序在編譯時(shí)會(huì)被分配到內(nèi)存上的一片有限的連續(xù)區(qū)域凶赁,這部分內(nèi)存會(huì)被用于存儲(chǔ)局部變量(在某一個(gè)函數(shù)內(nèi)聲明的變量)的值咧栗。我們平時(shí)聲明局部變量、給局部變量賦值的時(shí)候就在使用這部分的內(nèi)存虱肄。這部分的內(nèi)存區(qū)域被我們稱為——棧區(qū)(stack)致板。
- 棧上的內(nèi)存并不需要程序員進(jìn)行管理。
堆區(qū) for 程序主動(dòng)申請(qǐng)
這部分的內(nèi)存是我們通過程序手動(dòng)地向系統(tǒng)申請(qǐng)的浩峡。棧區(qū)內(nèi)存大小編譯時(shí)就已經(jīng)被限制可岂,如果使用超過限制的內(nèi)存就會(huì)出現(xiàn)“溢出”的情況,而堆區(qū)的內(nèi)存可以一直被申請(qǐng)翰灾、使用缕粹,直到操作系統(tǒng)中的有效內(nèi)存無法再被申請(qǐng)為止,相比之下纸淮,堆區(qū)控制起來更為靈活平斩。
- 堆區(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)存我們既不說它是“棧區(qū)”侈沪,也不說它是“堆區(qū)”揭璃。
動(dòng)態(tài)申請(qǐng)內(nèi)存
正因棧區(qū)上內(nèi)存大小受限,在內(nèi)存需要比較大的(超出棧區(qū)限制)的情況下亭罪,需要申請(qǐng)堆區(qū)上的內(nèi)存瘦馍。但堆區(qū)被申請(qǐng)后,在使用過程中若不釋放应役,就可能“內(nèi)存泄漏(memory leak)”情组。很多企業(yè)級(jí)應(yīng)用燥筷,都因內(nèi)存泄漏而在“正常”運(yùn)轉(zhuǎn)很長時(shí)間后院崇,突然崩潰肆氓。
若需使用堆上內(nèi)存,需將malloc.h或stdlib.h引入程序底瓣。這兩個(gè)標(biāo)準(zhǔn)庫都定義了申請(qǐng)谢揪、管理堆區(qū)上內(nèi)存的函數(shù)。引入后濒持,可通過
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 *
(無類型指針)類型,在malloc
之前添加的(int *)
會(huì)將這片內(nèi)存空間的起始地址標(biāo)記為整數(shù)型的地址蚁孔,使之與整數(shù)型的指針變量相匹配奶赔,否則編譯器就會(huì)說出現(xiàn)了類型不匹配的問題,不讓你的程序通過編譯杠氢。
如果要修改棧區(qū)內(nèi)存空間中的值應(yīng)該怎么做站刑?
可以直接通過p = 4;
這樣的語句實(shí)現(xiàn)嗎?不能鼻百。因?yàn)?code>p是個(gè)指針變量绞旅,寫
p = 4;
的話我們將修改存儲(chǔ)地址。
要對(duì)值進(jìn)行修改温艇,需使用之取值符號(hào)*
因悲,用*p = 4;
修改它指向的空間內(nèi)存儲(chǔ)的值。這點(diǎn)堆區(qū)空間與棧區(qū)空間相同勺爱。
相關(guān)語句
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)過初始化的晃琳,全部被設(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)該再被訪問的地址對(duì)應(yīng)的內(nèi)存,要將這個(gè)指針置為空取逾,即arr = NULL;
.
指向指針的指針
在給出的代碼中耗绿,我們聲明了一個(gè)指針變量p
并且讓它指向了整數(shù)型變量a
。我們有沒有可能聲明另一個(gè)指針砾隅,讓它指向我們已經(jīng)有的指針p呢误阻?
從內(nèi)存上來想,指針變量p
也是保存在內(nèi)存里的晴埂,所以它的地址也應(yīng)該是可以通過&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
無類型指針以及類型轉(zhuǎn)化
malloc函數(shù)的返回值類型為void *,是一種特殊的指針類型儒洛,任何一個(gè)其他指針變量都可以被直接賦值給void *類型的指針精耐,例如:
void *vp;
int *p;
p = &"123";
vp = p;
寫法合法。
強(qiáng)制類型轉(zhuǎn)換
但如果反過來琅锻,直接將無類型指針賦給一個(gè)其他類型指針變量卦停,就必須要在前面加上被賦值指針變量的類型,如(int *)
恼蓬。通過(int *)
這種方式使值的類型發(fā)生改變惊完,稱為 強(qiáng)制類型轉(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)()
被稱為 類型轉(zhuǎn)換運(yùn)算符小槐,可將之后的數(shù)值(變量、字面量或者函數(shù)的返回值等)強(qiáng)制變?yōu)槔ㄌ?hào)內(nèi)說明的類型荷辕。
隱式類型轉(zhuǎn)換
除了強(qiáng)制類型轉(zhuǎn)換凿跳,在混合多種不同類型的運(yùn)算中,還存在一種 隱式類型轉(zhuǎn)換(implicit type conversion 或 type coercion)桐腌。
- 如果一個(gè)運(yùn)算(和一個(gè)運(yùn)算符關(guān)聯(lián))中拄显,參與運(yùn)算的數(shù)值類型不同,則會(huì)先轉(zhuǎn)成同一類型案站,然后再進(jìn)行運(yùn)算躬审。
- 隱式轉(zhuǎn)換有一個(gè)固定的轉(zhuǎn)換方向,盡可能保證數(shù)據(jù)的精度蟆盐。例如承边,int型會(huì)被轉(zhuǎn)為long型,float型會(huì)被轉(zhuǎn)為double型石挂。
- 賦值時(shí)博助,類型會(huì)以賦值號(hào)左側(cè)為準(zhǔn),右側(cè)的表達(dá)式的結(jié)果類型會(huì)被轉(zhuǎn)為左邊變量的類型痹愚。如果右側(cè)表達(dá)式其實(shí)精度高于左側(cè)的變量類型的精度時(shí)富岳,一部分超出精度的數(shù)據(jù)將會(huì)丟失蛔糯。
位運(yùn)算
我們不僅可以把數(shù)值按照其聲明類型看待,也可以把他們當(dāng)成最本質(zhì)的內(nèi)存中的二進(jìn)制存儲(chǔ)看待窖式。
C 語言提供了直接對(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)算來得要高效淮逻。
- 按位異或:
異或操作符“^
”,“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é)生中名最長(名字中的一個(gè)空格長度計(jì)為 11)的學(xué)生是誰吨拍。
提示 1:
帶有空格的輸入褪猛,可以使用 scanf 讀入時(shí)可以逐字符讀入,第一個(gè)參數(shù)使用 "%c"羹饰,每行讀入以 \n 字符被讀入來判斷結(jié)束伊滋。對(duì)于是否還有新的行沒有讀入的情況,可以用: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é)生的名字不超過 100個(gè)字符乌妙。
輸出 n位學(xué)生中最長的學(xué)生名字(如果有多個(gè)名字一樣長的學(xué)生,輸出第一個(gè))建钥。
樣例輸入
3
Steve Jobs
Bill Ma
Sunny Fei
樣例輸出
Steve Jobs
解:這道題就非常惡心了藤韵,與其說是惡心,不如說是知識(shí)儲(chǔ)備不夠熊经,這里需要我做的是讀入字符串泽艘,但是反復(fù)會(huì)有溢出/段錯(cuò)誤的提示欲险,因此怎么樣使得字符串不溢出是需要考慮的問題。踩過的坑: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); //這里如果不寫\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++;//檢測到行末的\n焕盟,數(shù)組就換行讀入下個(gè)名字秋秤,
//還記得第一個(gè)scanf的\n吧,不寫的話脚翘,本個(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;
}
遇到問題的習(xí)題:哈希函數(shù):
小明設(shè)計(jì)了一個(gè)哈希函數(shù),將一個(gè)長度為 k 的字符串轉(zhuǎn)成個(gè)長度為 32的字符串来农。設(shè)計(jì)如下:
聲明一個(gè)長度為 32 的數(shù)組 arr鞋真,并將其中元素全部初始化為 0。
取出每一位的 ASCII 值沃于,將長度為 k 的字符串中第 ii位的 ASCII 碼加入到 arr[i % 32] 中(1≤i≤k)涩咖。
聲明一個(gè)長度為 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è)長度為 kk 的字符串(32 < k < 50032<k<500)薄风,這個(gè)字符串由大寫字母饵较、小寫字母和數(shù)字組成(不含空格)。
輸出格式
輸出為一行遭赂,為一個(gè)長度為 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;
}
踩過的坑:https://wenda.jisuanke.com/course/604?question_id=6108#answer-7544