这不是 Bug,而是语言特性

今天遇到一个奇怪的问题: 一个 C 语言初学者写了这样的错误代码: switch (a) { case1: // do something break; case2: // do something break; case3: // do something break; default: break; } 错误很容易解决,但是,在编译这样的程序时,编译器居然没有报错??!! 那么 case1 case2 这些东西是哪里来的? 几番求证无果,最后在翻阅《C 专家编程》这本书的时候突然发现了原因: 首先像下面这样写是可以的: switch (i) { case 5 + 3: do_again: case 2: printf("I loop unremittingly\n"); goto do_again; default: i++; case 3: ; } 这里定义了一个标签 do_again,并且通过 goto 跳转。 原错误代码中 case1 case2 等被编译器理解为了标签。 由于 goto 不被建议使用,博主居然已经忘记了标签和 goto 的用法。 不管怎么说,问题解决了。 这不是 Bug,而是 C 语言的特性。 #(滑稽) 类似地,有如下代码: ...

十一月 5, 2020 · Aimer Neige

C 函数指针

函数指针在大型的 C 语言项目中十分重要,但是学校对它的介绍一带而过,国内一些介绍函数指针的文章十分粗浅甚至存在错误(这里就不点名了),因此博主写了这篇文章介绍函数指针,希望能够帮助一些 C 语言学习者。 文章部分内容翻译引用于 https://www.geeksforgeeks.org/function-pointer-in-c/ 函数指针的概念 类似变量,函数也会被分配一段存储空间,这段存储空间的首地址即被称做这个函数的地址。而且函数名表示的就是这个地址。既然是地址,我们就可以定义一个指针变量来存放,这个指针变量就叫做函数指针变量,简称函数指针。 函数指针的定义 下面的语句定义了一个指向函数的指针变量 p。其中 void *表示返回值,(*p) 表示 p 是一个指针变量, (int, float) 是函数的参数列表。 void *(*p) (int, float); 这样我们就得到了一个指针变量 p,它的类型为 void * (*)(int, float) 所以函数指针的定义方法为: 函数返回类型 (*指针变量名) (函数参数列表); 这里的函数参数列表类似函数声明,只需写出变量类型即可,并不需要定义变量。 即下面的俩种写法等价并且第二种写法中定义的变量并没有任何意义,建议使用第一种写法,不要使用第二种写法。 void *(*p) (int, float); void *(*p) (int a, float b); 如何对函数指针赋值 int Func(int x); // 函数的声明 int (*p) (int x); // 函数指针的定义 p = &Func; // 函数指针的赋值 p = Func; // 另一种可行的写法 注:对于俩种赋值写法的详细说明见对函数赋值和调用的一些说明 函数指针的调用 #include <stdio.h> int max(int a, int b); // 函数声明 int main(int argc, char const *argv[]) { int a = 12; int b = 32; int (*p)(int, int); // 函数指针定义 p = &max; // 函数指针赋值 int c = (*p)(a, b); // 函数指针的调用 printf("%d\n", c); // 输出 `32` return 0; } // 函数定义 int max(int a, int b) { return a > b ? a : b; } 另一种可行的写法 ...

十一月 4, 2020 · Aimer Neige

C 语言多线程基础

线程于进程的对比 thread 线程 有共享内存 process 进程 没有共享内存 gcc file.c -lpthread 创建线程 #include <stdio.h> #include <pthread.h> void *myfunc(void *args) { for (int i = 0; i < 50; i++) { printf("%d\n", i); } return NULL; } int main() { pthread_t th1; pthread_t th2; pthread_create(&th1, NULL, myfunc, NULL); pthread_create(&th2, NULL, myfunc, NULL); pthread_join(th1, NULL); pthread_join(th2, NULL); return 0; } 传入参数 #include <stdio.h> #include <pthread.h> void *myfunc(void *args) { char *name = (char *)args; for (int i = 0; i < 50; i++) { printf("%s %d\n", name, i); } return NULL; } int main() { pthread_t th1; pthread_t th2; pthread_create(&th1, NULL, myfunc, "th1"); pthread_create(&th2, NULL, myfunc, "th2"); pthread_join(th1, NULL); pthread_join(th2, NULL); return 0; } 分部计算 ...

十一月 1, 2020 · Aimer Neige

C 语言静态函数

在 C 语言中,函数默认是全局的。在函数前加“static”关键字可以使一个函数变成静态函数。例如,下面的函数 fun() 是静态的: static int fun(void) { printf("I am a static funciton.\n"); } 与 C 语言中的全局函数不同,对静态函数的访问仅限于声明它们的文件。因此,当我们想要限制函数的访问时,我们可以将函数定义为静态的。另外,如果我们想要在其他文件中使用相同的函数名,我们也可以将函数定义为静态的。 例如,我们在文件 file.c 中存储了下面的程序: // in file `file1.c` #include <stdio.h> static void fun1() { printf("fun1 called.\n"); } 然后,我们在文件 file2.c 中存储了这样的程序: // in file `file2.c` #include <stdio.h> int main(void) { fun1(); return 0; } 接下来,如果我们按照如下的命令编译: gcc file2.c file1.c 你会得到这样的链接错误: undefined reference to `fun1' 这是因为 fun1() 函数在 file1.c 中被定义为静态,因而不能在 file2.c 中引用。

十月 31, 2020 · Aimer Neige

GCC 基础

GCC 简介 GCC 是 Linux 下的编译工具集,是 GNU Compiler Collection 的缩写,包含 gcc 、 g++ 等编译器。这个工具集不仅包含编译器,还包含其他工具集,例如 ar 、 nm 等。 GCC 工具集不仅能编译 C/C++ 语言,其他例如 Objective-C 、 Pascal 、 Fortran 、 Java 、 Ada 等语言均能进行编译。 GCC 在可以根据不同的硬件平台进行编译,即能进行交叉编译,在 A 平台上编译 B 平台的程序,支持常见的 X86 、 ARM 、 PowerPC 、 mips 等,以及 Linux 、 Windows 等软件平台。 GCC 的 C 编译器 是 gcc,其命令格式为 : Usage: gcc [options] file... GCC 下默认文扩展名的含义: 文件扩展名 GCC 所理解的含义 *.c 该类文件为 C 语言的源文件 *.h 该类文件为 C 语言的头文件 *.i 该类文件为预处理后的 C 文件 *.C 该类文件为 C++ 语言的源文件 *.cc 该类文件为 C++ 语言的源文件 *.cxx 该类文件为 C++ 语言的源文件 *.m 该类文件为 Objective-C 语言的源文件 *.s 该类文件为汇编语言的源文件 *.o 该类文件为汇编后的目标文件 *.a 该类文件为静态库 *.so 该类文件为共享库 a.out 该类文件为链接后的输出文件 GCC 下有很多编译器,可以支持 C 语言,C++语言等多种语言 ...

十月 17, 2020 · Aimer Neige

面向小白的 C 语言随机数详解

// 生成一系列0~9之间的随机整数 #include <stdio.h> #include <stdlib.h> #include <time.h> int main() { srand( time(NULL) ); int ret; for (int i = 0; i < 20; i++) { ret = rand() % 10; // ret是一个0~9之间的随机整数 printf("%d ", ret); } return 0; } 输出结果 7 6 6 1 6 9 2 7 1 8 2 0 5 9 2 5 4 0 0 0 这是啥啊?看不懂啊。 不要着急,我来慢慢讲 首先是一个最简单的随机数: #include <stdio.h> #include <stdlib.h> int main() { int ret = rand(); printf("%d", ret); return 0; } 输出结果 41 这里用到了rand()这个函数,使用这个函数需要引入stdlib.h这个头文件,函数返回一个随机数,生成的随机数都是整数,所以我们把它交给一个int的变量。然后使用printf函数输出即可看到结果。当然,由于我们使用了printf函数,我们还需要引入stdio.h 这个头文件。 但是我们如果多次运行,发现结果始终是 41,这是怎么回事呢? 这里要了解 C 语言随机数的生成机制: 在 C 语言中,rand()函数可以用来产生随机数,但是这不是真真意义上的随机数,是一个伪随机数,是根据一个数,我们可以称它为种子,为基准以某个递推公式推算出来的一系数,当这系列数很大的时候,就符合正态公布,从而相当于产生了随机数,但这不是真正的随机数,当计算机正常开机后,这个种子的值是定了的,除非你破坏了系统,为了改变这个种子的值,C 提供了 srand()函数,它的原形是 void srand( int a)。 ...

一月 24, 2020 · Aimer Neige