C语言语法回顾

一、Makefile

例如项目有很多源文件,彼此关联。例如下面main.c用到了myutil.c。

1. myuti.h

1
2
int maxNum(int a, int b);
int minNum(int a, int b);

2. myutil.c

1
2
3
4
5
6
7
8
9
#include "myutil.h"
int max(int a, int b) {
return a > b ? a : b;
}
int min(int a, int b) {
return a < b ? a : b;
}

3. main.c

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include "myutil.h"
int main() {
int a = 3;
int b = 55;
int max = maxNum(a, b);
printf("max is %d\n", max);
return 0;
}

4.Makefile

Makefile可以完成多文件的编译。

注意Makefile文件名的大小写,下面的缩进行必须是tab,否则在make的时候会报错。

1
2
3
4
main.out:myutil.o main.c
gcc myutil.o main.c -o main.out
myutil.o:myutil.c
gcc -c myutil.c

main.out需要myutil.o和main.c,生成的方法就是下面的gcc myutil.o main.c -o main.out,注意下行是tab
myutil.o需要myutil.c,生成方法gcc -c myutil.c

二、重定向

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main() {
int i,j;
printf("please input i: \n");
scanf("%d",&i);
printf("please input j: \n");
scanf("%d",&j);
if (0 == j) {
fprintf(stderr, "j must be greater than 0\n");
return -1;
} else {
int div = i / j;
printf("%d / %d = %d\n", i, j ,div);
}
return 0;
}

1. 正常运行

1
2
3
4
# cc stdtest.c -o stdtest.o && ./stdtest.o
please input i: 12
please input j: 3
12 / 3 = 4

2.使用文件作为标准输入

1
2
3
cat input.txt
120
3

标准的输入是键盘,这里用文件替换了标准输入。

1
2
3
4
➜ cc stdtest.c -o stdtest.o && ./stdtest.o < input.txt
please input i:
please input j:
120 / 3 = 40

3.标准输出

改为0,触发错误输出

1
2
3
cat input.txt
120
0

1是标准输出,2是错误输出。

1
2
3
4
5
6
7
8
./stdtest.o 1>> stdout.log 2>>stderr.log < input.txt
# cat stdout.log
please input i:
please input j:
# cat stderr.log
j must be greater than 0

三、main函数参数

argv是参数个数,argc是参数字符串数组,第一个参数就是命令本身。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main(int argv, char *argc[]) {
printf("argv is %d\n", argv);
for (int i = 0; i < argv; i++) {
printf("%s\n", argc[i]);
}
return 0;
}

运行结果

1
2
3
4
5
6
7
8
./main.out a b c d e
argv is 6
./main.out
a
b
c
d
e

四、main返回值

在linux中每执行一条命令都会有返回结果,如果返回0代表成功,其他代表有异常。例如

1
2
3
4
5
6
7
8
9
# date
2018年 2月23日 星期五 17时21分28秒 CST
#echo $?
0
# jlaifej
zsh: command not found: jlaifej
echo $?
127

例如我们执行两条命令可以用&&连接,如果第一条成功了,才可以执行下一条

1
2
3
# date && ls ./
2018年 2月23日 星期五 17时23分04秒 CST
CMakeLists.txt MakeFile a.out cmake-build-debug main.c main.out myutil.c myutil.h

所以在main函数中正确的返回是0

五、宏

1.函数宏

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#define sum(x, y) x + y
int main() {
printf("Hello, World!\n");
int x = 3;
int y = 2;
printf("sum is %d\n", sum(x, y));
return 0;
}

注意没有{},也没有return

2.系统宏
1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
printf("This is line %d\n", __LINE__);
printf("File name is %s\n", __FILE__);
printf("Function name is %s\n", __func__);
return 0;
}

输出结果:

1
2
3
This is line 4
File name is /Users/carlosfu/CLionProjects/untitled4/main.c
Function name is main

有些开发需要判断当前的操作系统,例如redis中的overcommit,系统要判断当前操作系统。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
printf("helloworld");
#ifdef __APPLE__
printf("aaa");
#endif
return 0;
}

如果是linux,可以用这个。

1
__linux__

六、ifdef、ifndef、endif

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main() {
printf("start\n");
#ifdef HELLO
printf("hello hello");
#endif
return 0;
}

输出

1
start

如果换为ifndef,则输出

1
2
start
hello hello

如果定义了HELLO, 且为ifdef

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#define HELLO
int main() {
printf("start\n");
#ifdef HELLO
printf("hello hello");
#endif
return 0;
}

则输出

1
2
start
hello hello

七、malloc free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *ptr = (char *)malloc(24);
if (ptr == NULL) {
printf("malloc 24 bytes error");
exit(1);
}
strcpy(ptr, "hello world");
printf("ptr is %s\n", ptr);
free(ptr);
return 0;
}

输出:

1
ptr is hello world

八、结构体

结构体有很多种使用方法:

1
2
3
4
5
6
#include <stdio.h>
#include <stdlib.h>
typedef struct Point{
int x;
int y;
} point;

typedef给struct Point定义了一个别名point,这样理解就容易多了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int main(){
//第一种方法
point p1;
p1.x = 11;
p1.y = 22;
printf("x is %d, y is %d\n", p1.x, p1.y);
//第二种方法
point p2 = {33, 44};
printf("x is %d, y is %d\n", p2.x, p2.y);
//第三种方法
point *p3 = (point *)malloc(sizeof(point));
p3->x = 55;
p3->y = 66;
printf("x is %d, y is %d\n", p3->x, p3->y);
//第四种方法(struct Point等价于point,因为使用typedef)
struct Point *p4 = (struct Point *)malloc(sizeof(struct Point));
p4->x = 77;
p4->y = 88;
printf("x is %d, y is %d\n", p4->x, p4->y);
//输出结构体的第三种方法
point *p = p4;
printf("x is %d, y is %d\n", (*p).x, (*p).y);
printf("hello world\n");
return 0;
}

有几点需要注意:

point p相当于已经生成对象(Java: new Point())。
point p只是定义了空指针(Java: Point p)。
因为使用了typedef,所以struct Point p 等价于 point p; (point可以看做是struct Point的别名)
point
p如果赋值,需要使用malloc开辟空间。
point p,则(p).x等价于p->x,同时等价于point p中的p.x。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
typedef struct listNode {
void *value;
} node;
node *nodeCreate(int value) {
node *n;
n = malloc(sizeof(node));
n->value = value;
return n;
}
int main() {
node *n = nodeCreate(333);
printf("node value is %d", n->value);
return 0;
}

九、函数指针和指针函数

简而言之:

函数指针:实际是一个指针,只不过指向了函数地址:

1
2
3
4
5
int xxfunc(int a, int b) {
return 0;
}
int (* funPointer)(int, int) = &xxfunc;

指针函数:实际是一个函数,只不过返回结果是指针

1
2
3
4
int xxfunc(int a, int b) {
int result = xx;
return &result;
}

下面是测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
//函数指针:其实就是一个函数,只不过返回结果是个指针
int *addPointer(int a, int b) {
int sum = a + b;
return &sum;
}
int main() {
int a = 3;
int b = 4;
int sum = add(3, 4);
printf("sum is %d\n", sum);
//函数指针,其实就是个指针,只不过是指向了函数的地址
int (* myAdd)(int, int) = &add;
printf("myAdd address is %p\n", myAdd);
sum = myAdd(a, b);
printf("myadd sum is %d\n", sum);
//指针函数
printf("pointer is %p\n", addPointer(a,b));
return 0;
}

输出结果:

1
2
3
4
sum is 7
myAdd address is 0x1067ffe60
myadd sum is 7
pointer is 0x7fff59400844