有时候学过的知识反过头来再看看总会有新理解,这可能就是所谓的温故而知新吧,最近读了一篇文章,觉得说的很对,感兴趣的可以读一下。
http://norvig.com/21-days.htmlnorvig.com
就像文章中说的一样,现在的人们都太急于求成,各种“XX天XXX语言从入门到大神”,大家有没有想一下,这现实吗?文章不是很长,建议大家都去看一下。
能够点进来看这篇文章的应该都是懂C语言的,那么相信大家也都写过很多代码了,那么我们先来看一下下面这几种main函数的定义,哪一个是正确的哪一个是错误的。
main()
main(void)
main(int argc,char *argv[])
main(int argc,char **argv)
void main()
void main(void)
void main(int argc,char *argv[])
void main(int argc,char **argv)
int main()
int main(void)
int main(int argc,char *argv[])
int main(int argc,char **argv)
相信大家或多或少都见过或这写过上面这这些main函数中的某一种,那么大家对这个C语言中最重要的一个函数有多少了解呢?有没有深究过该怎样写和为什么这样写呢?其实讲实话,我之前也是知其然不知所以然,因为觉得会用就好了,为什么还要深入的去了解呢?所以现在还是菜鸟的一个原因吧。。。之所以写这篇文章主要还是最近在做Linux程序开发,这技术水平不到可不就得深入去了解一番嘛,作为一个菜鸟码农就得不断去学习不断去充实自己。
其实上面的这几种main函数的定义在某种意义上来说是都是正确的,因为在不同的标准下编译都是可以编译通过的,但是这不代表可以编译通过我们就可以认为是正确的写法,既然有标准我们就要根据标准去写,毕竟有了规则大家才可以写出更好的代码,下图是C11标准中对main函数的说明,大家可以看一下。
下面我们来实际测试一下,看看编译器在不同标准下如何处理上面的几种情况,这里的测试环境是Ubuntu 16.04 & gcc 5.4.0,第一个测试代码:
#include
//不声明main函数类型
//main() 与 main(void) 是等价的
main(void){
printf("hello world!\r\n");
}
可以看出在C99与C11标准下编译会提示一个警告,说默认返回类型是int类型,但是在比较老的C90标准下是可以编译通过和运行的,我们再继续测试下一个:
#include
//声明main函数类型为void
//void main() 与 void main(void) 是等价的
void main(void){
printf("hello world!\r\n");
}
可以看出在三种标准之下是都可以编译通过的,但是这并不代表这就是正确的,我们再来看一下C11标准中是怎么讲函数的返回值的:
我们可以这样想,函数的返回值是判断这个函数执行是否成功的一个标志,就像你自己写某一个子函数时,不同的情况可能会对应不同的返回值,从而做出不同的处理,我们写一个程序最终是要被系统调用的,那我们定义一个void类型,系统该如何判断调用是否成功呢?所以这种写法是禁止的,不推荐的,而且这种写法在任何一个标准都没有声明过,我们继续测试下一个:
#include
//声明main函数类型为int
//int main() 与 int main(void) 是等价的
int main(void){
printf("hello world!\r\n");
}
可以看到我们这里虽然声明了int类型,却没有在程序中return一个返回值,但是在三种标准下都可以编译通过并执行,这是为什么呢?其实在前面图中有说明,如果我们没有写return语句,程序是会在执行到“}”时默认返回0,0表示程序执行成功,所以正确的写法应该如下:
#include
//声明main函数类型为int
//int main() 与 int main(void) 是等价的
int main(void){
printf("hello world!\r\n");
return 0;
}
在这几个测试代码中我们测试了不同类型的main函数在不同的标准下编译,我们也查看了标准中是推荐怎样写的,所以我们以后写的时候还是要根据标准来,有时候有一个良好的代码规范在合作开发中是非常必要的。
但是这不是本篇文章要讨论的重点,之所以写这篇文章是因为最近写的东西需要处理一些命令行参数,相信大家应该都接触过命令行参数吧,下面就给大家举个简单的栗子:
gcc -std=c11 -o hello hello.c
没错这就是我们刚刚用过的一条命令,gcc 是可执行文件,就像我们前面编译好的hello一样,是可以直接执行的,但是后面跟那么多参数是做什么的呢?其实简单来说就是根据不同的参数执行不同的命令,就像std(standard)参数一样,通过我们给出的不同参数执行不同标准下的编译标准。
那么这些参数是怎样传递进去的呢?这里就不得不提我们的main函数了,大家都知道main函数是整个程序的入口,程序的执行就是从这里开始,所以当然main函数就是我们的参数入口了,相信仔细阅读的你肯定在前面的图片中也有所发现了,这里我们再来看一下:
第一种就是不带参数的,第二种是带参数的,我们先上个简单代码:
#include
/**
*@author imliubo
*@brief main函数传参测试代码
*@param argc 传入的参数个数,argc>=1
*@param *argv[] 传入的参数的值,argv[0]=当前执行的程序名
**/
int main(int argc,char *argv[]){
printf("argc:\t argv:\r\n");
for(int i=0;i < argc;i++){
printf("%d\t %s\r\n",i,argv[i]);
}
printf("argc total:%d\r\n",argc);
return 0;
}
我们先来看一下不加任何参数运行会打印什么:
可以看到我们不加任何参数时,argc跟argv的值都是有变化的,因为默认的第一个参数是当前程序的名(包含路径),参数总个数为1,我们再来看看我们加几个参数会打印什么:
可以看到我们在命令行中的四个参数都打印出来了,加上默认的一共5个一个也没有少,参数与参数之间是通过空格区分的(如果想将两个参数合并成一个可以通过加上引号实现),那么我们该怎样利用这些参数呢?一个好的程序是可以配置的,不同参数会有不同的运行结果,那么我们写一个通过参数控制打印不同字符的程序,这里我们用最简单的方法去实现:
#include
#include
/**
*@author imliubo
*@brief main函数传参测试代码
*@param argc 传入的参数个数,argc>=1
*@param *argv[] 传入的参数的值,argv[0]=当前执行的程序名
**/
int main(int argc,char *argv[]){
for(int i=0;i < argc;i++){
if(strcmp("imliubo",argv[i])==0){
printf("Hi %s,how was going?\r\n",argv[i]);
}
if(strcmp("xiaomei",argv[i])==0){
printf("What a beautiful girl!\r\n");
}
}
return 0;
}
可以看出我们通过配置不同的参数,程序是不同的执行的结果,当然这是一种菜鸟程序员的写法,通过不断的比较去执行不同的代码,加入我们再在这个参数前面加上一个带有"-"修饰的字符,处理起来就要麻烦的多了,但是这种麻烦的问题肯定不只有我们才发现,所以已经有大神给我们写好工具了。
平常我们在处理很多繁杂的命令行参数时都会使用getopt()函数,比如像下面这条命令:
sudo ./nuwriter -m nand -d NUC972DF62Y.ini -t uboot -a 0x200 -w /home/imliubo/NUC972/WORKSPACE/COIDEA/BSP/03.uboot/nand_spl/u_boot_spl.bin -v
如果我们自己去解析这条命令中的参数,费时间不说,其中的逻辑还那么复杂,所以我们一般都是使用getopt()函数去解析,本篇文章就先到这,我会在下一篇文章中跟大家讨论一下。
文章难免有错误,如果有不对之处,还请及时指出我好及时改正。
欢迎关注我的专栏,我会不定期分享一些开发经验。