《C與指針》第六章練習


本章問題

1.如果一個值的類型無法簡單的通過觀察它的位模式來判斷,那么機器是如何知道應該怎樣對這個值進行操縱的?

answer:The machine doesn't make this determination.The compiler creates the appropriate instructions(指令) based on the declared type of the value,and the machine blindly executes the instructions.

(機器無法檢測這個值,編譯器在聲明一個值的類型的基礎上創建一個合適的指令,然后機器只能夠執行指令)

 

2.C為什么沒有一種方法來聲明字面值指針常量呢?

answer:They are rarely used because you can't tell ahead of time where the compiler will put variables.

(它們幾乎很少用,因為你不可能在編譯器創建變量之前知道它在哪個位置)

 

3.假定一個整數的值為244,為什么機器不會把這個值解釋為一個內存地址呢?

answer:The value is an integer,so the compiler will not generate instructions to dereference it.

(這個值是一個整型,所以編譯器不會產生這樣的指令)

 

4.在有些機器上,編譯器在內存位置零存儲0這個值,對NULL指針進行解引用操作將訪問這個位置,這種方法產生什么后果?

answer:It is dangerous,First,the result of dereferencing a NULL pointer is implementation specific,so programs should not do it,Allowing a program to continue after such an access is unfortunate,because of the strong possibility that the program is not operating correctly.

(這是非常危險的,首先,對NULL指針解引用操作的結果因編譯器而異,所以程序不該這么做,允許一個程序在這種訪問之后繼續運行是非常不幸的,因為有很大的可能程序不能正確運行)

 

5.表達式(a)和(b)的求值過程有沒有什么區別?如果有的話,區別在哪里?假定變量offset的值為3.

int i[10];
int *p = &i[0];
int offset;

p += offset; (a)
p += 3;      (b)

answer:Even if offset has the same value as the literal(字面值) in the next expression.it is more time consuming(消耗) to evaluate(計算) the first expression because the multiplication to scale(規模) offset to the size of an integer must be done at run time.This is because the variable might contain any value,and the compiler has no way of knowing ahead of time what the value might actually be.On the other hand,The literal three can be scaled to an integer by multiplying it at compiler time,and the result of that multiplication is simply added to p at run time,In other words,the second expression can be implemented by simply adding 12 to p(on a machine with four-byte integers);no runtime multiplication is needed.

(即使offset跟后面一個表達式的字面值的值相等,但它比計算第一個表達式要消耗更多的時間,因為對整型大小的偏移量必須在運行時才知道,這是因為變量可能包括更多的值,而編譯器不能在變量賦值之前知道它的值,另一方面,在編譯的時候字面值3可以被縮放到一個整數中,結果在運行是添加到p,換句話說,第二個表達式可以簡單的用12加上p,在四位機器上,不需要運行時的再相加)

 

6.下面的代碼有沒有問題,如果有的話,問題在哪里?

int array[ARRAY_SIZE];
int *pi;

for(pi = &array[0];pi < array[ARRAY_SIZE];)
    *++pi = 0;

 

answer:有兩個錯誤,對增值之后的指針進行解引用時,數組的第一個元素沒有被初始化為0,另外,指針在越過數組的右邊界以后仍然進行解引用,它將把其他內存地址的內容清零。注意pi在數組之后立即聲明,如果編譯器恰好把它放在緊跟數組之后的位置,結果將是災難性的,會導致一個微妙的無限循環。

 

7.下面的表顯示了幾個內存位置的內容。每個位置由它的地址和存儲於該位置的變量名標識。所有數字以十進制形式表示。使用這些值,用4中方法分別計算下面各表達式的值,首先,假定所有的變量都是整型,找到表達式的右值,再找到表達式的左值,給出它所指定的內存位置的地址。接着,假定所有的變量都是指向整型的指針,重復上述步驟,注意:在執行地址運算時,假定整型和指針的長度都是4個字節。

變量 地址 內容 變量 地址 內容
a 1040 1028 o 1096 1024
c 1056 1076 q 1084 1072
d 1008 1016 r 1068 1048
e 1032 1088 s 1004 2000
f 1052 1044 t 1060 1012
g 1000 1064 u 1036 1092
h 1080 1020 v 1092 1036
i 1020 1080 w 1012 1060
j 1064 1000 x 1072 1080
k 1044 1052 y 1048 1068
m 1016 1008 z 2000 1000
n 1076 1056      

 

 

 

 

 

 

 

 

 

 

 

 

a. m 
b. v + 1 
c. j – 4 
d. a – d 
e. v – w
f. &c 
g. &e + 1 
h. &o – 4 
i. &( f + 2 ) 
j. *g
k. *k + 1
l. *( n + 1 ) 
m. *h – 4 
n. *( u – 4 )
o. *f – g 
p. *f - *g 
q. *s - *q 
r. *( r – t ) 
s. y > i 
t. y > *i
u. *y > *i 
v. **h 
w. c++ 
x. ++c 
y. *q++ 
z. (*q)++
aa. *++q
bb. ++*q 
cc. *++*q 
dd. ++*(*q)++ 

answer:

           |--------整型------------------|--------指針-------------------|
表達式--------右值--------左值地址--------右值--------左值地址
a. m             1008          1016              1008           1016
b. v + 1        1037           illegal            1040           illegal
c. j – 4         0996           illegal             0984          illegal
d. a – d        12               illegal              3              illegal
e. v – w        -24             illegal              -6             illegal
f. &c             1056           illegal             1056         illegal
g. &e + 1      1036           illegal             1036         illegal
h. &o – 4       1080          illegal              1080        illegal
i. &( f + 2 )     illegal        illegal              illegal        illegal
j. *g               illegal         illegal             1000          1064
k. *k + 1         illegal        illegal              1045          illegal
l. *( n + 1 )      illegal       illegal               1012         1060
m. *h – 4        illegal        illegal               1076          illegal
n. *( u – 4 )     illegal        illegal               1056         1076
o. *f – g           illegal        illegal               illegal         illegal
p. *f - *g           illegal        illegal               52             illegal
q. *s - *q          illegal        illegal               -80             illegal
r. *( r – t )         illegal        illegal              illegal          illegal
s. y > i              0                illegal                0             illegal
t. y > *i             illegal          illegal            illegal          illegal
u. *y > *i           illegal          illegal              1              illegal
v. **h                illegal           illegal           1080          1020
w. c++              1076          illegal              1076         illegal
x. ++c               1077           illegal             1080        illegal
y. *q++              illegal         illegal             1080        1072
z. (*q)++           illegal         illegal              1080        illegal
aa. *++q           illegal          illegal              1056        1076
bb. ++*q           illegal          illegal               1081       illegal
cc. *++*q          illegal          illegal               illegal     illegal
dd. ++*(*q)++   illegal          illegal               1021        illegal

 

本章練習

1.請編寫一個函數,它在一個字符串中進行搜索,查找所有在一個給定字符集合中出現的字符。這個函數的原型應該如下:
char *find_char(char const *source,char const *chars);
它的基本想法是查找source字符串中匹配chars字符串中任何字符的第一個字符,函數然后返回一個指向source中第一個匹配所找到的位置的指針,如果任何一個參數NULL,或任何一個參數所指向的字符串為空,函數也返回一個NULL指針。

舉個例子,假定source指向ABCDEF。如果chars指向XYZ、JURY或QQQQ,函數就返回一個NULL指針。如果chars指向XRCQEF,函數就返回一個指向source中C字符的指針。參數所指向的字符串是不會被修改的。

碰巧,C函數庫中存在一個名叫strpbrk的函數,它的功能幾乎和這個你要編寫的函數一模一樣。但這個程序的目的是讓你自己練習操縱指針,所以:

a 你不應該使用任何用於操縱字符串的庫函數(如strcpy,strcmp,index等)

b 函數中的任何地方都不應該使用下標


answer:

/*遇到一個頭疼的問題,由於兩個參數都是const變量,
導致聲明char*類型的變量指向source和chars時引出警告
initialization discards ‘const’ qualifier from pointer target type,
類型不對啊,如果聲明的char*類型為const變量,
那么就無法改變指針的值,並且返回的類型也不對
所以,頂着警告還是可以運行的*/

char *find_char(char const *source,char const *chars)
{
    char *ps = source;
    char *pc = NULL;
    if(source == NULL || chars == NULL)
        return NULL;
    while(*ps != '\0'){
        pc = chars;
        while(*pc != '\0'){
            if(*pc == *ps)
                return ps;
            pc++;
        }
        ps++;
    }
    return NULL;
}
/*看了下標准答案,里面也有這樣一個警告,
避免不了的問題,就是把const指針賦值給普通指針,
這個函數原型的參數為const變量,
卻要求返回一個普通指針變量類型*/

/*如果有更好的解決方法,請大神賜教*/
char *find_char(char const *source,char const *chars)
{
    char *p;
    if(source != NULL && chars != NULL){
        for(;*source != '\0';source++){
            for(p = chars; *p != '\0';p++)
                if(*p == *source)
                    return p;
        }
    }
    return NULL;
}
/*另外還寫了不用聲明任何變量的辦法,
不過返回值卻不是普通變量,還是const*/

char *find_char(char const *source,char const *chars)
{
    int size = 0;
    if(source != NULL && chars != NULL){
        for(;*chars++ != '\0';size++)
            ;
        for(;*source != '\0';source++){
            for(chars-=size; *chars != '\0';chars++)
                if(*chars == *source)
                    return chars;
        }
    }
    return NULL;
}

 

2.請編寫一個函數,刪除一個字符串的一部分。函數的原型如下:

int del_substr(char *str,char const *substr);
函數首先應該判斷substr是否出現在sub中。如果它並未出現,函數就返回0;如果出現,函數應該把str中位於該子串后面的所有字符復制到該子串的位置;從而刪除這個子串,然后函數返回1,如果substr在函數中出現多次,函數只刪除第一次出現的子串。函數的第二個參數不能被修改。

舉個例子,假定str指向ABCDEFG。如果substr指向FGH,CDF或XABC,函數返回0,str未作任何修改,但如果substr指向CDE,函數就把str修改為指向ABFG,方法是把FG和結尾的NUL字節復制到C的位置,然后函數返回1.不論出現什么情況,第二個參數都不能被修改。

a 你不應該使用任何用於操縱字符串的庫函數

b 函數中的任何地方都不應該使用下標引用

一個值得注意的地方是,空字符串是每一個字符串的子串,在字符串中刪除一個空字符串不會產生任何變化。

answer:

/*下面的代碼無法在ubuntu下面運行,包括后面的標准答案上的答案也一樣
我測試了一下,發現在終端里只要有*p++ = *q++這樣指針之間賦值的語句
就會出現那句要命的段核心錯誤,注意是賦值語句而不是比較語句
不知道是編譯器的問題還是這樣寫都不行呢
連strcpy都不行,一使用就段核心錯誤
不過只要把char *類型改為char[]數組就完全沒有問題呢
*/

*int del_substr(char *str, char const *substr)
{
    char *p = str;
    char *ps;
    char *q;
    if(str != NULL){
        if(substr == NULL)
            return 1;
        while(*p != '\0'){
            q = substr;
            ps = p;
            while(*q != '\0'){
                if(*q++ != *ps++)
                    break;
            }
            if(*q == '\0'){
                while(*p++ = *ps++)
                    ;
                return 1;
            }
            p++;
        }
    }
    return 0;
}
/*這是標准答案寫法*/
char *match(char *str,char *want)
{
    while(*want != '\0')
        if(*str++ != *want++)
            return NULL;
    return str;
}

int del_substr(char *str,char const *substr)
{
    char *next;
    while(*str != '\0'){
        next = match(str,substr);
        if(next != '\0')
            break;
        str++;
    }
    if(*str == '\0')
        return 0;
    while(*str++ = *next++)
        ;
    return 1;
}

 

 

這個題目告訴我們,指針常量是不可以更改的,字符指針跟字符數組的區別也是大大的。

另外,像char *str = "abcdefg";這條語句中str指向的是一個字符串常量,是不可以修改的昂,

想要使上面的函數正常運行,記得在聲明str與substr時務必要使用字符數組。

 

3.編寫函數reverse_string,它的原型如下:

void reverse_string(char *string);
函數把參數字符串中的字符反向排列,請使用指針而不是數組下標,不要使用任何C函數庫中用於操縱字符串的函數。

提示:不需要聲明一個局部變量數組來臨時存儲參數字符串。

answer:

/*寫到這一題的時候,就明白了上面一道題,原因是,當我在main函數里
聲明char * string = "ABCDEFG"時,就出現了錯誤
而我改為char string[] = "ABCDEFG"時,函數就沒有一點問題
關於字符指針和字符數組的問題,我將在文章末尾表述我的認識*/
#include <stdio.h>

void reverse_string(char *string);

int main()
{
    char string[] = "ABCDEFG";
    printf("%s\n",string);
    reverse_string(string);
    printf("%s\n",string);
}

void reverse_string(char *string)
{
    char *p = string;
    char *q = string;
    while(*q != '\0')
        q++;
    q--;
    while(p < q){
        char temp = *p;
        *p = *q;
        *q = temp;
        p++;
        q--;
    }
}

 


4.質數就是只能被1和本身整除的整數,Eratosthenes篩選法是一種計算質數的有效辦法。這個算法的第一步就是寫下所有從2至某個上限之間的所有整數,在算法剩余部分,你遍歷整個列表並剔除所有不是質數的整數。

后面的步驟是這樣的,找到列表中的第一個不被剔除的數也就是2,然后將列表中所有2的倍數全部剔除,因為它們都可以被2整除,因此不是質數。接着,再回到列表的頭部重新開始,此時列表中尚未被剔除的第一個數是3,所以在3之后把每逢第3個數的倍數剔除,完成這一步驟之后,再回到列表開頭,3后面的下一個數是4,但它是2的倍數,已經被剔除,所以將其跳過,輪到5,將所有5的倍數全部剔除,這樣以此類推,反復執行,最后列表中未被剔除的數均為質數。

編寫一個程序,實現這個算法,使用數組表示你的列表。每個數組元素的值用於標記對應的數是否剔除。開始時所有元素的值都設置為FALSE。如果你的程序運行於16位機器上,小心考慮是不是需要把某個變量聲明為long。一開始先使用包含1000個元素的數組。如果你使用字符數組,使用相同的空間,你將會比使用整數數組找到更多的質數,你可以使用下標來表示指向數組首元素和尾元素的指針,但你應該使用指針來訪問數組元素。

注意除了2以外,所有的偶數都不是質數,稍微多想一下,你可以使程序的空間效率大為提高,方法是數組中的所有元素只對應奇數,這樣,你在相同的數組空間內,你可以尋找到的質數的個數大約是原先的兩倍。

answer:

 

/*關於篩選法選質數我已經在第四章編程練習的第二題中給出了代碼
接下來我將寫出以字符數組為存儲單位
以及在數組中僅使用奇數的方法篩選質數*/
#include <stdio.h>

#define MAX 1001
void Eratosthenes(char []);

int main()
{
    int i;
    int count = 0;
    char prime[1001];
    for(i = 1; i < MAX; i++)
        prime[i] = 1;

    printf("the prime range is 2 to %d\n",MAX * 2 + 1);
    Eratosthenes(prime);
    printf("2\n");
    for(i = 1; i < MAX; i++){
        if(prime[i] == 1){
            printf("%d ",i * 2 + 1);
            count++;
        }
        if(count % 10 == 0)
            printf("\n");
    }
}

void Eratosthenes(char prime[])
{
    int i = 0;
    int i_p;
    int j;
    int j_p;
    for(i = 1; i < MAX; i++){
        if(prime[i] == 1){
            i_p = 2 * i + 1;
            for(j = 2 * i_p; j < MAX * 2 + 1; j += i_p)
                if(j % 2 == 1){
                    j_p = (j - 1)/2;
                    prime[j_p] = 0;
                }
        }
    }
}

 

5.修改前一題的Eratosthenes程序,使用位的數組而不是字符數組,這里要用到第5章編程練習中所開發的位數組函數。這個修改使程序的空間效率進一步提高,不過代價是時間效率降低,在你的系統中,使用這個方法,你所能找到的最大質數是多少?

answer: 

/*這個題目本身並不難
而它卻把我困住了三個小時
而我現在還不知道到底為啥
有興趣的朋友們可以去試試
原因是在我的Ubuntu下(也不知道是不是它的原因)
那個set_bit和clear_bit函數操作起來似乎有點問題
花了一大塊時間調試之后
我決定不繼續了,如果有知道原因的大神,請賜教*/

/*下面是答案*/
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include "bitarray.h"

#define MAX_VALUE 10000
#define MAX_BIT_NUMBER ((MAX_VALUE - 3) / 2)
#define SIZE (MAX_BIT_NUMBER / CHAR_BIT + 1)

unsigned character_offset(unsigned bit_number);
unsigned bit_offset(unsigned bit_number);
void set_bit(char bit_array[], unsigned bit_number);
void clear_bit(char bit_array[], unsigned bit_number);
void assign_bit(char bit_array[], unsigned bit_number, int value);
int test_bit(char bit_array[], unsigned bit_number);

int main()
{
    char sieve[SIZE];
    int number;
    int bit_number;
    char *sp;
    
    for(sp = sieve; sp < &sieve[SIZE];)
        *sp++ = 0xff;

    for(number = 3; number < MAX_VALUE; number += 2){
        bit_number = (number - 3) / 2;
        if(test_bit(sieve,bit_number) == 0)
            continue;
        while((bit_number += number) <= MAX_BIT_NUMBER){
//            printf("%d\t",test_bit(sieve,bit_number));
            clear_bit(sieve,bit_number);
//            printf("%d\n",test_bit(sieve,bit_number));
        }
    }

    printf("2\n");
    for(bit_number = 0,number = 3;number <= MAX_VALUE;
            bit_number += 1,number += 2)
        if(test_bit(sieve,bit_number))
            printf("%d\n",number);

    return 0;
}

/*為了方便,我給出一部分輸出*/
...
9917
9919
9921
9923
9925
9927
9929
9931
9933
9935
9937
/*這是函數的最后一部分輸出,可以清晰的看出9935 9925
等不是質數的數仍然還在函數中*/

/*在我注釋了兩行語句的地方,可以清楚的看到題目出了問題
在把打印函數注釋掉之后,僅僅打印注釋的兩條printf語句
可以看到下列結果*/
...
0    0
0    0
1    1
0    0
1    1
0    0
1    1
0    0
0    0
0    0
0    0
0    0
...
/*按理來說使用了clearbit函數之后應該只有兩種情況*/
0      0
1      0
然而現實卻只有
0      0
1      1
這兩種情況,意思是沒有發生任何改變

 

6.大質數是不是和小質數一樣多,換句話說,在50000和51000之間的質數是不是和1000000到1001000之間的質數一樣多?使用前面的程序計算0到1000之間有多少個質數,1000到2000之間有多少個質數?以此每隔1000類推,到1000000有多少個質數,每隔1000個數中質數的質量的數量呈什么趨勢?

answer:

#include <stdio.h>

#define MAX 1000001

int Eratosthenes(char [], int max);

int main()
{
    int i;
    int sum = 0;
    int count = 0;
    char prime[MAX];
    int max;
    for(i = 1; i < MAX; i++)
        prime[i] = 1;
    
    for(max = 1000; max < MAX; max += 1000){
        count = Eratosthenes(prime,max) - sum;
        printf("%d\t%d\n",max,count);
        sum += count;
    }
}

int Eratosthenes(char prime[], int max)
{
    int count = 0;
    int i = 0;
    int i_p;
    int j;
    int j_p;

    for(i = 1; i < max; i++)
        prime[i] = 1;
    
    for(i = 1; i < max; i++){
        if(prime[i] == 1){
            i_p = 2 * i + 1;
            for(j = 2 * i_p; j < max * 2 + 1; j += i_p)
                if(j % 2 == 1 && prime[(j-1)/2] == 1){
                    j_p = (j - 1)/2;
                    prime[j_p] = 0;
                    count++;
                }
        }
    }
    return count;
}

這個答案的混亂性看起來是沒有什么特點的,所以答案算得是平均數昂

for(max = 100000; max < MAX; max += 100000){
        count = Eratosthenes(prime,max);
        printf("%f\n",(float)max/count * 1000);
    }

這是改過之后的,運行結果如下:

1219.274292
1203.803955
1195.686035
1190.302612
1186.234009
1183.287231
1180.689331
1178.423584
1176.581421
1174.995605

規律是不是出來了?

當我想按照答案上把數字改成九個零那么高的精確度之后,編譯器很高興的告訴我,段核心錯誤,想要那么精確的數位的話,還是把位數組琢磨出來吧,然后運行,睡個覺,沒准醒來的時候你就能看到結果了,下面直接給出標准答案,可能也有些數學方法能夠證明出來吧,有興趣的自己去查查唄。

Range of Numbers Average # of primes per Thousand Number
1000000 - 2000000 70.435
2000000 - 3000000 67.883
3000000 - 4000000 66.33
4000000 - 5000000 65.367
5000000 - 6000000 64.336
6000000 - 7000000 63.799
7000000 - 8000000 63.129
8000000 - 9000000 62.712
9000000 - 10000000 62.09
10000000 - 20000000 60.603
20000000 - 30000000 58.725
30000000 - 40000000 57.579
40000000 - 50000000 56.748
50000000 - 60000000 56.098
60000000 - 70000000 55.595
70000000 - 80000000 55.132
80000000 - 90000000 54.757
90000000 - 100000000 54.45
100000000 - 200000000 53.175
200000000 - 300000000 51.734
300000000 - 400000000 50.84
400000000 - 500000000 50.195
500000000 - 600000000 49.688
600000000 - 700000000 49.292
700000000 - 800000000 48.932
800000000 - 900000000 48.63
900000000 - 1000000000 48.383


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM