译注:本文摘编自 Quora 的一个热门问答贴。 请在linux系统下测试本文中出现的代码

Andrew Weimholt 的回复:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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. 声明紧随用途之后

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

1
2
3
4
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类型数组的指针 */

更多详情请看这里: Brian Bi’s answer to C (programming
language): Why doesn’t C use better notation for pointers?

2. 指定初始化:

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

1
2
3
4
5
6
struct

Foo {
    int

x;
    int

y;
    int

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

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

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

1
2
3
int

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

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

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

3. 受限指针(C99):

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

1
2
3
4
5
6
int

f(
const

int
*
restrict x,
int*
y) {
    (*y)++;
    int

z = *x;
    (*y)--;
    return

z;
}

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

1
2
3
int

f(
const

int
*
restrict x,
int*
y) {
    return

*x;
}

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

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

4. 静态数组索引(C99)

1
2
3
void

f(
int

a[
static

10]) {
    /*
... */
}

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

1
2
3
void

f(
int

a[
const])
{
    /*
... */
}

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

5. 泛型表达式(C11)

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

1
2
3
4
5
#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次方而不产生误差。这是为什么呢?

程序:

1
2
3
4
5
6
#include
<stdio.h>
#include
<math.h>
int

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

0;
}

输出结果:

1
740298315191606967520227188330889966610377319868419938630605715764070011466206019559325413145373572325939050053182159998975553533608824916574615132828322000124194610605645134711392062011527273571616649243219599128195212771328

答案:

这个问题包含两个部分。

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

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

如果你尝试打印3^474,

程序:

1
2
3
4
5
6
#include
<stdio.h>
#include
<math.h>
int

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

0;
}

输出结果:

14304567688284661153278974752312031583901259203711201647725006924333106634519194823303091330277684776547167093155518867557708479462413116497799842448027156309852771422896137582164841870381535840058702788340257784498862132559872

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

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

Utkal Sinha

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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;
}

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

1
2
3
4
5
6
7
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 语句

1
2
3
4
5
6
7
8
9
static

void

foo (
void)
{ }
static

void

bar (
void)
{
return

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

main (
void)
{
bar();
return

0;
}

4. 逗号表达式的使用

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

1
2
3
4
for

(
int

i=0; i<10; i++, doSomethingElse())
{
  /*
whatever */
}

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

1
int

j = (
printf("Assigning
variable j\n"
),
getValueFromSomewhere());

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

5. 将结构体初始化为0

struct mystruct a = {0};

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

6. 多字符常量

int x = 'ABCD';

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

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

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

main() {
    int

a = 3;
    float

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

0;
}

* 符号可以达到这一目的

希望这些可以帮助到大家

此致敬礼

Vivek Nagarajan

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

如果你写:

1
2
3
4
5
6
7
#include
<stdio.h>
 
void

main()
{
    printf
#include
"fragment.c"       
}

fragment.c 包含:

1
("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扩展语法)

1
2
3
4
5
switch(c)
{
  case

'A'

...
'Z':
//do
something
  break;
  case

1 ... 5 :
//do
something
}

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

1
printf("%d",0b1101);
//
prints 13

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

1
main;

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

Karan Bansal

scanf()的力量

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

读取一个字符串:

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

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

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

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

1
scanf("%s
%s"
,
temp, last_name);
//典型答案,使用一个临时变量
1
2
3
scanf("%s",
last_name);
scanf("%s",
last_name);
//
另一种答案,使用一个变量但是调用两次 `scanf()`
1
2
scanf("%*s
%s"
,
last);
//最佳答案,因为你不需要额外的变量或是调用两次`scanf()`

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

Afif Ahmed

~-n 等于n-1

-~n 等于n+1

原因:

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

C语言有哪些鲜为人知的特性?的更多相关文章

  1. 文本编辑利器Notepad++ 10个强大而又鲜为人知的特性【转】

    文本编辑利器Notepad++ 10个强大而又鲜为人知的特性 - 为程序员服务

  2. Google Guava的5个鲜为人知的特性

    译文出处: 花名有孚   原文出处:takipi.com Google Guava有哪些比较冷门但却又实用的特性呢? 它是最流行的开源库之一,你应该听过它的大名,它诞生的地方正是人们举办真正的魁地奇比 ...

  3. (转)C++语言的15个晦涩特性

    原文链接: Evan Wallace   翻译: 伯乐在线- 敏敏 译文链接: http://blog.jobbole.com/54140/ 这个列表收集了 C++ 语言的一些晦涩(Obscure)特 ...

  4. C++你不知道的那些事儿—C++语言的15个晦涩特性

    这个列表收集了 C++ 语言的一些晦涩(Obscure)特性,是我经年累月研究这门语言的各个方面收集起来的.C++非常庞大,我总是能学到一些新知识.即使你对C++已了如指掌,也希望你能从列表中学到一些 ...

  5. Go 语言简介(下)— 特性

    希望你看到这篇文章的时候还是在公交车和地铁上正在上下班的时间,我希望我的这篇文章可以让你利用这段时间了解一门语言.当然,希望你不会因为看我的文章而错过站.呵呵. 如果你还不了解Go语言的语法,还请你移 ...

  6. Go语言入门(一)特性、安装、环境搭建、第一个程序等

    Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性.谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮 ...

  7. Python语言的2个典型特性

    [动态编程语言] 学习过C.C++的同学都知道,使用这二者编写代码时,变量在使用前必须通过类似"int a=123"的语句提取申明,然后在真正赋值的时候校验取值跟类型是否相符.也就 ...

  8. 面试问题之C++语言:面向对象的三大特性

    转载于:https://www.cnblogs.com/BEN-LK/p/10720249.html 面向对象的三大特性:封装.继承.多态 封装:就是把客观事物封装成抽象的类,并且类可以把自己的数据和 ...

  9. 转:C++语言的15个晦涩特性

    转自 http://blog.jobbole.com/54140/ 操作符重载和检查顺序 重载,(逗号),||或者&&操作符会引起混乱,因为它打破了正常的检查规则.通常情况下,逗号操作 ...

随机推荐

  1. 【并发编程】ThreadPoolExecutor参数详解

    ThreadPoolExecutor executor = new ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long ke ...

  2. kindeditor用法简单介绍

    最近做毕业设计用了一个叫做kindeditor的文本编辑工具,相信很多人都用过,这货和fckeditor差不多,个人感觉这个的皮肤更好看,而且对中文的支持更好,没那么容易出现中文乱码问题.下次记录一下 ...

  3. iOS中的两种搜索方式UISearchDisplayController和UISearchController

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 以前iOS的搜索一般都使用UISearchDisplayCon ...

  4. UNIX网络编程——客户/服务器程序设计示范(三)

    TCP预先派生子进程服务器程序,accept无上锁保护 我们的第一个"增强"型服务器程序使用称为预先派生子进程的技术.使用该技术的服务器不像传统意义的并发服务器那样为每个客户现场派 ...

  5. Android进阶(二十五)setTextColor()的参数设置方式

    setTextColor()的参数设置方式 查了下资料发现setTextColor()的参数可以写成以下形式: 直接使用颜色值 setTextColor(0xFF0000FF);//0xFF0000F ...

  6. Android的DataBinding原理介绍

    Activity在inflate layout时,通过DataBindingUtil来生成绑定,从代码看,是遍历contentView得到View数组对象,然后通过数据绑定library生成对应的Bi ...

  7. Android项目-高考作文-使用ORMLite抽象公共的Dao层

    1, 定义统一的Dao接口 public interface IDao<T> { public abstract T getSingleById(int id); public abstr ...

  8. SQL Server扫盲系列——镜像篇

    为方便查看,并以专题形式展示,所以我会把一些文章整合起来.本部分为SQL Server镜像系列: 本文出处:http://blog.csdn.net/dba_huangzj/article/detai ...

  9. EXCEPTION与ERROR的区别

    EXCEPTION与ERROR的区别

  10. tomcat的realm域

    Realm域,其实可以看成是一个包含了用户及密码的数据库,而且每个用户还会包含了若干角色.也就是包含了用户名.密码.角色三个列的数据记录集合,如下图,最下面椭圆内的包含的整块即可以看成realm域.它 ...