c语言函数与指针(完)

内容概要

  一、函数的传参的方式

  二、返回值为指针的函数(简称指针函数)

  三、函数指针

  四、可变长参数的函数

 

1、函数的传参方式

  -值传入

#include <stdio.h>

void swap(int,int);

void swap(int x, int y){  //定义了交换函数
    int temp;
    printf("in swap x:%d , y:%d\n", x, y);
    temp = x;
    x = y;
    y = temp;
    printf("in swap x:%d , y:%d\n", x, y);
    
}

int main(){
    int x = 3, y = 5;
    
    printf("in main x:%d , y:%d\n", x, y);
    swap(x,y);
    printf("in main x:%d , y:%d\n", x, y);
    
    return 0;
}

  执行结果

in main x:3 , y:5
in swap x:3 , y:5
in swap x:5 , y:3
in main x:3 , y:5  //函数内交换了x,y的值,但是并没有改变main函数内的x,y值

 

  -指针(地址)传入

#include <stdio.h>

void swap(int,int);

void swap(int *x, int *y){
    int temp;
    printf("in swap x:%d , y:%d\n", *x, *y);
    temp = *x;
    *x = *y;
    *y = temp;
    printf("in swap x:%d , y:%d\n", *x, *y);
    
}

int main(){
    int x = 3, y = 5;
    
    printf("in main x:%d , y:%d\n", x, y);
    swap(&x,&y);
    printf("in main x:%d , y:%d\n", x, y);
    
    return 0;
}

  执行结果

in main x:3 , y:5
in swap x:3 , y:5
in swap x:5 , y:3
in main x:5 , y:3  //修改了main函数下的x,y

 

2、返回值为指针的函数

   注意:不要返回指向函数内变量的指针或者返回数组

    -尝试返回数组

#include <stdio.h>

char *pa();

char *pa(){
    char str[] = "hello world";
    
    return str;
}


int main(){
    char *str;
    str = pa();
    printf("%s\n",str);
    return 0;
}

    -尝试返回指针

#include <stdio.h>

char *pa();

char *pa(){
    char str[] = "hello world";
    char *pstr = str;
    return pstr;
}

int main(){
    char *result;
    result = pa();
    printf("%s\n",result);
    return 0;
}

    -尝试返回一个普通变量(这样是可以的)

#include <stdio.h>

int number();

int number(){
    return 1024;
}

int main(){
    int num;
    num = number();
    printf("%d\n",num);
    return 0;
}

  分析:函数返回值是值返回,就像传参时传入的是值,而不是变量内存地址

    返回一个指针或者数组

  验证:因为函数返回的指针指向了一个将要被销毁的值,那么就会出错;如果指向一个不会被销毁的量

#include <stdio.h>

char str[] = "hello world";  //定义了一个全局变量

char *number();

char *number(){
    char *pstr = str;
    return pstr;  //返回的地址指向了全局变量str
}

int main(){
    char *result;
    result = number();
    printf("%s\n",result);
    return 0;
}

    c语言这样的特性与python中的闭包函数不同

      python内函数如果使用了外函数的变量,外函数将内函数返回。那么外函数的变量的生命周期将会被延长

 

3、函数指针

  在python中,函数名称是可以作为变量使用的,它也有自己的内存地址

def func():
    print("这是一个函数")

# 功能字典
func_dic = {"1":func}
func_dic['1']()

# 将func的地址赋值给func2
func2 = func

# 改变func存储的地址
# func = 10

print(func)

 

  c语言函数其实也有自己的内存地址,那么类似于数组指针,就可以使用指针获取函数的内存地址,如果再告诉编译器指针指向的地址中的数据如何使用那么就是函数指针了

#include <stdio.h>

char *my_str();

char *my_str(){
    return "hello world";
}

int main(){
    char *(*pa)();  //char *指的是函数的返回值类型;(*p)保证了这次定义的变量为指针;()进一步说明指针指向的数据是一个函数
    pa = my_str;
    
    printf("pa:%p  my_str:%p\n",pa,my_str);
    return 0;    
}

  小练习:区分char *my_str()和char (*my_str)();

    区分的关键是运算符的优先级

      前者()优先级比*高,所以它是一个函数——my_str(),返回值类型为char *;

      后者两个小括号优先级左边比右边高,所以它是一个指针变量*my_str,并且是一个返回值为char类型的函数指针

 

  -将函数作为参数传入另外一个函数中

#include <stdio.h>

int add(int,int);
int sub(int,int);
int calc(int (*pfunc)(int,int),int a,int b);

int add(int a,int b){
    return a+b;
}

int sub(int a,int b){
    return a-b;
}

int calc(int (*pfunc)(int,int),int a,int b){  //将函数指针作为形式参数
    return pfunc(a,b);
}

int main(){
    char c;
    int a,b,result;
    
    printf("please input a phrase:(for example:1+2)");
    scanf("%d%c%d",&a,&c,&b);
    
    switch (c){
        case '+':result = calc(add,a,b);break;  //传入一个函数地址
        case '-':result = calc(sub,a,b);break;
    };
    
    printf("%d %c %d = %d",a,c,b,result);
    return 0;
}

    对比python,c语言要想传入函数的过程真的繁琐

      python无参装饰器模板

def outer(func):
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    return wrapper

  -同样的,将函数作为返回值返回

#include <stdio.h>

int add(int, int);
int sub(int, int);
// int (*)(int, int) calc(char);  这样定义是绝对不可以的,原本意图是定义一个返回值为(整形,且有两个整形参数的)一个(本身有1个char类型的参数)的函数;
int (*calc(char))(int, int);

int add(int a, int b){
    return a+b;
}

int sub(int a, int b){
    return a-b;
}

int (*calc(char c))(int, int){
    
    switch (c){
        case '+':return add;  //返回的是一个函数
        case '-':return sub;
        default: 0;
    }
}

int main(){
    int a,b,total;
    char sign;
    int (*result)(int, int);
    
    printf("please input a phrase(for example: 2+20):");
    scanf("%d%c%d",&a,&sign,&b);
result
= calc(sign); total = result(a,b); printf("%d\n",total); return 0; }

  int (*calc(char))(int,int);  解析

    1、编译器从左到右读取代码,直到遇到分号停止;

    2、按照运算符优先级解析代码,这里发现有多个括号,那就按照从左到右的循序解析

    3、首先解析  *calc(char)  ()优先级比*高,所以继续解析calc(char)  编译器理解为定义了一个calc函数,函数参数为一个char类型参数,*calc(char)编译器暂时不会处理

    4、接下来编译器要知道函数的返回值类型   int * (int, int)就是函数的返回值类型——函数指针

    就像  int *pa[4];  编译器先解析pa[4],理解为一个数组,int * 为数组元素的类型

 

4、可变长参数的函数

  想要使用可变参数函数,需要导入stdarg.h的库,库中定义了关于使用可变参数的宏定义

    ...在函数定义时,放在形参最后,表示使用可变长参数

    va_list

      va_list 类型定义一个参数指针

    va_start(参数指针,离无名参数表最近的有名参数)

      使用va_start时,函数形参必须至少要有一个有名参数

      功能:将参数指针指向有名参数后的第一个参数的内存地址

    va_arg(参数指针,下一个参数的数据类型)

      将参数指针指向下一个无名参数

    va_end

      回收相关资源

 

  书上的例子:实现一个简单的printf功能

#include <stdio.h>
#include <stdarg.h>

void minprintf(char *, ...);

void minprintf(char *str, ...){
    va_list parg;
    int i_val;
    double f_val; //接受到的可变参数类型也应该为double双精度浮点型
    char *pstr, *s_val;
    
    pstr = str;
    
    va_start(parg, str);  //将参数指针指向有名参数char *str后一个无名参数
    for (pstr = str; *pstr; pstr++){
        if (*pstr != '%'){
            putchar(*pstr);
            continue;
        }
        switch (*++pstr){
            case 'd':
                i_val = va_arg(parg, int);
                printf("%d", i_val);
                break;
            case 'f':
                f_val = va_arg(parg, double); //注意,可变参数中的float类型使用double代替,也就是说你往可变参数中传入单精度浮点型,使用va_arg获取时,应该指定为double类型
                printf("%f", f_val);
                break;
            case 's':
                s_val = va_arg(parg, char *); //这样能执行,不影响下一个参数的获取,说明传入char *时,指针指向的内存地址不是简单加一,都是会继续遍历,找到\0?
                printf("%s", s_val);
                break;
            default:
                putchar('%');
                putchar(*pstr);
                break;
        }
    }
    
    va_end(parg);
}

int main(void){
    
    minprintf("hello world%f%s", 22.2, "hello world", 20);
    
    return 0;
}

    利用函数宏才能做到传入一个int的实参;普通函数要想传入int是不行的,因为int形参类型无法定义

      对于case:'s'分支,书上是这样写的

            case 's':
                for (s_val = va_arg(pang, char *); *s_val; s_val++){
                    putchar(*s_val);
                }
                break;

***完***

posted @ 2021-03-14 00:22  口乞厂几  阅读(101)  评论(0)    收藏  举报