函数的定义

  • 文件顶部要先函数声明,之后才写函数体;(函数声明后面要写分号)【不写函数声明也是可以正常运行的】
  • 参数:形参有两种方式:传值型:void fun(int a)、引用型:void fun(int &a) 这两种参数的调用都是 fun(a) 注:引用型是c++语法且不能传常量;形参定义和调用函数实参之间均用‘,’(其实当函数定义无参数时是应该注明 void 的;例如:int fun(void){...});
  • 返回值:C语言默认返回值类型为 ' int ' ;空返回类型用 ' void ',就算空返回类型时,函数也可以通过 ‘return ; ’ 结束函数
#include <stdio.h>
//函数省略了返回类型:默认int型返回值
test0() {
    return 123; //一个较小的整数
}
 
test1() {
    return 4.765f; //浮点数
}
 
test2() {
 return 12345678;//较大的整数,超过0xFFFF
}
 
test3() {
    return 98765432112345LL;//大整数,超过32位无符号整数的范围
}
 
test4() {
    printf("In test4.\n");//没有写返回值
}
 
void main() {
    printf("test0 returns %d\n", test0()); //123
    printf("test1 returns %.2f\n", (float)test1()); //4.00
    printf("test2 returns %d\n", test2()); //12345678
    printf("test3 returns %d\n", test3()); //溢出了
    printf("test4 returns %d\n", test4()); //0
}
  • 要注意形参是否为指针,若为指针实参要传址,否则只需传值既可以。
  • 数组作为实参时:由于数组名是一个地址信息就为地址,所以是传址操作。
  • 数组作为形参时:占用指针类型相同的字节(实际上就是一个地址);而实参则不同,传递数组应有大小字节。
void fun(char a[][3],int b[]){
    printf("fun形参a:长度为%d,尺寸为%d\n",strlen(a),sizeof(a));
    printf("fun形参b:长度为%d,尺寸为%d",strlen(b),sizeof(b));
    //从函数中数组形参的尺寸都是8,可以断定其实际为指针
    //因为数组形参是指针,所以一维数组可以不写长度
    //注:二维数组可以不写行数,但列数必须写
}

int main()
{
    char a[][3]={"aaa","bbb","ccc","ddd"};
    int b[100]={1,2,3};
    printf("main实参a:长度为%d,尺寸为%d\n",strlen(a),sizeof(a));
    printf("main实参b:长度为%d,尺寸为%d\n",strlen(b),sizeof(b));
    fun(a,b);
}
//main实参a:长度为13,尺寸为12
//main实参b:长度为1,尺寸为400
//fun实参a:长度为13,尺寸为8
//fun实参b:长度为1,尺寸为8
  • 实参字符串数组,相应形参用二级指针接收
void fun(char **p){
    //二级指针:指向一级指针,这里指向指针数组
    printf("%s  %s\n",*(p+1),*p+1);
    //一次解引用是字符地址(字符串的首地址)
    printf("%c  %c\n",*(*(p+2)+1),1+*(*(p+2)+1));
    //两次解引用是某个字符,再加减就是对字符操作了
}

int main(){
    char *str[]={"asdf","qwer","zxvc"};
    fun(str);
}
//输出:qwer  sdf
//      x  y
  • 实参是二维数组时,形参的定义定义方式
//形参接收二维数组有两种方法:二维数组、数组指针
//并且两种方式都必须写明列数:系统按列数划分一维数组大小
void fun(char a[][4],char (*b)[2]){
    int i,j;
    printf("二维数组形参(3*4):");
    for(i=0;i<3;i++){
        for(j=0;j<4;j++) printf("%c",a[i][j]);
        putchar(' ');
    }putchar('\n');
    printf("二维数组形参(6*2):");
    for(i=0;i<6;i++){
        for(j=0;j<2;j++) printf("%c",b[i][j]);
        putchar(' ');
    }
}

int main()
{
    int i,j;
    char ss[][3]={"aaa","bbb","ccc","ddd"};
    printf("二维数组形参(4*3):");
    for(i=0;i<4;i++){
        for(j=0;j<3;j++) printf("%c",ss[i][j]);
        putchar(' ');
    }putchar('\n');
    fun(ss,ss);
}
//二维数组形参(4*3):aaa bbb ccc ddd
//二维数组形参(3*4):aaab bbcc cddd
//二维数组形参(6*2):aa ab bb cc cd dd
  • 可变参数的定义
//可变参数:n表示参数的个数;va_list vap为定义一个参数列表;va_start(vap,n),va_arg(vap,int),va_end(vap)为宏定义;(#include <stdarg.h>)
#include <stdio.h>
#include <stdarg.h>

int sun(int n,...);

int sun(int n,...){
    int i,sum=0;
    va_list vap;
    va_start(vap,n);
    for(i=0;i<n;i++){
        sum+=va_arg(vap,int);
    }
    va_end(vap);
    return sum;
} 

指针函数:定义时要用指针定义

  • char *getWord(char);这里为函数定义;因为'()'的优先级高于'*';所以定义的是一个函数,去掉函数名和函数参数结构是返回类型,此处为 char 型指针
//声明:char *getWord(char);
char  *getWord(char c){
    return "abc";
}
//上述的函数返回一个字符串:实际返回的为 'a' 的地址;C语言中没有定义字符串类型,只是用char类型的指针来指向字符串,以'\0'结束) 直接返回字符串,c语言则会找到一个固定的存储区域来存放这个常量;
//不能返回局部变量的地址的,因为在函数中的局部变量出了函数就已经被销毁了。如:以下就会异常
char  *getWord(char c){
    char str[]="abc"; 
    return str;
}

函数指针:指向函数的指针,函数名就是函数的地址

  • int (*p)();函数指针定义:因为'()'的优先级高于'*';所以定义的是一个指针,去掉指针标志 '*' ,剩下的表示指针指向的类型,此处为一空参数且返回值是 int 型的函数;故,函数指针
  • 函数指针作为参数:

 

int add(int ,int);
int calc(int (*fp)(int,int),int ,int);
//fp为函数指针类型参数的参数名;指针参数也可以不写参数名:int calc(int (*)(int,int),int ,int);

int add(int num1,int num2){
    return num1+num2;
}

int calc(int (*fp)(int,int),int num1,int num2){
    return (*fp)(num1,num2);
    //return fp(num1,num2);一样可以运行,相当于间接调用函数
}

int main(){
    printf("3+5=%d\n",calc(add,3,5));
    return 0;
}
//add为上面定义的一个函数;这里作为参数传入calc函数

 

  • 函数指针作为返回值:
  • int  ( *select(char) )(int,int);因为'()'的优先级高于'*';所以定义看第一个括号中内容,是一个参数类型为 char 返回值为指针的函数;去掉函数名 select 和参数 char 就剩下这个函数返回值类型(函数指针:指向参数是 (int,int)且返回值是 int 的函数)

 

#include <stdio.h>

int (*select(char))(int ,int);
//声明时将函数名定义合并入返回类型中;所以先是select(char)的内容表示一个叫'select'函数,参数列表为char;
//然后去掉函数的定义部分就是返回值类型 int (*)(int,int);由此刻得处返回值是一个函数指针;
int add(int a,int b){return a+b;}
int sub(int a,int b){return a-b;}
int (*select(char op))(int,int){
    switch(op){
        case '+':return add;
        case '-':return sub;
    }
}
int calc(int (*fp)(),int a,int b){
    return fp(a,b);
}
int main(){
    char op='-';
    int (*fp)();
    fp=select(op);
    printf("3%c5=%d\n",op,calc(fp,3,5));
}

 


作用域

  • 代码块作用域:位于一个 '{}' 中;
  • 文件作用域:在代码块之外声明,作用域为从声明开始到文件结尾处均可访问;
  • 原型作用域:在函数原型中声明的参数名
  • 函数作用域;

链接属性

  • external:声明(允许跨文件访问,默认选项),多个文件中声明的同名标识符表示同一个实体
  • internal:单个文件中声明的同名标识符表示同一个实体
  • none(无):声明的同名标识符被当做独立不同的实体
  • static:修饰的变量或函数只能在本文件中使用,并且会自动赋值为0,在其他文件中链接不过去的。该属性可使原先拥有external属性的标识符变为internal属性。连接属性只能修改一次,只针对具有文件作用域的标识符;

注:对于拥有其他作用域的标识符(变量)是另一种功能——静态:具有静态存储期,(生存期与全局变量相同,但作用域不会改变)


变量的生存期

  • 静态存储期:具有文件作用域的变量(函数,全局变量);在程序执行期间将一直占据存储空间,直到程序关闭才释放
  • 自动存储期:具有代码块作用域的变量(即使是函数中的定义的变量都是自动存储期);在代码块结束时将自动释放存储空间

存储类型

  • auto:自动变量,在代码块中声明的变量默认就是 auto
  • register:寄存器变量,将使用频繁的局部变量存储在CPU的寄存器中,由于寄存器中存取速度远高于内存,因此可以提高执行效率
  1. 这种变量有可能被存储在CPU的寄存器中,所以无法通过取址操作符获得该变量的地址;
  2. 很多方面和自动变量相同,拥有代码块作用域,自动存储期和空链接属性
  • static:1、修饰有文件作用域的标识符(函数,全局变量):使原先拥有external属性的标识符变为internal属性。

       2、修饰无文件作用域的标识符(局部变量):使局部变量指定为静态局部变量,自动赋值为0,具有静态存储期,(生存期与全局变量相同,但作用域不会改变)
    注:修饰文件作用域,限定只可在本文件中使用;修饰局部变量,作用域是不变;

  • extern:声明这个变量在还未编译的其他位置定义,不要报错;经常用于两源文件使用同一各变量的情况(方便阅读)。

结:auto,register属于自由存储期;static,extern,typedef属于静态存储期;