C程序设计语言--第五章:指针与数组
為什么80%的碼農都做不了架構師?>>> ??
指針是一種保存變量地址的變量.
5.1 指針與地址
通常的機器都有一系列連續編號或編址的存儲單元,這些存儲單元可以單個進行操縱,也可以連續成組的方式操縱.指針是能夠存放一個地址的一組存儲單元(通常是兩個或四個字節).
一元運算符&可用于取一個對象的地址.
p = &c; 將把c的地址賦值給變量p,我們稱p為"指向"c的指針.地址運算符&只能應用于內存中的對象,即變量與數組元素.它不能作用域表達式,常量或register類型的變量.但是,如果我們非要讀取地址100上的數據呢?則可以用void *來顯式的聲明指針進行賦值.
#include <stdio.h>int main(void) {int *p = ( void * )100;printf("%d\n", p );printf("%d\n", *p );return 0; } p的值為100,但是*p的值卻是未定義的.一元運算符*是間接尋址或間接引用運算符.當它作用于指針時,將訪問指針所指向的對象.
int x = 1; int y = 2; int z[ 10 ]; int *ip = NULL;ip = &x; /* ip現在指向x */ y = *ip; /* y的值現在為1 */ *ip = 0; /* x的值現在為0 */ ip = &z[ 0 ]; /* ip現在指向z[ 0 ]*/ 我們應該注意:指針只能指向某種特定類型的對象,也就是說,每個指針都必須指向某種特定類型的數據類型(一個例外的情況是指向void類型的指針可以存放指向任何類型的指針,但它不能間接引用其自身.)如果iq,ip均是指向int的指針,那么
iq = ip; 將把ip中的值拷貝到iq中,這樣,指針iq也將指向ip指向的對象.這種方法通常用于建立臨時變量iq來代替ip進行運算.5.2 指針與函數參數
由于C語言是以傳值的方式將參數值傳遞給被調用函數,因此,被調用函數不能直接修改主調用函數中變量的值.但是,如果我們把變量的地址傳遞進去,無法修改地址的情況下修改地址上存儲的變量的值,則可以達到類似修改了參數值的效果.
下列swap函數無任何效果:
void swap( int x, int y ) {int temp = x;x = y;y = temp; } 但是,我們聲明參數為指針,則可以達到效果: void swap( int *px, int *py ) {int temp = *px;*px = *py;*py = temp; } 書上有個例子,讀取輸入流中的數字,但是并不通用,因為你會發現:當輸入流中加入一個非空格或者數字的字符(比如字母)的時候,那么程序就這樣死循環了----因為它會不斷的getch這個字母,ungetch這個字母: #include <ctype.h> #include <stdio.h>#define SIZE 128 char buffer[ SIZE ]; int index;int getch( void ) {return index > 0 ? buffer[ --index ] : getchar(); } void ungetch( int iValue) {if ( index >= SIZE ){printf("error: buffer is full\n");}else{buffer[ index++ ] = iValue;} }int getint( int *pn ) {int c, sign;while ( isspace( c = getch() ) ){;}if ( !isdigit( c ) && c != EOF && c != '+' && c != '-' ){ungetch( c );return 0;}sign = ( c == '-' ) ? -1 : 1;if ( c == '+' || c == '-' ){c = getch();}for ( *pn = 0; isdigit( c ); c = getch() ){*pn = 10 * *pn + ( c - '0' );}*pn *= sign;if ( c != EOF ){ungetch( c );}return c; }int main(void) {int n;int array[10];for ( n = 0; n < 10 && getint( &array[ n ] ) != EOF; n++ );for ( n = 0; n < 10; n++ ){printf("%d--", array[ n ] );}return 0; } 程序輸出:
習題5-1:
對于此習題,個人并不推薦將'+'或者'-'寫回到輸入流中,而是應該用某種技巧忽略非數字的符號來達到目的,否則當寫回輸入流后,會導致數組的n位置變成了垃圾值:
#include <ctype.h> #include <stdio.h>#define SIZE 128 char buffer[ SIZE ]; int index;int getch( void ) {return index > 0 ? buffer[ --index ] : getchar(); } void ungetch( int iValue) {if ( index >= SIZE ){printf("error: buffer is full\n");}else{buffer[ index++ ] = iValue;} }int getint( int *pn ) {int c, sign;while ( isspace( c = getch() ) ){;}if ( !isdigit( c ) && c != EOF && c != '+' && c != '-' ){ungetch( c );return 0;}sign = ( c == '-' ) ? -1 : 1;if ( c == '+' || c == '-' ){int temp = c;c = getch();if ( !isdigit( c ) ){ungetch( temp );return 0;}}for ( *pn = 0; isdigit( c ); c = getch() ){*pn = 10 * *pn + ( c - '0' );}*pn *= sign;if ( c != EOF ){ungetch( c );}return c; }int main(void) {int n;int array[10];for ( n = 0; n < 10 && getint( &array[ n ] ) != EOF; n++ );for ( n = 0; n < 10; n++ ){printf("%4d\n", array[ n ] );}return 0; } 程序輸出:
即途中的a,b,c,d均占用了數組的一個元素空間.我們可以簡單的忽略掉這些字母:
將if判斷換成while判斷即可:
if ( c == '+' || c == '-' ){c = getch();while ( !isdigit( c ) ){c = getch();} } 則程序輸出:
習題5-2:
對于習題5-2,我們進行改進,即將輸入的字符串中的浮點數提取出來,并將其一一輸出:
#include <stdio.h> #include <ctype.h> #include <math.h>void showFloatNumber( char *line ) {char *str = line;char *temp = str;while ( '\n' != *line ){while ( !isdigit( *line ) ){line++;}while ( isdigit( *line ) || '.' == *line ){*str++ = *line++;}*str = '\0';str = temp;printf("%f\n", atof( str ) );} }int main(void) {char buf[128];while ( NULL != fgets( buf, 128, stdin ) ){showFloatNumber( buf );}return 0; } 從這里可以看出,指針的作用真的非常的強大.程序輸出:
5.3 指針與數組
pa為數組,則
pa[ i ]<==>*( pa + i ) 簡而言之,一個通過數組和下標實現的表達式可等價的通過指針和偏移量實現.但是,數組名和指針之間有一個不同之處:指針是一個變量.因此,在C語言中,語句pa = a和pa++都是合法的.但數組名不是變量,因此,類似于a = pa和a++形式的語句是非法的.
當把數組名傳遞給一個函數時,實際上傳遞的是該數組第一個元素的地址.在被調用函數中,該參數是一個局部變量,因此,數組名參數必須是一個指針,也就是一個存儲地址值的變量.
#include <stdio.h>char* my_strlen( char *s ) {int n;for ( n = 0; *s != '\0'; s++ ){n++;}return s; }int main(void) {char arr[] = "hello";printf("%s\n", my_strlen( arr ) );printf("%s\n", arr );return 0; } 函數進行操作的s的副本,故這段程序輸出的是:
注意:第一個輸出為空('\0');
我們不能改變my_strlen中s的地址!!!所以s++并為對實參s有任何的影響,但我們可以改變其內容:
#include <stdio.h>char* my_strlen( char *s ) {char *temp = s;while ( '\0' != *s ){*s++ = 'h';}*s = '\0';s = temp;return s; }int main(void) {char arr[] = "hello";printf("%s\n", my_strlen( arr ) );printf("%s\n", arr );return 0; } 程序輸出:
來對"我們不能改變s的地址"做個解釋:這里不是說改變就有錯誤,而是你改變s的地址,改的是副本的地址,而不是原來實參的地址:
#include <stdio.h>//char arr[] = "world"; char* my_strlen( char *s ) {char arr[] = "world";char *p = arr;s = p;return s; }int main(void) {char arr[] = "hello";printf("%s\n", my_strlen( arr ) );printf("%s\n", arr );return 0; } 程序輸出:
之所以第一個字符串為亂碼是因為s指向的是一個臨時的局部變量,當程序從函數中返回的時候,這個局部變量已經銷毀了....
5.4 地址算術運算
我們來編寫類似于malloc和free的堆棧版本alloc和afree函數,進行地址算術運算的討論:
#define ALLOCSIZE 10000static char allocbuf[ ALLOCSIZE ];static char *allocp = allocbuf;char *alloc( int n ) {if ( allocbuf + ALLOCSIZE - allocp >= n ){allocp += n;return allocp - n;}return 0; }void afree( char *p ) {if ( p >= allocbuf && p < allocbuf + ALLOCSIZE ){allocp = p;} } 指針與整數之間不能相互轉換,但0是例外:常量0可以賦值給指針,指針也可以和常量0進行比較.但是,指向不同數組的元素的指針之間的算術或比較運算沒有定義(這里有個特例:指針的算術運算中可使用數組最后一個元素的下一個元素的地址).我們可以通過指針的地址運算來改寫strlen函數:
int strlen( char *s ) {char *p = s;while ( '\0' != *p ){p++;}return p - s; } 這里要注意:p最后的值是'\0',所以只要返回p - s, 而不是返回p - s + 1.有效的指針運算包括相同類型指針之間的賦值運算:指針同整數之間的加法或減法運算:指向相同數組中元素的兩個指針間的減法或比較運算;將指針賦值為0或指針與0之間的比較運算.其他所有形式的指針運算都是非法的,例如兩個指針間的加法,乘法,出發,移位或屏蔽運算;指針同float或double類型之間的加法運算;不經強制類型轉換而直接將指向一種類型對象的指針賦值給指向另一種類型對象的指針的運算(兩個指針之一是void *類型的情況除外).
5.5 字符指針與函數
字符串常量是一個字符數組.例如:
"I am a string" 在字符串的內部表示中,字符數組以空字符'\0'結尾.字符串常量可通過一個指向其第一個元素的指針訪問. char *pmessage; pmessage = "now is the time"; 將把一個指向字符數組的指針賦值給pmessage.該過程并沒有進行字符串的復制,而只是涉及到指針的操作.C語言沒有提供將整個字符串作為一個整體進行處理的運算符.下面兩個定義之間有很大的差別:
char amessage[] = "hello world";/*定義一個數組*/ char *pmessage = "hello world";/*定義一個指針*/ 上述聲明中,amessage是一個僅僅足以存放初始化字符串以及空字符'\0'的一維數組.數組中的單字符可以進行修改,但amessage始終指向一個存儲位置.另一方面,pmessage是一個指針,其初值指向一個字符串常量,之后它可以被修改以指向其他地址.但如果試圖修改字符串的內存,結果是沒有定義的. #include <stdio.h>int main(void) {char amessage[] = "hello world";char *pmessage = "hello world";amessage[ 2 ] = 'm';pmessage[ 2 ] = 'm';printf("%s\n", amessage );printf("%s\n", pmessage );return 0; } 程序出現未定義行為,因為pmessage[ 2 ] = 'm'本身是未定義的.以下兩個有用的函數來討論數組和指針方面的關聯:
strcpy數組的實現:
void strcpy( char *s, char *t ) {int i;i = 0;while ( ( s[ i ] = t[ i ] ) != '\0' ){i++;} } 指針實現: void strcpy( char *s, char *t ) {while ( *s++ = *t++ ); } strcmp數組的實現: int strcmp( char *s, char *t ) {int i;for ( i = 0; s[ i ] == t[ i ]; i++ ){if ( '\0' != s[ i ] ){return 0;}}return s[ i ] - t[ i ]; } 指針實現: int strcmp( char *s, char *t ) {for ( ; *s == *t; s++, t++ ){if ( '\0' == *s ){return 0;}}return *s - *t; } 這里不能將for循環改寫為: while ( *s++ == *t++ ) 是因為當尋找到不相等的元素的時候,自增操作導致return回去的是不相等字符的下一個元素之差.習題5-3:
void strcat( char *s, char *t ) {while ( '\0' != *s ){s++;}while ( *s++ = *t++ ){;} } 習題5-4: int strend( char *s, char *t ) {char *temp = t;while ( '\0' != *s ){s++;}while ( '\0' != *t ){t++;}while ( temp <= t ){if ( *t != *s ){return 0;}t--;s--;}return 1; } 習題5-5: #include <stdio.h>void strncpy( char *s, char *t, int n ) {while ( n-- && ( *s++ = *t++ ) ) //&&的優先級高于=,所以要加上括號;*s = '\0'; }void strncat( char *s, char *t, int n ) {while ( '\0' != *s ){s++;}while ( ( *s++ = *t++ ) && n-- );*s = '\0'; }int strncmp( char *s, char *t, int n ) {for ( ; ( *s == *t ) && n--; s++, t++ ){if ( '\0' != *s ){return 0;}}return *s - *t; }int main(void) {char s[128] = "hello";char t[] = "helld";strncpy( s, t, 4 );printf("%s\n", s );strncat( s, t, 5 );printf("%s\n", s );printf("%d\n", strncmp( s, t, 3 ) );return 0; } 程序輸出:
習題5-6:
getline函數:
int getline( char *s, int lim ) {int ch;char *temp = s;while ( --lim > 0 && ( ch = getchar() ) != EOF && ch != '\n' ){*s++ = ch;}if ( '\n' == ch ){*s++ = ch;}*s = '\0';return s - temp; } atoi函數: #include <stdio.h> #include <stdlib.h> #include <ctype.h>int my_atoi( char *s ) {int n = 0;int sign = 1;while ( isspace( *s ) ){s++;}if ( '-' == *s || '+' == *s ){sign = ( '-' == *s ) ? -1 : 1;s++;}while ( isdigit( *s ) ){n = 10 * n + ( *s - '0' );s++;}return sign * n; } int main(void) {printf("%d\n", my_atoi("-12345") );return 0; } 程序輸出:
itoa函數 + reverse函數:
#include <stdio.h> #include <stdlib.h> #include <ctype.h>void reverse( char *s ) {char *temp = s;while ( '\0' != *temp ){temp++;}temp--;while ( s < temp ){char ch = *s;*s++ = *temp;*temp-- = ch;} }void my_itoa( int n, char *s ) {char *temp = s;int sign = 0;if ( n < 0 ){n = -n;sign = 1;}while ( n / 10 ){*temp++ = n % 10 + '0';n /= 10;}*temp++ = n + '0';if ( sign ){*temp++ = '-';}*temp = '\0';reverse( s ); }int main(void) {char str[128];my_itoa( -12345, str );printf("%s\n", str );return 0; } 程序輸出:
strindex函數:
#include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h>int strindex( char *s, char *t ) {char *tempForS = s;char *tempForT = t;int len = strlen( t );while ( '\0' != *s ){while ( *t == *s ){t++;s++;}if ( '\0' == *t ){return s - tempForS - len;}if ( '\0' == *s ){return -1;}s++;t = tempForT;}return -1; }int main(void) {char str[] = "hello world i love this world";char dst[] = "this";printf("%d\n", strindex( str, dst ) );return 0; } 程序輸出:
getop函數:
int getop( char *s ) {int c;while ( ( *s = c = getch() ) == ' ' || c == '\t' );s++;*s = '\0';if ( !isdigit( c ) && c != '.' ){return c;}if ( isdigit( c ) ){while ( isdigit( *s = c = getch() ) ){s++;}}if ( '.' == c ){while ( isdigit( *s = c = getch() ) ){s++;}}*s = '\0';if ( c != EOF ){ungetch( c );}return NUMBER; } 5.6 指針數組以及指向指針的指針 #include <stdio.h> #include <string.h>#define MAXLINES 5000char *lineptr[ MAXLINES ];int readlines( char *lineptr[], int nlines ); void writelines( char *lineptr[], int nlines );void qsort( char *lineptr[], int left, int right );int main(void) {int nlines;if ( ( nlines = readlines( lineptr, MAXLINES ) ) >= 0 ){qsort( lineptr, 0, nlines - 1 );writelines( lineptr, nlines );return 0;}else{printf("error: input too big to sort\n");return 1;} }#define MAXLEN 1000 int getline( char *, int ); char *alloc( int );int readlines( char *lineptr[], int maxlines ) {int len, nlines;char *p, line[ MAXLEN ];nlines = 0;while ( ( len = getline( line, MAXLEN ) ) > 0 ){if ( nlines >= maxlines || ( p = alloc( len ) ) == NULL ){return -1;}else{line[ len - 1 ] = '\0';strcpy( p, line );lineptr[ nlines++ ] = p;}}return nlines; }void writelines( char *lineptr[], int nlines ) {while ( nlines-- > 0 ){printf("%s\n", *lineptr++ );} }void qsort( char *v[], int left, int right ) {int i, last;void swap( char *v[], int i, int j );if (left >= right ){return;}swap( v, left, ( left + right ) / 2 );last = left;for ( i = left + 1; i <= right; i++ ){if ( strcmp( v[ i ], v[ left ] ) < 0 ){swap( v, ++last, i );}}swap( v, left, last );qsort( v, left, last - 1 );qsort( v, last + 1, right ); }void swap( char *v[], int i, int j ) {char *temp;temp = v[ i ];v[ i ] = v[ j ];v[ j ] = temp; } 5.7 多維數組我們考慮一個日期轉換的問題:把某月某日這種日期表示形式轉換為某年中的第幾天的表示形式,反之亦然.
習題5-8:
static char daytab[ 2 ][ 13 ] = {{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31,30, 31},{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31,30, 31} }; int day_of_year( int year, int month, int day ) {if ( year <= 0 || month < 1 || month > 12 || day < 0 || day > 31 ){return -1;}int i, leap;leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;for ( i = 1; i < month; i++ ){day += daytab[ leap ][ i ];}return day; }void month_day( int year, int yearday, int *pmonth, int *pday ) {int i, leap;leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;if ( leap && yearday > 366 ){return;}else if ( yearday > 365 ){return;}for ( i = 1; yearday > daytab[ leap ][ i ]; i++ ){yearday -= daytab[ leap ][ i ];}*pmonth = i;*pday = yearday; } 5.8 指針數組的初始化 char *month_name( int n ) {static char *name[] = {"Illegal month","January", "February", "March","April", "May", "June","July", "August", "September", "October", "November", "December"};return ( n < 1 || n > 12 ) ? name[ 0 ] : name[ n ]; } 5.9 指針與多維數組習題5-9:
static char daytab[ 2 ][ 13 ] = {{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31,30, 31},{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31,30, 31} }; int day_of_year( int year, int month, int day ) {int i, leap;char *p;leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;p = daytab[ leap ];while ( --month ){day += *++p;}return day; }void month_day( int year, int yearday, int *pmonth, int *pday ) {int i, leap;char *p;leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;p = daytab[ leap ];while ( yearday > *++p ){yearday -= *p;}*pmonth = p - *( daytab + leap );*pday = yearday; }int main(void) {int month;int day;int newday = day_of_year( 2013, 10, 1 );month_day( 2013, newday, &month, &day );printf("%d--%d\n", month, day );return 0; } 程序輸出:
5.10 命令行參數
調用主函數main時,它帶有兩個參數.第一個參數argc的值表示運行程序時命令行中參數的數目;第二個參數argv是一個指向字符串數組的指針,其中每個字符串對應一個參數.且ANSI標準要求argv[ argc ]的值必須為一個空指針.
程序echo的作用是將命令行參數進行回顯.比如我們輸入echo hello, world 則打印出hello, world
程序echo的第一個版本將argv看成是一個字符指針數組:
#include <stdio.h>int main( int argc, char *argv[] ) {int i;for ( i = 1; i < argc; i++ ){printf("%s%s", argv[ i ], ( i < argc - 1 ) ? " " : "" );}printf("\n");return 0; } 第二個版本則是指針的指針: #include <stdio.h>int main( int argc, char *argv[] ) {while ( --argc > 0 ){printf("%s%s", *++argv, ( argc > 1 ) ? " " : "" );}printf("\n");return 0; } 習題5-10: #include <stdio.h>int main(int argc, char *argv[] ) {int buf[ 128 ];int index = -1;int result;int i;for ( index = 0; index < 128; index++ ){buf[ index ] = 0;}index = -1;while ( --argc > 0 ){++argv;if ( '+' == ( *argv )[ 0 ] ){//我不知道為什么不能用"+" == *argv進行比較result = buf[ index ];index--;buf[ index ] += result;}else if ( '-' == ( *argv )[ 0 ] ){result = buf[ index ];index--;buf[ index ] -= result;}else if ( '*' == ( *argv )[ 0 ] ){result = buf[ index ];index--;buf[ index ] *= result;}else if ( '/' == ( *argv )[ 0 ] ){result = buf[ index ];if ( 0 == result ){printf("error: zero to divisor\n");break;}index--;buf[ index ] /= result;}else{buf[ ++index ] = atoi( *argv );}}printf("%d\n", buf[ 0 ] );return 0; } 程序輸出:
習題5-11:
信心被一點一點擊垮了.....根本不知道如何下手..........
轉載于:https://my.oschina.net/voler/blog/165515
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的C程序设计语言--第五章:指针与数组的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java之UUID生成
- 下一篇: android 设置定位服务,Andro