C|函数参数的一些谜思、随想与解惑

函数是程序的基本构件,函数参数潜藏了函数定义与调用的诸多坑。

1 函数参数可以提高函数的通用性,但函数参数也不宜太多,不然太难使用了。

2 值传递可以保持主调函数和被调函数之间的独立性,没有副作用的函数也称为纯函数,这样安全性更高,这也是函数式编程语言所追求的。

3 址传递可以避免值传递的复制所产生的额外开销(overhead).

4 址传递可以让被调函数生产对主调函数的副作用,产生一种隐式输出的效果。

指针能够读写指向的目标对象的数据,被赋值一个合法地址后就有了指向,根据目标类型便可以进行指针的移动和数据的读写。

#include <stdio.h>
void func(int *p)
{
    *p = 11;
}
int main()
{
    int n;
    func(&n);
    printf("%d\n",n);
    getchar();
}

5 数组为什么是址传递?

因为数组名在C中是以指针存在的。数组名是一个具有常量性质的指针,做为一块内存n个相同数据类型的数据的基址,但元素的访问只需要用偏移基址即可。在一定的上下文中,数组名可以表示数组整体的指针,也可以是目标类型为数组元素的指针。

#include <stdio.h>
void func(int p[][4],int n)
{
    *(*(p+2)+2) = 11;
}
int main()
{
    int arr[3][4] = {0};
    int (*pp)[3][4] = &arr;  // 数组名与运算符&一起使用时,其目标类型为数组整体(int[3][4])
    int n = sizeof arr / sizeof *arr;// 数组名与运算符sizeof一起使用时,其目标类型为数组整体
    func(arr,n);// 除了上述两种情况,数组名的目标类型是元素类型,其自身类型为int(*)[4]
    printf("%d\n",arr[2][2]);
    getchar();
}

6 数组指针做参数为什么需要多一个参数来传递数组的长度信息?而结构体不需要?

因为数组指针只是一个指向数组首元素的指针,包含了目标对象的类型信息(包括元素的长度信息),但缺乏需要处理多少个元素的信息(数组长度信息)。结构体可以传值也可以传址,不管哪种方式,其对象整体和元素的内存长度和类型信息都是确定的。

#include <stdio.h>
#include <malloc.h>
void arrPrint(int arr[],int n)
{
    for(int i=0;i<n;i++)
        printf("%d ",*(arr+i));
    printf("\n");
}
void strcpy(char* dst,const char* src)
{
    while(*dst++=*src++);
}
struct student{
    char id[11];
    char name[32];
    double score[3];
};
void demo(struct student *stu)
{
    strcpy(stu->id,"123");
    stu->score[0] = 88;
}
int main()
{
    int arr[] = {3,1,2,5};
    int n = sizeof arr / sizeof *arr;
    arrPrint(arr,n); // 3 1 2 5
    struct student stu;
    demo(&stu);
    printf("%s\n",stu.id);
    getchar();
}

7 数组指针做参数需要一个额外的数组长度的参数,而为什么字符串指针做参数不需要?

因为C风格的字符串使用了一个标志值‘\0’做为结束标志,而strlen()和其它的一些字符串的处理函数都是以'\0'为标志的。

char * strcat (
        char * dst,
        const char * src
        )
{
        char * cp = dst;
        while( *cp )
                cp++;                   /* find end of dst */
        while( *cp++ = *src++ ) ;       /* Copy src to end of dst */
        return( dst );                  /* return dst */
}
char* toUpper(const char* str)
{
    const char *p = str;
    int n=0;
    while(*p++)n++;
    char *news = (char*)malloc(sizeof(char)*n);
    for(int i=0;i<n;i++){
        news[i] = str[i] & ~('a'-'A');
    }
    news[i]='\0';
    return news;
}

8 为什么void*做参数需要更多的参数,例如qsort()函数?

因为void是一个不完全类型,但使用void*可以起到泛型的作用,如果要处理一个数组,void只是提供了一个基址,目标类型待定,在最终读写这块内存时,类型需要确定,qsort的做法是通过额外的元素的数量、类型的内存长度做参数去按字节(char*)操作的。

#include <stdio.h>      /* printf */
#include <stdlib.h>     /* qsort, bsearch, NULL */
int compareints (const void * a, const void * b)
{
    return ( *(int*)a - *(int*)b );
}
int values[] = { 50, 20, 60, 40, 10, 30 };
int main ()
{
    int * pItem;
    int key = 40;
    qsort (values, 6, sizeof (int), compareints);
    pItem = (int*) bsearch (&key, values, 6, sizeof (int), compareints);
    if(pItem!=NULL)
        printf ("%d is in the array.\n",*pItem);
    else
        printf ("%d is not in the array.\n",key);
    return 0;
}

以下两个库函数也是逐字节操作:

size_t fread(void *buffer, size_t size, size_t count FILE *stream);
size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);

9 递归有时为什么使用更多的参数?

因为递归参数可以在递归调用时形成一种迭代关系,并且其值保存在栈帧上。

#include <stdio.h>
int binsearch(int a[], int x, int low, int high)
{
    int mid;
    if(low > high)
        return -1;
    mid = (low + high) / 2;
    if(x == a[mid])
        return (mid);
    else
        if(x < a[mid])
            binsearch(a, x, low, mid - 1);
        else
            binsearch(a, x, mid + 1, high);
}
int main()
{
    int arr[] = {1,3,5,7,9,12,15,19};
    int n = sizeof arr / sizeof *arr;
    int idx = binsearch(arr,9,0,n);
    printf("%d\n",idx); // 4
    getchar();
}

10 为什么要对主调函数内一个一级指针产生产生副作用需要给被调函数传递一个二级指针?

给一个变量产生副作用需要将这个变量的地址传递给被调函数,普通变量的地址可以赋值给一个一级指针。一个一级指针的地址只能存储在一个二级指针中。在被调函数的函数体内,对主调函数的副作用必须是指针变量解引用做左值,一级指针的解引用就是目标对象的操作,二级指针的解引用也是对目标对象的操作,但操作的还是一个地址。

void insert(int key, struct node **leaf)
{
    if( *leaf == 0 )
    {
        *leaf = (struct node*) malloc( sizeof( struct node ) );
        (*leaf)->key_value = key;
        /* initialize the children to null */
        (*leaf)->left = 0;    
        (*leaf)->right = 0;  
    }
    else if(key < (*leaf)->key_value)
        insert( key, &(*leaf)->left );
    else if(key > (*leaf)->key_value)
        insert( key, &(*leaf)->right );
}

11 被调函数的指针参数要对主调函数产生副作用需要在被调函数的函数体内用解引用做左值,为什么数组指针和结构体指针做参数时有时看到解引用操作?

对于数组来说,一次下标引用[]的操作也相当于一次解引用操作。对于结构体来说,操作符->相当于(*).操作。

#include <stdio.h>
#include <malloc.h>
int arr[3][4];
struct MyStruct{
    int a;
    int b;
}ms;
void func(int arr[][4],struct MyStruct* ms)
{
    arr[2][3] = 12;     // side-effect;
    *(*(arr+2)+3) = 12; // side-effect;  
    arr = (int(*)[4])malloc(sizeof(int)*3*4);               // no side-effect;
    ms->a = 12;     // side-effect;
    (*ms).a = 12;   // side-effect;
    ms = (struct MyStruct*)malloc(sizeof(struct MyStruct)); // no side-effect;
}
main()
{
    func(arr,&ms);
    printf("%d %d\n",arr[2][3],ms.a);//12 12
    getchar();
}

-End-

原文链接:,转发请注明来源!