目錄表

國立屏東大學 資訊工程學系 程式設計(二)

18. 高階指標應用


18.1 指標與字串

字串這一章中,我們已經介紹過兩種C語言的字串:字串陣列與字串指標,請參考下面的程式:

#include <stdio.h>
#include <string.h>
 
int main()
{
  char str[]="Hello";
  char *p;
  int i;
 
  printf("str at %p\n", str);
 
  for(i=0;i<strlen(str);i++)
  {
    printf("str[%d] at %p\n", i, &str[i]);
  }
 
  p = str;
 
  for(i=0;i<strlen(str);i++)
  {
    printf("*(p+%d)=str[%d]=%c at %p\n", i, i, *(p+i), p+i);
  }
 
  str[0]='h';
  *(p+3)='L';
 
  puts(str);
  puts(p);
 
  return 0;
}

在這個程式中,我們先宣告了一個字串陣列,然後讓指標p指向該字串,我們可以使用陣列與指標的方式來存取在記憶體中的這個字串。其實這兩種方式在記憶體中是使用同樣的配置,其差別只在於我們是以陣列的索引來存取,亦或是使用指標來存取。換句話說,一個字串陣列可以當成指標字串來使用,反之亦然。請參考下面的程式:

#include <stdio.h>
#include <string.h>
 
int main()
{
  char str[]="Hello";
  char *p;
 
  p = str;
 
  p[0]='H';
  *(str+3) = 'L';
 
  puts(str);
  puts(p);
 
  return 0;
}

18.2 動態配置字串

我們也可以使用「malloc()函式」或「calloc()函式」來動態地在記憶體中配置字串所需的空間,例如:

#define LEN 10;
 
char *str = malloc((LEN+1)*sizeof(char));
或是
char *str = malloc(LEN+1);

但是要特別注意的是,這種動態配置的字串,從其配置開始至程式結束,都會一直存在記憶體中,除非我們以「free()函式」將之釋放。為了在程式執行時,不要過度佔用記憶體,當我們不再需要該字串時,應該以下列程式碼將其釋放:

free(str);

如果要動態地宣告由多個字串所組成的陣列,又該如何做呢?請參考下面的例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define numString 10
#define LEN 20
 
int main()
{
  char *strs[numString];
  int i;
 
  for(i=0;i<numString;i++)
  {
    strs[i] = (char *)(malloc(LEN+1));
  }
 
  for(i=0;i<numString;i++)
  {
    printf("String %d = ", i+1);
    scanf(" %[^\n]", strs[i]);
  }
 
  for(i=0;i<numString;i++)
  {
    puts(strs[i]);
  }
 
  return 0;
}

我們也可以將上述程式,改成以指向指標的指標的方式來完成:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define numString 10
#define LEN 20
 
int main()
{
  char **strs;
  int i;
 
  strs = malloc(numString*sizeof(void *));
  for(i=0;i<numString;i++)
    *(strs+i) = malloc(LEN+1);
 
  for(i=0;i<numString;i++)
  {
    printf("String %d = ", i+1);
    scanf(" %[^\n]", *(strs+i));
  }
 
  for(i=0;i<numString;i++)
    puts(*(strs+i));
 
  return 0;
}

18.3 字串操作函式之實作

我們以下列範例示範以指標進行各式的字串操作:

計算字串長度

首先是實作計算字串長度的函式:

int strlen(const char *s)
{
   int n;
   for(n=0; *s!='\0';s++)
      n++;
 
   return n;
}

int strlen(const char *s)
{
   int n=0;
   for(; *s!='\0';s++)
      n++;
 
   return n;
}

int strlen(const char *s)
{
   int n=0;
   for(; *s; s++)
      n++;
 
   return n;
}

int strlen(const char *s)
{
   int n=0;
   for(; *s++; )
      n++;
 
   return n;
}

int strlen(const char *s)
{
   int n=0;
 
   while(*s++)
      n++;
 
   return n;
}

int strlen(const char *s)
{
   const char *p = s;
 
   while(*s)
      s++;
 
   return s-p;
}

字串串接

char *strcat(char *s1, const char *s2)
{
   char *p = s1;
   while(*p !='\0')
      p++;
   while(*s2 != '\0')
   {
      *p = *s2;
      p++;
      s2++;
   }
   *p = '\0';
   return s1;
}

char *strcat(char *s1, const char *s2)
{
   char *p = s1;
 
   while(*p)
      p++;
   while(*p++ = *s2++);
   return s1;
}

trim

設計一個trim()函式,將字串前與後的空白字元移除:

char *trim(char *s)
{
   char *f = s;
   char *t = s;
 
   while(*t !='\0')
   {
      if(*f==' ')
         f++;
      t++;
   }
 
   t--;
   while((*t ==' ')||(*t =='\n'))
   {
      t--;
   }
   t++;
   *t='\0';
 
   s = f;
   return s;
}

18.4 動態陣列

下面這個程式利用動態配置的方法,建立了一個陣列,並在執行時間改變其大小:

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
  int *data;
  int size, i;
 
  printf("Array Size=? ");
  scanf(" %d", &size);
 
  data = malloc(size*sizeof(int));
 
  for(i=0;i<size;i++)
    data[i]=i;
 
  // add 10 more numbers
  data = realloc(data, (size+10)*sizeof(int));
  for(i=0;i<size+10;i++)
  {
    data[i]=i;
  }
 
  // remove last 5 numbers
  data = realloc(data, (size+5)*sizeof(int));
 
  for(i=0;i<size+5;i++)
    printf("%d ", data[i]);
  printf("\n");
 
  return 0;
}

下面的例子,則是動態建置一個二維的陣列:

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
  int **data;
  int Row, Col;
  int i,j;
 
  Row=3;
  Col=2;
 
  data = malloc(sizeof(int *)*Row);
  for(i=0;i<Row;i++)
    data[i] = malloc(Col*sizeof(int));
 
  for(i=0;i<Row;i++)
    for(j=0;j<Col;j++)
      data[i][j]=i*Col+(j+1);
 
  for(i=0;i<Row;i++)
  {
    for(j=0;j<Col;j++)
      printf("%d ", data[i][j]);
    printf("\n");
  }
 
  return 0;
}

在這個例子中,「data」在語法上是一個指向整數的指標的指標(pointer to pointer),但在語意上是一個以指標來操作的陣列,其中每個陣列的元素為一個以指標來操作的陣列,也就形成了一個二維陣列。我們稱此種「雙指標」為「pointer to pointers」。

18.5 動態結構體

下面這個程式動態產生了一個Point的結構體:

#include <stdio.h>
#include <stdlib.h>
 
typedef struct
{
  int x;
  int y;
} Point;
 
void showPoint(Point p)
{
  printf("(%d,%d)\n", p.x, p.y);
}
 
int main()
{
  Point *p1 = malloc(sizeof(Point));
  p1->x=5;
  p1->y=10;
  showPoint(*p1);
  return 0;
}

我們也可以產生一個動態的陣列用以存放多個結構體:

#include <stdio.h>
#include <stdlib.h>
 
typedef struct
{
  int x;
  int y;
} Point;
 
void showPoint(Point p)
{
  printf("(%d,%d)\n", p.x, p.y);
}
 
#define LEN 10
 
int main()
{
  Point *ps = malloc(sizeof(Point)*LEN);
  int i;
 
  for(i=0;i<LEN;i++)
  {
    ps[i].x=i;
    ps[i].y=i;
    showPoint(ps[i]);
//    showPoint(*(ps+i));
  }
  return 0;
}

18.6 函式指標

函式指標(function pointer)為指向函式的指標,在C語言裡,若使用函式名稱但不接後續的括號,就會被視為是一個指向該函式的指標(也就是該函式在記憶體中的位址),請參考下例:

int foo(int f)
{
   return f*f;
}
 
int main()
{
   printf("Function foo at %p.\n", foo);
   printf("The address of Function foo is %p.\n", &foo);
   return 0;
}

請執行上述的程式,看看其結果。就如同陣列「int data[]」,其「data」與「&data」的值是一樣的,使用「foo」與「&foo」都是代表foo函式在記憶體中的位址,因此,我們可以宣告一個指標指向該位址:

int (*f)(int); //其中第一個int是函式的傳回值,第二個int則是引數

我們可以在程式中以下列程式碼,讓「f」指向「foo函式」,並加以呼叫:

f=foo;
 
printf("%d\n", f(3));
或是
printf("%d\n", (*f)(3));

這樣一來,在程式執行時,我們也可以讓指標動態地指向不同的函式,以完成不同的操作。請參考下面的程式:

#include <stdio.h>
#include <math.h>
 
int maximum(int d[], int n)
{ 
  int max=d[0];
  int i;
 
  for(i=1;i<n;i++)
    if(max<d[i])
      max=d[i];
  return max;
} 
 
int minimum(int d[], int n)
{ 
  int min=d[0];
  int i;
 
  for(i=1;i<n;i++)
    if(min>d[i])
      min=d[i];
  return min;
} 
 
int median(int d[], int n)
{ 
  double average=0.0;
  int i,med;
  double temp;
 
  for(i=0;i<n;i++)
  {
    average+=d[i];
  }
  average/=n;
 
  med=d[0];
  for(i=1;i<n;i++)
    if(abs(med-average) > abs(d[i]-average))
      med=d[i];
 
  return med;
} 
 
int findANumber( int (*func)(int d[], int s), int data[], int size)
{ 
  return (*func)(data,size);
} 
 
 
int main()
{ 
  int data[10] = { 113, 345, 23, 75, 923, 634, 632, 134, 232, 98 };
  int num;
 
  num = findANumber(maximum, data, 10);
  printf("The maximum is %d.\n", num);
  num = findANumber(minimum, data, 10);
  printf("The minimum is %d.\n", num);
  num = findANumber(median, data, 10);
  printf("The median is %d.\n", num);
 
  return 0;
}

我們也可以延伸此做法,設計宣告一個指向多個函式的指標陣列:

void (*funcs[])(void) = {insert, delete, update};
 
...
 
(*funcs[i])(); //呼叫第i個函式

18.7 結構體的彈性陣列成員

C99開始提供一個新的功能,允許我們為結構體設計彈性的陣列成員(Flexible Array Member),也就是未定義大小的陣列,但必須為所有成員的最後一個,且只能有一個。請參考下面的程式:

#include <stdio.h>
#include <stdlib.h>
 
struct vstring
{
  int len;
  char chars[];
  // char *chars;
};
 
 
int main()
{
  struct vstring *str = malloc(sizeof(struct vstring)+10);
  str->len=10;
 
  return 0;
}