C/C++由于有许多不同的编译器,导致main()函数的格式存在一些差别。为了规范,本文只接受ANSI-C在C89/C99中规定的main()函数定义。
个人所感,想到哪写到哪,比较跳跃。

#main()函数定义

main()函数存在以下两种形式:

1
2
3
4
5
6
7
8
9
10
11
/* 无参数形式 */
int main( void ) {
...
return 0;
}

/* 带参数形式 */
int main( int argc, char *argv[] ) {
...
return 0;
}

可以看到定义包括返回值(int)、函数名(main)、函数参数(无参与两个参数)、函数体、函数返回值(0与非0)。
下面将细细下详解。

#main()函数只能存在一个
需要注意的是这两种main()函数定义并不能同时存在,只能取其中一个。否则编译的时候会出现“conflicting types for ‘main’”的错误,如下图:

1
2
3
4
5
6
7
8
sodino:jni sodino$ gcc -o return_error return_error.c 
return_error.c:8:5: error: conflicting types for 'main'
int main(int argc, char *argv[]){
^
return_error.c:3:5: note: previous definition is here
int main(void) {
^
1 error generated.

main()函数是系统调用,会在系统中存在定义,当代码中出现两个不同的main()函数时,会以”conflicting types”提示错误。
这种提示与一般函数名重名的定义是不同的,如下代码中定义了两个test()函数,函数名相同但函数参数不同,则编译器报错是”redefinition of xx”

1
2
3
4
5
return_error.c:14:5: error: redefinition of 'test'
int test(int a){ return 0;}
^
return_error.c:13:5: note: previous definition is here
int test(){ return 0;}

有人可能会问?C语言不支持函数overload吗?答案是是的,函数重载是C++才支持的。把代码文件改为.cpp格式并使用g++编译,则可编译成功。

#函数参数
main()函数只有无参数与两个参数的两种形式。
####无参数的main(void)函数。
在标准的函数样式中,函数()内标明是void,指明这种样式定义的main函数是不接受任何参数的。
不在括号内添加void也能编译通过,但并在C语言中并不是一个好习惯。因为有这么个坑:
在C语言中函数括号内为空,表明该函数可以接受做任何个数的参数,且编译通过(gcc有wranning)。

1
2
3
4
5
6
7
8
9
void test() {
return;
};

int main(void) {
test();
test(1); // 编译通过,warning: too many arguments in call to 'test'
return 0;
}

在如上的示例代码中,调用了两次test(),在第二次调用中test()传入了一个参数1,而且会编译通过。(“▔□▔)

这一问题在C++中也被改过来了,在C++中,函数括号内有无void并不存在差别。C++标准规定如果没有对函数的参数列表进行定义就表示该函数不能传递任何参数。很显然这更符合普通人的思维方式。看下面的代码:

1
2
3
4
5
6
7
8
9
int test(){ return 0;} // test()等同于test(void)

int main() {
printf("hello world\n");
test(1, 2); // error: no matching function for call to 'test'
// note: candidate function not viable: requires 0 arguments, but
2 were provided
return 0;
}

所以在C/C++中建议无参数函数直接在函数声明的括号内标明void吧。

####有参数的main(int argc, char*argv[])函数。
第一个参数:argc argument count. int整型,参数个数。最小值为1。
第二个参数:argv argument vector.字符串向量(可以理解为字符串数组)。至少包含当前的启动程序名。
(ps:C/C++里的全名全是小写,加个大小写断词理解就可以方便多了,是不是早期的cer都是懒人…)

由于可以通过指向指针的指针来替换char*,所以main()也存在另一种形式:

1
int main(int argc, char** argv)

以下示例代码实现打印当前main函数参数

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(int argc, char *argv[]){
printf("I'm arg printer.\n");
printf("arg count=%d;\n", argc); // 打印出参数个数
for (int i = 0; i < argc; i ++) { // 遍历参数
printf("index=%d: %s \n", i, argv[i]);
}
return 0;
}

在Terminal终端运行结果如下:

1
2
3
4
5
6
7
sodino:jni sodino$ ./argPrint a b c
I'm arg printer.
arg count=4;
index=0: ./argPrint
index=1: a
index=2: b
index=3: c

#函数返回值
main()函数返回值分为0与非0两两种。当main()返回0时,表示程序正常运行并正常退出;当返回非0时,表示程序运行异常或运行结果不是预期结果,返回的数值用于表示程序逻辑中定义的结果标识。
在Mac/Linux Terminal终端下,运行任何一个命令后,都可以通过echo $?来查看main()函数的返回值,如下:

1
2
3
sodino:jni sodino$ gcc -o argPrint argPrint.c 
sodino:jni sodino$ echo $?
0

在Window下,则通过echo %ERRORLEVEL%来获取main()函数返回值,如下:

1
2
3
4
5
C:\Users\sodino> java
用法: java [-options] class [args...]
... ... ...
C:\Users\sodino> echo %ERRORLEVEL%
1

在Terminal下,&&逻辑控制符会受main()的返回值会影响。
如下示例中,通过&&先后运行return_error和hello_world,由于代码中设置return_error返回值是1,则不继续往下执行hello_world。
但如果使用的是&,则不会因return_error的结果影响后续的hello_world的执行。这是由于&&是逻辑控制符,只要有一个条件不符合结果,则不会继续往下执行;但&是运算操作符,会执行所有的命令。见下图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
### hello_world.c
#include <stdio.h>

int main(int argc, char *argv[]){
printf("hello world.\n");
return 0;
}

### return_error.c
#include <stdio.h>

int main(void) {
printf("run return_error:1.\n");
return 1;
}

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
sodino:jni sodino$ ./return_error && ./hello_world
run return_error:1.
sodino:jni sodino$ ./return_error & ./hello_world
[1] 1748
run return_error:1.
hello world.
[1]+ Exit 1 ./return_error
sodino:jni sodino$ ./hello_world & ./hello_world
[1] 1750
hello world.
hello world.
[1]+ Done ./hello_world