C 函数详解

发布于 2021-06-10 12:48:18 字数 10462 浏览 1163 评论 0

一、函数的定义

#include <stdio.h>

/*
#函数的定义

类型名 函数名(参数列表){函数体}
类型名是函数返回值的类型,如果函数没有返回值,类型名为void
*/


/*
# 函数的申明

所谓申明,就是告诉编译器我要使用这个函数,你现在没有找到它的定义不要紧,请不要
报错,稍后我会把定义补上

如果函数的定义写在函数的调用之后,必须写
如果函数的定义写在函数的调用之前,可以不写
*/

void print_c();//函数的申明

void print_c(){
    printf(" ###### \n");
    printf("##    ##\n");
    printf("##      \n");
    printf("##      \n");
    printf("##    ##\n");
    printf(" ###### \n");
}

int main(){
    print_c();//函数的调用
    printf("\n");
    print_c();
    printf("\n");
    print_c();
    return 0;
}

二、函数的参数和返回值

#include <stdio.h>

//函数的参数和返回值

/*
# 函数的参数
*/

int sum(int n);
int sum(int n){
    int sum = 0;;
    while(n>=0){
        sum+=n;
        n--;
    }
    return sum;
}

int cmp(int a,int b);
int cmp(int a,int b){
    if(a>=b){
        return a;
    }else{
        return b;
    }
}


int main(){
    int n;
    printf("请输入n的值:");
    scanf("%d",&n);
    int result;
    result = sum(n);
    printf("和为:%d\n",result);

    int max;
    max = cmp(5,4);
    printf("较大值为:%d\n",max);
    return 0;
}

三、参数和指针

#include <stdio.h>
//一个函数仅实现一个功能



//参数和指针


/**
# 传值和传址

指针也是一个变量,你可以通过参数传递给函数
*/

void swap(int x ,int y);
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);
}

void swap2(int *a, int *b);
void swap2(int *a, int *b){
    printf("In swap2互换前为a=%d,b=%d\n",*a,*b);
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
    printf("In swap2互换后为:a=%d,b=%d\n",*a,*b);
}
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);


    printf("-------------------------------\n");
    int a = 3,b = 5;
    printf("In main互换前为a=%d,b=%d\n",a,b);
    swap2(&a,&b);
    printf("In main互换后为:a=%d,b=%d\n",a,b);

    return 0;
}

四、指针函数

#include <stdio.h>


//指针函数 与 函数指针

/**

#指针函数:使用指针变量作为函数的返回值

不要

*/

char *getWord(char c);
char *getWord(char c){
    switch(c){
            case 'A':
            return "Apple";
            case 'B':
            return "Banana";
            case 'C':
            return "Cat";
            case 'D':
            return "Dog";
            default:
            return "NULL";
    }
}
int main(){
    char input;
    printf("请输入一个字母:");
    scanf("%c",&input);

    printf("%s\n",getWord(input));

    return 0;
}

五、函数指针

#include <stdio.h>


//函数指针

//指向函数的指针
//int (*p)()

int square(int num);
int square(int num){
    return num*num;
}


int main(){
    int num;
    //申明一个函数指针
    int (*fp)(int);

    printf("请输入一个整数:");
    scanf("%d",&num);

    fp = square;

    printf("%d\n",(*fp)(num));
    return 0;
}

六、局部变量和全局变量

#include<stdio.h>

/**
局部变量和全局变量
*/

//1、不同函数的变量无法相互访问
//2、在函数内部定义叫做局部变量,在函数外部定义叫做外部变量,也叫全局变量

//如果不对全局变量进行初始化,那么它会自动初始化为0
//如果在函数的内部存在一个与全局变量同名的局部变量,编译器并不会报错,而是在函数中屏蔽全局变量(也就是说在这个函数中全局变量不起作用)
int main_1(){
    int i = 520;
    printf("before,i=%d\n",i);//520

    for(int i = 0;i<10;i++){//此时的i只争对本代码块内有效
        printf("%d\n",i);
    }

    printf("after,i=%d\n",i);//520
    return 0;
}
//有时候,我们可能需要在多个函数中共同使用一个变量,那么就会用到全局变量。因为全局变量可以被本程序中其他函数共用的。
int count = 0;//这就是一个全局变量
void a();
void b();
void c();
void a(){
    count++;
}
void b(){
    count++;
}
void c(){
    count++;
}
int main_2(){
    a();
    b();
    c();
    b();
    printf("小郭今天被抱了%d次\n",count);//小郭今天被抱了4次
}

//extern关键字
//用extern关键字告诉编译器这个变量我在后边定义了,你先别急着报错
void incr();
void incr(){
    extern incrNum;//关键字
    incrNum++;
}
int incrNum = 0;
int main(){
    incr();
    printf("%d\n",incrNum);
    return 0;
}
/**
 不要大量使用全局变量

 1.使用全局变量会使你的程序占用更多的内存,因为全局变量从被定义时候开始,直到程序退出才被释放。而局部变量当函数被调用之后就释放了。
 2.会污染命名空间,虽然局部变量会屏蔽全局变量,但这样以来也会降低程序的可读性,人们往往很难一下子判断出每个变量的含义和作用范围。

 3.提高了程序的耦合性,牵一发而动全身,时间久了,代码长了,你都不知道全局变量被哪些函数修改过。
 */

七、作用域和链接属性

#include<stdio.h>

//作用域和链接属性

/**
 作用域:

 1、当变量被定义在程序的不同位置时,它的作用范围是不一样的,这个作用返回就是我们所说的作用域。
 2、C语言编译器可以确认4种不同类型的作用域:
 ----代码块作用域
 ----文件作用域
 ----原型作用域(少用)
 ----函数作用域(少用)
 */

/**
 代码块作用域:
 1、在代码块种定义的变量,具有代码块作用域。作用范围是从变量定义的位置开始,到标志该代码块结束的右大括号处
 2、尽管函数的形式参数不在打括号内定义,但其同样具有代码块作用域,隶属于包含函数体的代码块。
 */
int main_1(){
    int i = 100;//i1
    {
        int i = 110;//i2
        {
            int i = 120;//i3
            printf("i = %d\n",i);//120
        }
        {
            printf("i = %d\n",i);//110
            int i =130;//i4
            printf("i = %d\n",i);//130
        }
        printf("i = %d\n",i);//110
    }
    printf("i = %d\n",i);//100
    return 0;
}

/**
 文件作用域(file scope):

 1、任何在代码块之外声明的标识符都具有文件作用域,作用范围是从他们的声明位置开始,到文件的结尾处都是可以访问的。
 2、另外,函数名也具有文件作用域,因为函数名本身也是在代码块之外。
 */

/**
 原型作用域:

 1、原型作用域只适用于那些在函数原型中声明的参数名。函数在声明的时候可以不写参数名字(但参数的类型是必须要写上的),其实函数原型的参数名还可以随便写一个名字,不必与形式参数相匹配(当然,这样做没有任何意义!)。
 void func(int a,int b,int c);
 void func(int d,int e,int f){...}
 */


/**
 函数作用域:

 1、函数作用域只适用于goto语句的标签,作用将goto语句的标签限制在同一个函数内部,以及防止出现崇明标签。
 */


//定义和声明
/**
 1、当一个变量被定义的时候,编译器为变量申请内存空间并填充一些值。
 2、当一个变量被声明的时候,编译器就知道该变量被定义在其他地方。
 3、声明是通知编译器该变量名及相关的类型已存在,不需要再为此申请内存空间。
 4、局部变量及时定义又是申明。
 5、定义只能来一次,否则就叫做重复定义某个同名变量;而声明可以有很多次。
 */


//链接属性
/**
 1、external(外部的):多个文件中声明的同名标识符表示同一个实体
 2、internal(内部的):单个文件中声明的同名标识符表示同一个实体
 3、none(无):申明的同名标识符被当作独立不同的实体。
 */

/**
 1、只有具备文件作用域的标识符才能拥有external或internal的链接属性,其他作用域的标识符都是none属性。
 2、默认情况下,具备文件作用域的标识符拥有external属性。也就是说该标识符允许跨文件访问。对于external属性的标识符,无论再不同文件中声明多少次,表示的都是同一个实体。
 */

/**
使用static关键字可以使得原先拥有external属性的标识符变为internal属性。这里有两点需要注意:

 1、使用static关键字修改链接属性,只对具有文件作用域的标识符生效(对于拥有其他作用域的标识符是另一种功能)
 2、链接属性只能修改一次,也就是说一旦标识符的链接属性变为internal,就无法变回external了。
 */

八、生存期和存储类型

#include<stdio.h>

//生存期和存储类型


//生存期
/**
 C语言中的变量拥有两种生存期:
 1、静态存储期(static storage duration)
 2、自动存储期(automatic storage duration)

 具有文件作用域的变量属于静态存储期,函数也属于静态存储期。属于静态存储期的变量在程序执行期间将一直占据存储空间,直到程序关闭才释放。

 具有代码块作用域的变量一般情况下属于自动存储期。属于自动存储期的变量在代码块结束时将自动释放存储空间。
 */
int A;//文件作用域(静态存储期)
static int B;//文件作用域(静态存储期)
extern int C;//文件作用域(静态存储期)
//文件作用域(静态存储期)
void func(int m,int n){
    int a,b,c;//代码块作用域(自动存储期)
}
//文件作用域(静态存储期)
void func_tmp(void){
    int i,j,k;//代码块作用域(自动存储期)
}



//存储类型

/**
存储类型其实就是指存储变量值的内存类型,C语言提供了5种不同的存储类型
 1、auto(自动变量)
 2、register(寄存器变量)
 3、static(静态局部变量)
 4、extern
 5、typedef
 */

/**
 1、auto
 在代码块种声明的变量默认的存储类型就是自动变量,使用关键字auto来描述。
 由于这是默认的存储类型,所以不写auto是完全没有问题的。
 */
int main_1(){
    auto int i,j,k;
    return 0;
}


/**
 2、register
 将一个变量声明为寄存器变量,那么该变量就 有可能 被存放在CPU的寄存器中。
 寄存器变量和自动变量在很多方面时一样的,他们都拥有代码块作用域,自动存储期和空链接属性。
 不过这里有一点需要注意的是:当你将变量声明为寄存器变量,那么你就没办法通过取址运算符获得该变量的地址。(CPU的寄存器地址一般是不允许获取的)
 */
int main_2(){
    register int i = 520;
    //printf("Addr of i:%p\n",&i);//报错:不会允许获取CPU寄存器的地址
    return 0;
}


/**
 3、static
 使用static来声明局部变量,那么就可以将局部变量指为静态局部变量。
 static使得局部变量具有静态存储期,所以它的生存期与全局变量一样,知道程序结束才释放。
 */
void f(void);
void f(void){
    static int count = 0;
    printf("count=%d\n",count);
    count++;
}
int main(){
    int i;
    for(i=0;i<10;i++){
        f();
    }
    return 0;
}


/**
 4、static和extern
 作用于文件作用域的static和extern,static关键字使得默认具有external链接属性的标识符编程internal链接属性,而extern关键字是用于告诉编译器这个变量或函数在别的地方已经定义过了,先去别的地方找找,不要急着报错。
 */


/**
 5、typedef
 结构体再说。
 */

九、递归

#include<stdio.h>

//递归

/**
 注意“递归必须要有结束条件,否则程序将崩溃
 */

void recursion(void);
void recursion(void){
    printf("------");
    static int count = 10;
    printf("Hi,%d\n",count);
    if(--count){//这里是终止递归的关键
        recursion();
    }
}
int main_1(){
    recursion();
    return 0;
}

//递归求阶乘
/**
 循环
 */
long fact(int num);
long fact(int num){
    long result;
    for(result = 1;num>1;num--){
        result *= num;
    }
    return result;
}
int main_2(void){
    int num;
    printf("请输入一个正整数:");
    scanf("%d",&num);
    printf("%d的阶乘是:%d\n",num,fact(num));
    return 0;
}

/**
 递归实现
 */

long fact1(int num);
long result = 1;
long fact1(int num){
    result *= num;
    if(num>1){
        fact1(num-1);
    }
    return result;
}
int main(void){
    int num;
    printf("请输入一个正整数:");
    scanf("%d",&num);
    printf("%d的阶乘是:%d\n",num,fact1(num));
    return 0;
}

十、汉诺塔

#include<stdio.h>

//汉诺塔

void hanoi(int n,char x,char y,char z);

void hanoi(int n,char x,char y,char z){
    if(n==1){
        printf("%c --> %c\n",x,z);
    }else{
        hanoi(n-1,x,z,y);
        print("%c --> %c\n",x,z)
        hanoi(n-1,y,x,z);
    }
}

int main(void){
    int n;
    printf("请输入汉诺塔的层数:");
    scanf("%d",&n);
    hanoi(n,'X','Y','Z');
    return 0;
}

十一、快速排序

#include<stdio.h>

//快速排序


/**
 分治法:

 大事化小,小事化了(递归就是分治法的一种实现)
 */


/**
 快速排序算法的基本思想是:通过一趟排序将待排序数据分割成独立的两部分,其中一部分的所有元数据均比另一部分的元素小,然后分别对这两部分继续进行排序,重复上述步骤直到排序完成。
 */
void quick_sort(int arr[],int left,int right);
void quick_sort(int arr[],int left,int right){
    int i = left,j = right;
    int temp;
    int pivot;//基准点
    pivot = arr[(left+right)/2];

    while (i<=j) {
        //从左到右找到大于等于基准点的元素
        while(arr[i] < pivot){
            i++;
        }
        //从左到右找到小于等于基准点的元素
        while(arr[j] > pivot){
            j--;
        }
        //如果i<=j则互换
        if(i<=j){
            temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
            i++;
            j--;
        }
    }
    if(left<i){
        quick_sort(arr,left,j);
    }
    if(i<right){
        quick_sort(arr,i,right);
    }
}

int main(void){
    int arr[] = {71,107,110,118,101,70,105,115,104,67,46,99,111,109};
    int i,length;
    length = sizeof(arr)/sizeof(arr[0]);
    quick_sort(arr,0,length-1);
    for(i=0;i<length;i++){
        printf("%d ",arr[i]);
    }
    return 0;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

马化腾

文章 0 评论 0

thousandcents

文章 0 评论 0

辰『辰』

文章 0 评论 0

ailin001

文章 0 评论 0

冷情妓

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文