1. 前言
这篇文章介绍C语言的内联函数、递归函数、函数指针、指针函数、局部地址、const关键字、extern关键字等知识点;这些知识点在实际项目开发中非常常用,非常重要。
下面就以小章节的流程介绍每个知识点。
2. 函数返回局部空间的地址问题
子函数: 在调用结束后空间会被释放—被系统回收。
总结:子函数不能返回局部变量的地址。
示例1:
#include <stdio.h> char *func(void); int main() { printf("%s\n",func()); //打印不出来。 return 0; } char *func(void) { char buff[]="1234567890"; return buff; }
示例2:
#include <stdio.h> char *func(char *p); int main() { char buff[]="1234567890"; printf("%s\n",func(buff)); //可以打印 return 0; } char *func(char *p) { return p; }
示例3:
#include <stdio.h> char *func(void); int main() { printf("%s\n",func()); //可以打印 return 0; } char *func(void) { static char buff[]="1234567890"; return buff; }
3. const 只读关键字(常量)
(1) const关键字—修饰变量
#include <stdio.h> int main() { //const int a; //初始化不赋值,这行代码就没有意义 const int a=100; a=200; //错误的代码--无法对常量赋值--只读变量赋值 printf("a=%d\n",a); return 0; }
(2) const关键字—修饰指针
#include <stdio.h> //指针: 数据域、指针(地址)域 int main() { int a=100; int b=200; //const常用4种修饰指针的方法 const int *p1=&a; //指向空间值无法修改,指向的地址可以改变 int const *p2=&a; //指向空间值无法修改,指向的地址可以改变 int *const p3=&a; //指向空间值可以修改,指向的地址无法改变 const int *const p4=&a; //向空间值无法修改,指向的地址无法改变 //int *p5 const; 语法是错误的 //*p1=666; 错误的 //p1=&b; 正确的 //*p2=666;错误的 //p2=&b;正确的 //*p3=666;正确的 //p3=&b;错误的 //p4=&b;错误的 //*p4=666;错误的 return 0; }
4. 内联函数
内联函数: 在调用的时候不会进行压栈出栈(不会经历保存地址的过程和恢复地址的过程)。
内联函数相当于一个替换的过程。
内联函数设计要注意:内联函数里只能写简单的代码—不能写复杂代码。
函数里的局部变量存放在栈空间里的。栈空间:是由系统管理的。
#include <stdio.h> void func(void); int main() { int a; func(); printf("12345\n"); return 0; } //inline 声明-定义内联函数 inline void func(void) { printf("hello\n"); }
5. extern 外部引用声明
extern 多用于多文件编程里变量、函数、其他数据类型的引用声明。
外部引用声明的时候,不能赋值。
#include <stdio.h> //引用声明 extern int a; extern char buff[]; extern void func(); int main() { printf("%d\n",a); printf("%s\n",buff); func(); return 0; } int a=100; char buff[]="1234567890"; void func() { printf("hello\n"); }
6. 字符串数组和字符串常量定义问题
#include <stdio.h> int main() { //p1指向的字符串是在程序编译的时候赋值 char *p1="1234567890"; //指针指向字符串常量 //p2数组是程序运行的时候赋值 char p2[]="abcdefg"; //p1[0]='A'; 错误的 printf("%s\n",p1); printf("%s\n",p2); return 0; }
示例2:
#include <stdio.h> int main() { //p1指向的字符串是在程序编译的时候赋值 char *p1="1234567890"; //指针指向字符串常量 //p2数组是程序运行的时候赋值 char p2[]="abcdefg"; int a; int b; int c; printf("a=%#x\n",&a); printf("b=%#x\n",&b); printf("c=%#x\n",&c); printf("p1=%#x\n",p1); printf("p2=%#x\n",p2); return 0; } /* a=0xbf9f93e0 b=0xbf9f93dc c=0xbf9f93d8 p1=0x8048524 p2=0xbf9f93e4 */
7. 标准main函数形参语法
#include <stdio.h> /* int argc :传入的参数数量(包括可执行文件本身) char **p :保存传入的数据地址 main传入的参数数据都是字符串类型。 传参数的方式: ./a.out 123 456 789 */ int main(int argc,char **p) //int main(int argc,char *p[]) p[0] p[1] p[2] { int i; for(i=0;i<argc;i++) { printf("p[%d]=%s\n",i,p[i]); } return 0; }
8. 指针函数与函数指针
数组指针: 本身是指针,指向二维数组的指针(一维数组指针)。int (*p)[5];
指针数组: 本身是数组,数组里存放的是地址。int *p[5]; (相当于定义了5个指针)
数组的名称本身就是数组元素的首地址—数组名称就是地址。
**函数指针: **本身是指针,指向函数的指针。语法:int (*p)(int,int); 不支持++和—运算符。
指针函数: 本身是函数,表示函数的返回值是指针类型。语法: int *func(int a,int b){}
函数名称就是地址。
示例1:
#include <stdio.h> int func(int a,int b); int main(int argc,char **argv) { int (*p)(int,int); //指向函数的指针 p=func; printf("%d\n",func(10,20)); //通过函数名称调用函数 printf("%d\n",p(10,20)); //通过指针调用函数--写法1 printf("%d\n",(*p)(10,20)); //通过指针调用函数--写法2 return 0; } int func(int a,int b) { return a+b; }
示例2: 函数指针当做函数形参
#include <stdio.h> int func1(int a,int b); int func2(int (*p)(int,int),int a,int b); int main(int argc,char **argv) { printf("%d\n",func2(func1,100,200)); return 0; } int func1(int a,int b) { return a+b; } int func2(int (*p)(int,int),int a,int b) { return p(a,b); }
9. 递归函数
什么是递归函数? 子函数直接或者间接的方式调用自己的过程叫做递归。
函数自己调用自己的过程—递归。
递归函数注意事项:必须有终止条件。
示例1:
#include <stdio.h> int func(int a); int main(int argc,char **argv) { printf("%d\n",func(1)); //10 return 0; } int func(int a) { a++; if(a==10) { return a; } else { func(a); } }
示例2:计算字符串的长度—使用递归
#include <stdio.h> int func(char *p); int main(int argc,char **argv) { printf("%d\n",func("1234567890")); //10 return 0; } //计算字符串长度 int func(char *p) { if(*p=='\0') { return 0; } return 1+func(p+1); } /* 演示递归函数的返回过程: a(); //3 int a() { return 1+b(); } int b() { return 1+c(); } int c() { return 1; } */