Hello World
就需要引入依赖,<stdio.h> 为标准 IO 库的头文件。printf
方法在标准输出(命令行)打印消息。#include <stdio.h> int main() { printf("Hello World!\n"); return 0; }
C 语言中的基础类型包括:
所有的类型均有默认初始值(0,0.0 和 '\0'),当你定义一个变量时,若不赋一个初始值,此变量为默认初始值。
C 语言中没有布尔类型的值,一般用整型数值类型表示,0 为 false,非 0 为 true。
类型变量所占的字节数是不同编译器的实现带来的事实标准,C 语言规范只规定了一些约束。
int a = 109; float b = (float)a; // b is 109.00 char c = (char)a; // c is 'm'
printf
方法来自标准库,此函数支持以模板字符串的方式打印消息,是 C 语言编写的程序向外输出消息的最直接通道。
printf("%d\n", 127); // 127 printf("%o\n", 177); // 177 八进制 printf("%x\n", 177); // b1 十六进制 printf("%f\n", 314.15); // 314.159000 printf("%.2f\n", 314.15); // 314.15 指定精度 printf("%e\n", 314.15); // 3.141590e+02 printf("%c\n", 'c'); // c printf("%s\n", "hello"); // hello
转字符:
\n
表示换行,类似常用的还有\t
(制表),\\
(反斜杠),\'
和\"
(引号),完整的转义字符表。
编译:将源码编译为可执行文件(在 linux 下默认以 out 为后缀名); 运行:执行
最原始的编译方法是直接调用 gcc(Mac 或 Linux 下应该自带了);
> gcc helloworld.c -o a.out
此时会在当前目录下生成新的可执行文件 a.out
(后缀名是可选的),然后执行 a.out
:
> ./a.out Hello World!
新建项目:
编译:
菜单项 Product -> Build,编译构建。构建产物可执行文件放在 XCode 资源目录中,可以在左侧 TOC 部分的 Products Group 下看到,可以 cd 到彼目录下,或将可执行文件拷贝出来运行。
编译和运行:
直接点击三角形的「播放」按钮,可编译并直接运行,还可以方便地打断点调试。
桌面平台的应用各集成环境, Visual Studio 或 XCode;跨平台或打包为三方包通常选择 CMake。
int i[3]
int i[] = {1,2,3}
,数组字面量有大括号括起来(写惯了 JavaScript 很容易犯错)。int i[3]
相当于 int i[] = {0,0,0}
。int i[3] = {1, 2, 3}; i[2] = 4; printf("%d\n", i[2]);
习惯了 JavaScript 中的数组,可能一开始比较难使用 C 中更接近底层的数组。其实 JavaScript 更接近 C++ 中的 Vector 的指针。C 的数组是不能被「赋值的」,你不能
int a[3]; int b[] = a;
(其实应该这样:int a[3]; int *b = a;
),这些疑惑等复习到指针相关的章节就解了。
\0
的元素的位置。char p[] = "hello"; printf("%s\n", p); // hello
上面这种初始化字符串的方法,我理解成一种语法糖,其背后相当于
char p[] = {'h','e','l','l','o','\0'};
。
分支体与循环体的写法和 JavaScript 几乎完全一样。
if else
分支:
int i = 1; if(i){ printf("i is not 0;\n"); }else{ printf("i is 0;\n"); }
switch case
分支:
int i = 1; switch(i){ case 0: printf("i is 0;\n"); break; case 1: printf("i is 1;\n"); break; default: break; }
for
循环:
// for for(int i = 0; i < 10; i++){ printf("i is %d\n", i); }
while
循环:
// while int j = 0; while(j < 10){ j++; }
do while
循环:
int k = 0; do{ if(k++ > 10){ break; } }while(1);
enum
关键字声明枚举类型。enum
中声明的字面量赋值给枚举类型的变量。unsigned int
(占 4 个字节),按照枚举声明的顺序依次为 0,1,2 等等;比如,将不同枚举类型中,声明次序一致的值拿出来比较,是相等的(编译时会产生警告)。enum A {aa, bb}; enum B {cc, dd}; void main(){ enum A c = aa; enum B d = cc; c == d; // true }
.
)符号访问结构体的属性值。struct Pair{ int a; float b; }; void main(){ struct Pair p = {1, 2.0}; printf("%d,%f\n", p.a, p.b); }
int add(int i, int j){ return i + j; }; void main(){ int sum = add(5,3); printf("%i\n", sum); // 8 }
指针是存储变量地址的变量,可理解为一个特殊的 usigned long 类型变量。在 64 位机器上,指针占 8 个字节,在 32 位机器上,占 4 个字节。
*
)。*
)引用指针指向的值,并进行读写。int v = 1; int *vp = &v; *vp += 1; // v 变成了 2
当我们不知道指针类型的时候(貌似是件常事儿?),可以用 void *
来指代,等到知道指针类型的时候再转回去,反正指针本身的大小是固定的嘛。
int i[3] = {1, 2, 3}; int *p = i; // 或 int *p = &i[0]; *(p+2) = 4; // 相当于 i[2] = 4; printf("%d\n", *(p+2)); // 相当于打印 i[2]
二维数组,即「数组的数组」,其内存也是连续分配的,可以通过移动指针来访问二维数组中的元素。
int arr[2][3] = {{1,2,3}, {4,5,6}}; int *p = arr[0]; *(p + (3 * 1) + 2) = 7; // 相当于 arr[1][2] = 7; printf("%d\n", arr[1][2]);
指针也是一种变量类型,自然可以被「指向指针的指针」所指。
int v = 1; int *pv = &v; int **pp = &pv; (**pp)++;
&
)。int add(int a, int b){ return a + b; } int sub(int a, int b){ return a - b; } int main(int argc, const char * argv[]) { int (*op)(int, int) = &add; printf("%i\n", (*op)(3, 1)); op = ⊂ printf("%i\n", (*op)(3, 1)); return 0; }
变量,数组,函数(我猜哦),结构体,其内存都是分配在栈上的;随着程序的运行,栈上的内存可能会被频繁地调整(也是我猜的哦)。所以,我们还可以手动从堆上去获取内存,并在合适的时候手动释放之。当内存比较大的时候,性能会比在栈上要好。
void *
类型的指针。int *p = (int *) malloc(8); p[0]=0; // 这样写也可以哦,和 *(p+0) 的是一样的 p[1]=1; printf("%d\n", p[1]); // 1 p = realloc(p, 12); p[2] = 2; printf("%d\n", p[2]); // 2 free(p);
#define
宏是直接对文本进行操作,使用不当容易引起编译错误。#ifdef
宏来避免重复声明。#define PI 3.14159 float c(float r){ return 2.0 * PI * r; }
使用 typedef
关键字为类型(基础类型,枚举或结构体)创建别名。
typedef int interger; typedef enum {Apple, Banana} Fruit; typedef struct { int a; float b; } Pair; void main(){ interger i = 1; Fruit fruit = Apple; Pair p = {1, 2.0}; }
main
函数作为入口。比如由 main.c
,add.c
和 add.h
组成的程序:
main.c
如下:
// main.c #include <stdio.h> #include "add.h" void main() { printf("%d\n", add(1,2)); }
add.c
如下:
// add.c #include "add.h" int add(int a, int b){ return a+b; }
add.h
如下:
#ifndef add_h #define add_h int add(int a, int b); #endif
GCC 编译时直接传入多个源码文件:
> gcc main.c add.c -o a.out
直接将源码放在同一个 group 下,编译器会自动识别源码文件并传给编译器。
简单复习一下几个最常用的标准库操作吧:
fopen
函数打开文件,获得文件句柄。r
模式为读,a
模式为追加写,w
为覆盖写。fgets
函数从文件中读取一行,读到文件末尾时会返回 '\0'
。fputs
函数向文件中写一行。fclose
函数关闭文件。#include <stdio.h> #define MAX 10000 void main() { FILE *s = fopen("a.txt", "r"); FILE *t = fopen("b.txt", "a"); char c[MAX] = ""; char *e; while((e = fgets(c, MAX, s)) != 0){ fputs(c, t); printf("%s", c); } fclose(s); fclose(t); }
strlen
取字符串的长度;strchr
取某个字符第一次出现的位置(指针);strcpy
将一个字符串复制到另一个字符串中;strcat
将一个字符串追加到另一个字符串中。#include <stdio.h> #include <string.h> #define MAX 1000 void main() { char s[MAX] = "hello world"; char t[MAX] = ""; int len = strlen(s); // len is 11 char *pl = strchr(s, 'l'); // (pl-s) is 2 strcpy(t, s); // t is "hello world" strcat(t, s); // t is "hello worldhello world" }