欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C语言编程笔记丨C 有哪些鲜为人知的特性?

程序员文章站 2022-04-20 18:09:44
注:请在linux系统下测试本文中出现的代码。以下是一些优秀程序员的回答,特此译注。 Andrew Weimholt 的回复: switch语句中的case 关键词可以放在if-else或者是循环当中 Brian Bi 的回复: 1. 声明紧随用途之后 理解声明有一条很简单的法则,不过不是什么“从左 ......

注:请在linux系统下测试本文中出现的代码。以下是一些优秀程序员的回答,特此译注。

andrew weimholt 的回复:

switch语句中的case 关键词可以放在if-else或者是循环当中

; html-script: false ]switch (a)

{

    case 1:;

      // ...

      if (b==2)

      {

        case 2:;

        // ...

      }

      else case 3:

      {

        // ...

        for (b=0;b<10;b++)

        {

          case 5:;

          // ...

        }

      }

      break;

    case 4:

 

brian bi 的回复:

1. 声明紧随用途之后

理解声明有一条很简单的法则,不过不是什么“从左向右”这种没道理却到处宣传的法则。这一法则的观点是,一个声明是要告诉你,你所声明的对象要如何使用。例如:

; html-script: false ]int *p; /* *p是int类型的, 因此p是指向int类型的指针 */

int a[5]; /* a[0], ..., a[4] 是int类型的, 因此a是int类型的数组 */

int *ap[5]; /* *ap[0], .., *ap[4] 是int类型的, 因此ap是包含指向int类型指针的指针数组 */

int (*pa)[5]; /* (*pa)[0], ..., (*pa)[4] 是int类型的, 因此pa是指向一个int类型数组的指针 */

2. 指定初始化:

在c99之前,你只能按顺序初始化一个结构体。在c99中你可以这样做:

; html-script: false ]struct foo {

    int x;

    int y;

    int z;

};

foo foo = {.z = 3, .x = 5};

这段代码首先初始化了foo.z,然后初始化了foo.x. foo.y 没有被初始化,所以被置为0。

这一语法同样可以被用在数组中。以下三行代码是等价的:

; html-script: false ]int a[5] = {[1] = 2, [4] = 5};

int a[] = {[1] = 2, [4] = 5};

int a[5] = {0, 2, 0, 0, 5};

3. 受限指针(c99):

restrict关键词是一个限定词,可以被用在指针上。它向编译器保证,在这个指针的生命周期内,任何通过该指针访问的内存,都只能被这个指针改变。比如,在

; html-script: false ]int f(const int* restrict x, int* y) {

    (*y)++;

    int z = *x;

    (*y)--;

    return z;

}

编译器可能会假设,x和y 所指的并不是同一个int对象,因为如果它们指向了同一个对象,则x的值将可以通过y修改,这正是你保证不会发生的。因此,将允许编译器来优化f,就好像函数原本被写做如下这样:

; html-script: false ]int f(const int* restrict x, int* y) {

    return *x;

}

如果你违反协议向f传递两个指向同一int对象的指针时,将产生未定义行为。

我猜想,引入这一特性最初的动机之一是想让c语言在数值计算时可以fortran一样快。在fortran 中,默认假定数组不会重叠,因此只有你通过restrict 限定词来显式的告诉编译器数组不能重叠,编译器才能在c语言中进行这样的优化。

4. 静态数组索引(c99)

; html-script: false ]void f(int a[static 10]) {

    /* ... */

}

中,你向编译器保证,你传递给f 的指针指向一个具有至少10个int 类型元素的数组的首个元素。我猜这也是为了优化;例如,编译器将会假定a 非空。编译器还会在你尝试要将一个可以被静态确定为null的指针传入或是一个数组太小的时候发出警告。

; html-script: false ]void f(int a[const]) {

    /* ... */

}

你不能修改指针a.,这和说明符int * const a.作用是一样的。然而,当你结合上一段中提到的static 使用,比如在int a[static const 10] 中,你可以获得一些使用指针风格无法得到的东西。

5. 泛型表达式(c11)

这个表达式会在编译期间根据控制表达式的类型,在一个含有一个或多个备选方案的集合中做出选择。下面这个例子可以很好的说明这一切:

; html-script: false ]#define cbrt(x) _generic((x), \

                        long double: cbrtl, \

                        default: cbrt, \

                        float: cbrtf \

                        )(x)

因此,如果expr 是long double类型的, cbrt(expr) 被转换为cbrtl(expr),如果是float类型 则转换为cbrtf(expr) ,或是转换为cbrt(expr),如果是其他不同的类型(比如说double )。注意,_generic 可以用在宏以外的地方,但是用在宏里面最好因为c不允许你进行函数重载。

6. wint_t (c99)

我相信大家都知道wint_t 但是 wint_t 到底是个什么鬼东西呢?

好吧,记住fgetc 实际上并不会返回 char 。它会返回int。显然这是因为fgetc 必须返回返回一个与其他char 都不同的值,也就是eof,表示到达文件末尾。基于相同的原因,fgetwc并不返回wchar_t。它会返回一个类型,叫做wint_t 可以表示所有无效wchar_t 类型,包括weof,来表示到达文件末尾。

 

michal forišek

下面这段c程序可以准确的打印2的747次方而不产生误差。这是为什么呢?

程序:

; html-script: false ]#include <stdio.h>

#include <math.h>

int main() {

    printf("%.0f\n",pow(2,747));

    return 0;

}

输出结果:

; html-script: false ]740298315191606967520227188330889966610377319868419938630605715764070011466206019559325413145373572325939050053182159998975553533608824916574615132828322000124194610605645134711392062011527273571616649243219599128195212771328

答案:

这个问题包含两个部分。

其一,2的次方可以在double 中被准确的保存而不产生任何精度上的损失(这一结论直到2^1023都是对的,再往后就会产生上溢,得到一个正无穷的值)

另外一部分,很多人猜测是语言实现中的某些特殊情况导致的,但是实际上并非如此。的确,当输入的数据可以被2的某高次方整除时,有一部分代码被执行了,但是本质上这只是通常实现工作时的一个副作用。基本上,printf 在打印数字(任何类型)的时候只是做了从二进制到十进制的转换。并且由于结果对于浮点数可能会过大,printf 的内部实现包含和使用一个大整型实现,尽管在c中并没有大整型这种变量(在gcc源代码中,vfprintf.c 和dtoa.c 中包含了很多转换,如果你想要了解可以一看。)

如果你尝试打印3^474,

程序:

; html-script: false ]#include <stdio.h>

#include <math.h>

int main() {

    printf("%.0f\n",pow(3,474));

    return 0;

}

输出结果:

; html-script: false ]14304567688284661153278974752312031583901259203711201647725006924333106634519194823303091330277684776547167093155518867557708479462413116497799842448027156309852771422896137582164841870381535840058702788340257784498862132559872

结果仍然是一个很大的数且位数也正确,但是这一次却不够精确。这里会产生一个相对误差,因为3^474不能以双精度浮点数准确的表示。准确的数应该是这样的143045676882846603471…

译注:在linux系统上是可以的,在windows 64位上后面会有很多0

 

utkal sinha

我发现一些c语言特性或者是小技巧,我觉得只有很少的人知道。

1. 不使用加号来使数字相加

因为printf() 函数返回它所打印的字符的个数,我们可以利用这一点来使数字相加,代码如下:

; html-script: false ]#include<stdio.h>;

int add(int a,int b){

    if(if(a!=0&&b!=0))

        return printf("%*c%*c",a,'\r',b,'\r');

    else return a!=0?a:b;

}

int main(){

    int a = 0, b = 0;

    printf("enter the two numbers to add\n");

    scanf("%d %d",&a,&b);

    printf("required sum is %d",add(a,b));

    return 0;

}

利用位操作同样也可以做到:

; html-script: false ]int add(int x, int y)

{

    if (y == 0)

        return x;

    else

        return add( x ^ y, (x & y) << 1);

}

2. 条件运算符的用法

通常我们都这样使用它:

x = (y < 0) ? 10 : 20;

但是同样也可以这样用:

(y < 0 ? x : y) = 20;

3. 在一个返回值为void 的函数中写一个return 语句

; html-script: false ]static void foo (void) { }

static void bar (void) {

return foo(); // 注意这里的返回语句.

}

int main (void) {

bar();

return 0;

}

4. 逗号表达式的使用

通常逗号表达式会这样使用:

; html-script: false ]

for (int i=0; i<10; i++, dosomethingelse())

{

  /* whatever */

}

但是你可以在其他任何地方使用逗号表达式:

; html-script: false ]int j = (printf("assigning variable j\n"), getvaluefromsomewhere());

每条语句都进行了求值,但是表达式的值是最后一个语句的值。

5. 将结构体初始化为0

struct mystruct a = {0};

这将把结构体中全部元素初始化为0

6. 多字符常量

int x = 'abcd';

这会把x的值设置为0x41424344(或者0x44434241,取决于架构)

7. printf 允许你使用变量来格式化格式说明符本身

; html-script: false ]#include <stdio.h>

int main() {

    int a = 3;

    float b = 6.412355;

    printf("%.*f\n",a,b);

    return 0;

}

* 符号可以达到这一目的,希望这些可以帮助到大家

 

vivek nagarajan

你可以在奇怪的地方使用#include

如果你写:

; html-script: false ]#include <stdio.h>

void main()

{

    printf

#include "fragment.c"        

}

且fragment.c 包含:

; html-script: false ]("dayum!\n");

这完全没有问题。只要#include 包含完整可解析的c表达式,预处理器并不在意它放在什么位置。

 

vipul mehta

1. printf 格式限定符中指定(posix扩展语法)

printf("%4$d %3$d %2$d %1$d", 1, 2, 3, 9); //将会打印9 3 2 1

2. 在scanf 中忽略输入输入

scanf("%*d%d", &a);// 如果输入1 2,则只会得到2

3. 在switch 中使用范围(gcc扩展语法)

; html-script: false ]switch(c) {

  case 'a' ... 'z': //do something

  break;

  case 1 ... 5 : //do something

}

4. 使用前缀ob 来限定常数,使其被当做二进制数(gcc扩展语法)

; html-script: false ]printf("%d",0b1101); // prints 13

5.完全正确的最短的c语言程序

; html-script: false ]main;

译注:虽然编译没有error但是却不能执行

 

karan bansal

scanf()的力量

假定我们有一个数组char a[100]

读取一个字符串:

scanf("%[^\n]\n", a);//表示一直读取直到遇到'\n',并且忽略掉'\n'

读取字符串直到遇到逗号:

scanf("%[^,]", a);//但是这次不会忽略逗号

如果你想忽略掉某个输入,使用在% 后使用*,如果你想要得到john smith 的姓:

; html-script: false ]scanf("%s %s", temp, last_name); //典型答案,使用一个临时变量
; html-script: false ]scanf("%s", last_name);

scanf("%s", last_name);

// 另一种答案,使用一个变量但是调用两次 `scanf()`

; html-script: false ]scanf("%*s %s", last);

//最佳答案,因为你不需要额外的变量或是调用两次`scanf()`

顺便提一句,你应该非常小心的使用scanf 因为它可能会是你的输入缓冲溢出!通常你应该使用fgets 和sscanf 而不是仅仅使用scanf,使用fgets 来读取一行,然后用sscanf 来解析这一行,就像上面演示的一样。

 

afif ahmed

~-n 等于n-1

-~n 等于n+1

原因:

当我们写-n时,实际上是以补码形式储存,所以-n 可以写成~n + 1,吧整个式子放在上面表达式的前面你就能明白原因了。

博主是一个有着7年工作经验的架构师,对于c++,自己有做资料的整合,一个完整学习c语言c++的路线,学习资料和工具。可以进我的q群7418,18652领取,免费送给大家。希望你也能凭自己的努力,成为下一个优秀的程序员!另外博主的微信公众号是:c语言编程学习基地,欢迎关注!