函数的结构

#include <stdio.h> // 函数的别称是方法,函数是完成某一特定功能的模块 void print() // 自定义函数 { printf("锄禾日当午 "); // printf是系统函数 /* 函数名 printf 函数的参数 "锄禾日当午 " () 紧挨着函数名之后,包含参数 */ getchar(); // 等待输入一个字符 } void printadd(int a, int b) { printf("a+b=%d", a+b); // 将a+b的值转换为字符串,映射到字符串“a+b=%d” } // 函数就是加工厂,给你输入,你给我输出;但是函数的输入与输出可以为空 void main1() //C程序的入口,有且仅有一个main函数 { //print(); // 自定义函数 //printf("锄禾日当午 "); // printf是系统函数 printadd(4, 8); getchar(); }
函数名与函数表

#include <stdlib.h> #include <stdio.h> // int getmax中的int是函数的返回值类型 // getmax即函数名,是一个指向常量的指针,指向代码区的函数表某个地址;记录了函数名对应的函数体的入口地址(每一个应用程序都有一个函数表,该表存储了该程序所有函数的入口地址——即函数名对应的地址,改变函数入口地址可以改变函数的行为) // int a, int b函数的形式参数 // {...} 块语句,不允许省略 // return a > b ? a : b; 返回值 // int getmax(int a, int b) 函数声明,别称函数头 int getmax(int a, int b) { /*int a; */ // error C2082 : redefinition of formal parameter 'a' , 函数体内的变量和函数参数是同级别的同属函数体 return a > b ? a : b; } void main02() { printf("%p", getmax); getchar(); } /* 在下面语句的下方打一断点: printf("%p", getmax); 0017133E 查找反汇编:地址栏输入0x0017133E 跳转到该程序的函数表对应的: _getmax: 0017133E jmp getmax (01717A0h) 在函数表中 即函数名getmax映射到getmax()函数体的位置0x01717A0 地址栏输入0x01717A0 跳转到了函数体的内存位置: 函数实体 int getmax(int a, int b) { 001717A0 push ebp 001717A1 mov ebp,esp 001717A3 sub esp,0C4h ... */
函数的定义与声明

#include <stdlib.h> // std标准库,C语言标准库是跨平台的 #include <stdio.h> #include <Windows.h> // 仅适用于Windows系统(第三方库函数) // C++属于严格的编程语言,函数的声明必须在调用之前 int getres(int a, int b, int c); // 函数的声明 /* 调用一个函数,首先必须知道这个函数是存在的C语言自上而下编译 被调函数没有定义或者其在主调函数之后时,编译会出错 故解决的方法有: 首先要定义这个被调函数 其次被调函数如果在主调函数之后,则需要在主调函数之前对被调函数加以声明;如果被调函数在主调函数之前则不需另加声明 */ // 函数的声明 void msg(); // 函数的声明,只是说明函数的存在,因此也可出现重复(傻子才这么干) void msg(); void msg(); void msg(); int cheng(int a, int b); void main123() { msg(); printf("%d ", cheng(12, 3)); system("pause"); } // 函数的定义 void msg() { MessageBoxA(0, "锄禾日当午", "学Cztm刺激", 0); } //void msg() error C2084: function 'void msg()' already has a body; note: see previous definition of 'msg' //{ // 函数的定义则只能出现一次 //} int cheng(int a, int b) { return a + b; } void main124() { // 代码重用 int x = 3, y = 6, z = 9; x = x*x*x; y = y*y*y; z = z*z*z; int res = x + y + z; res = getres(x, y, z); /* getres(x, y, z);调用在定义之前时 C程序: warning C4013: 'getres' undefined; assuming extern returning int 1>LINK : fatal error LNK1561: entry point must be defined (C中能编译——仅是警告,但是链接不行——严重错误:实例处必须定义) CPP程序: 1>LINK : fatal error LNK1561: entry point must be defined 2>error C3861: 'getres': identifier not found */ int a = 10, b = 13, c = 14; a = a*a*a; b = b*b*b; c = c*c*c; int res1 = a + b + c; res1 = getres(a, b, c); system("pause"); } int getres(int a, int b, int c) { return a*a*a + b*b*b + c*c*c; } void run(char *path) // 外部函数,C语言功能的实现(代码重用)主要靠函数 { ShellExecuteA(0, "open", path, 0, 0, 1); } void main01() { run("E:\Thunder Network\Thunder\Program\Thunder.exe"); getchar(); /* 库函数: 由C语言系统提供,用户无需定义,也不必在程序中做类型说明,只需在程序前包含有该函数定义的头文件即可 */ }

#include <stdlib.h> #include <stdio.h> int add05(int, int); // 函数声明 /* 声明时,变量名可省略,可以多次声明 函数的类型声明应该和定义的函数的类型保持一致,否则编译器会报错 当被调函数定义在主调函数之前,声明就没必要了 */ void main005() { add05(3, 2); system("pause"); } int add05(int a, int b) { return a + b; }
函数的参数

#include <stdio.h> #include <stdlib.h> /* 形参(形式参数)与实参(实际参数)的总结: 形参: 1、函数调用之前,形参即函数定义时()里的参数,值是不确定的; 2、不确定的值,不会分配内存,只有调用的时候,才会分配内存并新建一个变量,新建的这个变量(形参)会接收实参(实际参数)的值,当函数调用结束之后,形参所占据的内存会被回收 实参: 1、函数调用时,主调函数传递给被调函数的确切值就是实际参数,实参可以是常量、变量或者表达式 形参与实参内存地址不一样,占用的是不同的内存空间 */ // 地址不一样,说明change(int num)与main()函数中的num是2个不同的变量 // 函数定义的时候是形参 void change(int num) // 此处的int num,是int类型的变量,是形参,只有在函数change被调用的时候才会分配内存,change运行完毕,num的内存即被回收;函数调用的时候,形参分配内存,新建一个num的变量,用于存储传递过来的实际参数的值 { printf("change=%x, num=%d ", &num, num); // change = 17824956, num=10 num = 100; printf("change=%x, num=%d ", &num, num); // change = 17824956, num=100 } void main11() { int num = 10; printf("main=%x, num=%d ", &num, num); // main = 17825168, num=10 change(num); // 此处的num是一个实参,函数调用的时候传递的是实参;实参可以是变量、常量或表达式 printf("%d ", num); // 10 system("pause"); }

#include <stdlib.h> #include <stdio.h> /* 函数调用的时候,形参分配内存 新建一个变量,存储传递过来的实参的值,等价于实参给形参赋值,赋值会自动完成数据类型转换 */ void print_1(int num) { printf("num=%d ", num); } int adddata(int a, int b) { return a + b; } void main12() { // 调用函数的时候,尽量做到类型匹配,否则可能导致误差或错误 //print_1(10.8); double db = adddata(10.9, 9.8); printf("%lf ", db); // 19.000000 double db2 = adddata("Ahg", "fgB"); // Warning C4047 'function': 'int' differs in levels of indirection from 'char [4]' ----> C // Warning C4024 'adddata': different types for formal and actual parameter 1 ----> CPP printf("%lf ", db2); // 11990992.000000 意想不到的结果 system("pause"); }
函数的可变参数

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> void myprintf(char *ptstr, ...) // 可变参数 { va_list ap; // 定义起始点 (char *类型的指针) va_start(ap, ptstr); /* 固定参数pstr,是函数myprintf()的第一个参数,存储于栈中,位于栈底 typedef char * va_list; 把 n 圆整到 sizeof(int) 的倍数 #define _INTSIZEOF(n) ( (sizeof(n)+sizeof(int)-1) & ~(sizeof(int)-1) ) 初始化 ap 指针,使其指向第一个可变参数。v 是变参列表的前一个参数 即 此处的ptstr #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) 该宏返回当前变参值,并使 ap 指向列表中的下个变参(ap指向的数据以type类型的方式加以解析) #define va_arg(ap, type) ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) ) 将指针 ap 置为无效,结束变参的获取 #define va_end(ap) ( ap = (va_list)0 ) 原文链接:https://blog.csdn.net/u013490896/java/article/details/85490103 参考链接:https://www.cnblogs.com/clover-toeic/p/3736748.html */ char flag; while (*ptstr) { flag = *ptstr; if (flag != '%') { putchar(flag); ptstr++; } else { flag = *++ptstr; switch (flag) { case 'd': printf("%d", va_arg(ap, int)); ptstr++; break; case 's': printf("%s", va_arg(ap, char *)); ptstr++; break; case 'c': //printf("%c", va_arg(ap, char)); putchar(va_arg(ap, char)); ptstr++; break; case 'f': printf("%f", va_arg(ap, double)); ptstr++; break; default: break; } } } va_end(ap); // 结束读取 } void main003() { myprintf("niha "); myprintf("niha%d ", 10); myprintf("niha%d%s ", 10, "Hello"); myprintf("niha%d%s%c ", 10, "Hello", 'A'); myprintf("niha%d%s%f%c ", 10, "Hello", 3.1415926, 'A'); puts(" "); system("pause"); }

#define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <stdio.h> #include <stdarg.h> // stand argument // 已知参数的个数为num int add3(int num, ...) // ...代表可变参数,num代表参数个数 { int res = 0; //结果 va_list argp; // 存储参数开始的地址(参数列表首地址) va_start(argp, num);// 从首地址开始,读入num后面的数据到地址 for (int i = 0; i < num; ++i) { res += va_arg(argp, int); // 按照int类型读取一个数据单元 } va_end(argp); return res; } void go(int num, ...) { va_list argp; va_start(argp, num); for (int i = 0; i < num; ++i) { /*char str[50]; sprintf(str, "%s", va_arg(argp, char *)); system(str);*/ system(va_arg(argp, char *)); } va_end(argp); } void showint(int start, ...) { va_list argp; va_start(argp, start); int argvalue = start; // 第一步初始化 do { printf("%d ", argvalue); argvalue = va_arg(argp, int); } while (argvalue != -1); va_end(argp); } void main08() { //printf("%d ", add3(3, 1, 2, 3)); //6 //printf("%d ", add3(4,3, 1, 2, 3)); // 9 //printf("%d ", add3(5, 4, 7, 1, 2, 3)); // 17 //go(3, "notepad", "calc", "tasklist&pause"); showint(1, 2, 3, 4, 5, -1); getchar(); }

#include <stdio.h> #include <stdlib.h> // void main(void) 返回空类型,参数为空 // main() 默认的返回值类型为int类型,参数为空 main(int argc, char *args[]) { for (int i = 0; i < argc; i++) { puts(args[i]); } // argc是参数的个数 // args[]是一个指针数组,存储的是常量字符串的地址,args[0]第一个参数是主程序的路径,第二个是附加参数 system("pause"); } void main020(int argc, char *args[], char *envr[]) // 第三个参数:环境变量字符串的地址 { char **pp = envr; while (*pp != NULL) { puts(*pp); pp++; } system("pause"); }
函数参数的运算与入栈顺序

#include <stdlib.h> #include <stdio.h> void add2(int a, int b) { printf("a=%d, b=%d ", a, b); } void main07() { int a = 5; int b = 5; int c = 5; int d = 5; int e = 5; int f = 5; /* 函数参数与函数一样,遵循栈先进后出的原则,即先执行的代码后入栈,后执行的代码先入栈 */ add2(a, a++); // a=6, b=5 /* 入栈: 1、a++是一个表达式,保存在栈中的结果为5,a自增1等于6 2、a是一个变量,保存在栈中的结果为a 出栈: 2、a ---> 6 1、a++ ---> 5 ----> 5 */ add2(b, b += 1); // a=6, b=6 /* 入栈: 1、b+=1是一个赋值表达式,保存在栈中的结果为b,b自增1等于6 2、b是一个变量,保存在栈中的结果为b 出栈: 2、b ---> 6 1、b+=1 ---> b ---> 6 */ add2(c++, c = c + 2); // a=7, b=8 add2(d++, d += 2); // a=7, b=8 /* 入栈: 1、d+=2是一个赋值表达式,保存在栈中的结果为d,d自增2等于7 2、d++是一个表达式,保存在栈中的结果为7,d自增1等于8 出栈: 2、d++ ---> 7 1、d+=2 ---> d ---> 8 */ add2(e, e++, e += 2); // a=8, b=7 /* 入栈: 1、e += 2是一个赋值表达式,保存在栈中的结果为e,e自增2等于7 2、e++是一个表达式,保存在栈中的结果为7,f自增1等于8 3、e是一个变量,保存在栈中的结果为e 出栈: 3、e ---> 8 2、e++ ---> 7 因为add2()函数定义中只有2个形参,因此1、e += 2不会作为函数的参数出栈 */ add2(f += 2, f++, f += 2); // a=10, b=7 /* 入栈: 1、f += 2是一个赋值表达式,保存在栈中的结果为f,f自增2等于7 2、f++是一个表达式,保存在栈中的结果为7,f自增1等于8 3、f += 2是一个赋值表达式,保存在栈中的结果为f,f自增2等于10 出栈: 3、f += 2 ---> f ---> 10 2、f++ ---> 7 因为add2()函数定义中只有2个形参,因此1、f+= 2不会作为函数的参数出栈 */ getchar(); }
函数的执行过程

#include <stdlib.h> #include <stdio.h> // C语言通过栈来控制程序的执行顺序,后执行的语句或函数先进栈,最后压栈的语句或函数先出栈执行 void main06() { printf("main 上 "); //void print1(); // C语言理论上是要加声明的 //print1(); 没有声明时,该函数无参数,会到系统中去找匹配的函数,结果没有,就发生链接错误 add1(3, 4); // 而此时add1函数未加声明,C编译器却编译并链接成功,还能正确执行,只能说C编译器太宽泛了,此处编译器根据add1(3,4);有参数,而且返回值是整数,因此找到了被调函数 printf("%d ", add1(3)); // 15274067(执行第二次结果就不一样了) 参数多了或少了,结果不可预测(居然编译并成功执行了,C编译器真是大条了!) printf("main 下 "); system("pause"); } /* 被调函数的定义在主调函数之后,C语言编译器可能找到了,则会通过编译并链接成功;如果没找到则会出现以下错误: (10): warning C4013: 'print1' undefined; assuming extern returning int (17): error C2371: 'print1': redefinition; different basic types 但是在被调之前加以声明,则一定能找到已定义的被调函数 */ void print1() { printf("main 上 "); printf("main 下 "); } int add1(int a, int b) { return a + b; }
函数调用的注意事项

#include <stdio.h> void main09() { //printf("Hello China. "); printf; // 引用函数名必须声明 // warning C4550: expression evaluates to a function which is missing an argument list 加入#include <stdio.h>时 } /* C语言函数可以自动定位,没有头文件,没有函数声明,自动寻找,参数多了、少了都能执行;如果不是函数调用,无法自动定位 */ void main001() { //printf("%d ", add4(2, 3)); //add4; /* 当上面一句没有被注释时:[compile] warning C4013: 'add4' undefined; assuming extern returning int,而且能够链接并且程序能够运行 当上面一句被注释时:[compile]error C2065: 'add4': undeclared identifier 说明C编译器查找add4(2, 3); 时,因为有函数调用而且是有参调用,能够精确定位到本程序内部的函数定义,接着下面的add4就在上一步的基础上找到了。 可见C语言的编译器太灵活了。 */ print11(); system("pause"); } int print11() { } int add4(int a, int b) { return a + b; }
函数的返回值

#include <stdlib.h> #include <stdio.h> int go() { } /* Warning C4716 'go': must return a value 非main函数如果函数类型不是void,编译时会出现一个警告,如果是CPP文件,则会是一个错误 函数的返回值类型必须与函数名前的类型保持一致,当函数的类型为void时,可以没有return语句 void main(void) 返回空类型,参数为空 main() 默认的返回值类型为int类型,参数为空 */ int main6() // 无论main函数是否是void类型,还是其他类型的函数,main函数可以没有返回值 { //printf(" 1"); //printf(" 2"); //return 1; // return 之后的语句不会接着执行,main函数中的return意味着整个程序的退出和结束 //printf(" 3"); //printf(" 4"); //printf(" 5"); godata(); getchar(); // main函数或其他函数中没有return语句,执行完所有的语句后,函数会自动退出 } /* 函数内部定义的变量或参数(局部变量),在函数调用结束之后(函数返回后),变量即刻被销毁,内存被回收 函数返回值有副本机制,返回的时候,另外再存一份 如果返回值是全局变量,则该变量一直存在,直到程序运行结束,全局变量才会被销毁,内存这时才被回收 */ int addpp(int a, int b) { int z = a + b; printf("%p", &z); return z; } int addbb(int a, int b) { int z = 0; z = a + 1; z = a + 2; z = a + b; return a + b; // a+b是临时变量,在寄存器中保存其计算结果 // 返回临时变量时,临时变量会从寄存器中即刻销毁 } void main() { // 此处打印的是副本,原来内存中的数据已被销毁 //printf("%d ", addpp(3, 7)); printf("%d ", addbb(3, 7)); printf(" "); getchar(); }
局部变量与全局变量

#include <stdio.h> #include <stdlib.h> /* 总结: 块语句内部的变量,其作用域是变量所在块语句定义的起始处至该块语句的结束,也可作用于内部包含的块语句(前提是没有定义与该变量的名称相同的变量——存在内层变量屏蔽外层变量的情况) 同一个块语句不能重复定义一个变量 局部变量调用完成以后会被回收,该局部变量的位置出现垃圾数据 局部变量是为块语句服务的,块语句执行结束,局部变量就被回收 函数内部定义的变量以及函数的参数均是局部变量 */ int a = 100; // 变量名相同的全局变量和局部变量,是两个(内存地址)不同的变量,因此可以重复定义 void main16() { int a = 10; // 在同一块语句内,变量不能重复定义 //int a = 11; // Error C2374 'a':redefinition int b = 99; printf("%x, %x, %d, %d ", &a, &b, a, b); // 5bfd44, 5bfd38, 10, 99 { int a = 11; // 不同的块语句,不同的作用域 printf("%x, %x, %d, %d ", &a, &b, a, b); // 5bfd2c, 5bfd38, 11, 99 块语句中可以包含块语句,局部变量b的作用范围包括子块,而在子块中定义的变量a屏蔽了母块中的变量a(其效果与块语句中的变量a屏蔽全局变量a一样) } system("pause"); }

#include <stdlib.h> #include <stdio.h> // 全局变量不属于任何一个函数,可以被任何一个函数调用 // 创建的全局变量比main函数还早,全局变量的生存期是整个程序的生存期 // 全局变量在程序生命周期内一直存储于内存,而局部变量在所属的函数调用完毕之后生命周期结束,同时其所占据的内存也将被回收 // 需要任意函数调用的场合就需要全局变量,全局变量可用于函数间的通信 int num = 4298; // 全局变量 -- 整个公司的RMB int data = 10; void 事业部A() { num = num - 800; // 支出800 num = num + 900; // 营收900 } void 事业部B() { num = num - 1800; // 支出1800 num = num + 900; // 营收900 } void printdata() { printf("%d ", data); } void main14() { data = 100; printdata(); // 100 //num += 1000; // 科技创新基金 //事业部A(); //事业部B(); //printf("num=%d ", num); system("pause"); }

#include <stdio.h> #include <stdlib.h> /* 全局变量特点/缺点: 生存周期一直持续到程序退出 内存也在程序退出之后才释放 容易与局部变量重名,容易被屏蔽失效 值容易被修改——例如游戏,遇到外挂一类的程序、注入的黑客技术等技术,值容易被修改 */ /* 全局变量可以被本文件中所有的函数所共享 使用全局变量要做到: 1、变量名要容易理解,尽可能不与局部变量重名 2、避免占内存较大的变量作为全局变量 3、避免全局变量被错误的修改(正规的软件工程,写一个函数需要修改全局变量时,一定要注释为什么修改,修改的目的是什么,修改值是多少) */ int RMB = 3000; void addRMB(int num) { RMB += num; } void mulRMB(int num) { RMB -= num; } void printRMB() { printf("你的RMB还有%d ", RMB); } void main17() { addRMB(1000); mulRMB(5); mulRMB(500); mulRMB(5); printRMB(); system("pause"); }

#include <stdio.h> #include <stdlib.h> int x = 1000; void main15() { int x = 8; // 变量名称相同的时候,在局部变量的作用域中,局部变量会屏蔽全局变量(不是改变) printf("%d ", x); // 8 //printf("%d ", ::x); // C++可以用::x来访问全局变量x, C则不可以 system("pause"); } /* 全局变量很容易被内部变量屏蔽,很容易被引用或修改 */ int a; void maina() { printf("%d ", a); /* 本文件未声明变量a时:int a error C2065: 'a': undeclared identifier 声明a时:int a; 输出结果为9,int a =9;是在同工程中的“全局变量和局部变量.c”文件中声明的——说明全局变量是可跨文件调用的 */ getchar(); } void main006() // 这里说的屏蔽是指变量名相同的变量 { int a = 10; printf("%d ", a); // 局部变量会屏蔽全局变量 { printf("%d ", a); // 10 此处是引用的上一层局部变量 char a = 'B'; //int a = 66; // error C2371: 'a': redefinition; different basic types printf("%d,%c ", a, a); // 内部块语句局部变量会屏蔽内部块语句以外的变量 } printf("%d ", a); // 10 getchar(); }

#include <stdlib.h> #include <stdio.h> /* 局部变量只有定义和初始化的概念,不存在声明 全局变量可以多次声明,定义的时候相当于声明+初始化 */ //int a = 10; // 全局变量 int a; // 全局变量被当成声明来看待 int a; // 全局变量未定义默认为ASCII为0,字符为空 int a; int a = 9; // 全局变量的定义 //int a = 3; // error C2374: 'a': redefinition; multiple initialization void main004() { printf("%d,%c ", a, a); // 0,' ' int b; //printf("%d ", b); // error C4700: uninitialized local variable 'b' used printf("%c,%d ", c, c); // ' ',0 system("pause"); } void main003() { int a; // 局部变量 int a=13; // error C2086: 'int a': redefinition } void go001() { a = 13; //(无全局变量时) error C2065: 'a': undeclared identifier }
函数与递归

#include <stdio.h> #include <stdlib.h> //void main() //{ // main(); // 死循环 //} // f(n)=f(n+1)+1 // f(5) return void go_3(int num) { if (num >= 5) return; else { system("notepad"); go_3(num + 1); } } /* 1+2+3+...+100 f(100) 1+2+3+...+99 f(99) f(100)=f(99)+100 f(n)=f(n-1)+n */ int goadd_1(int num) { if (num == 0) return 0; else return num + goadd_1(num - 1); } /* 10 % 2 0 5 % 2 1 2 % 2 0 1 % 2 1 (10)10=(1010)2 */ void to2(int num) { if (num == 0) return; else { //printf("%d", num % 2); // 0101 调用之前顺序 to2(num / 2); printf("%d", num % 2); // 1010 调用之后逆序 } } void main009() { //go_3(0); //printf("%d ", goadd_1(100)); to2(10); getchar(); }

#include <stdio.h> #include <stdlib.h> /* 斐波那契数列: 有一对兔子,每个月可以生育一对兔子,刚出生的兔子2个月后可以生育一对兔子,问N个月之后又多少对兔子 1 1 1 1 1 1 1 1 1 1 1 1 g(n)=g(n-1)+g(n-2) */ int getRabbitTreeRecursive(int num) { if (num == 1 || num == 2) return 1; else return getRabbitTreeRecursive(num - 1) + getRabbitTreeRecursive(num - 2); } int getRabitForCycle(int num) { int f1 = 1, f2 = 1; int f3; for (int i = 3; i <= num; ++i) { f3 = f2 + f1; f1 = f2; f2 = f3; // 轮替注意值的覆盖顺序 } return f2; } void main() { printf("%d ", getRabitForCycle(40)); printf("%d ", getRabbitTreeRecursive(40)); printf("%d ", getRabitForCycle(40)); /* 树状递归速度较慢,函数的循环调用需要消耗时间,但是算法简单; 树状递归可以分解为:循环+栈 */ getchar(); }