C语言中的符号的技巧

一、注释符号

C语言中的符号:



奖项:Best of show

功能:处理三种文件格式(PGM、PPM和ASCII Art)的降采样工具

作者:Google华裔工程师Don Hsi-Yun Yang


注释符号,下面哪些注释是正确的。

#include <stdio.h>int main()

{    

int/*...*/i; // 注释由空格来代替,所以是正确的    

char* s = "abcdefgh      //hijklmn"; // 在双引号中的注释会当成字符,所以也不会报错,但与程序愿意不符        

//Is it a \    valid comment? // ‘\’接续符,整个语句就是一个注释       

  in/*...*/t i; //会报错      

  return 0;

}

gcc编译后出错结果:

[root@localhost 1]# gcc 1-1.c 

1-1.c: In function ‘main’:1-1.

c:11: error: ‘in’ undeclared (first use in this function)1-1.

c:11: error: (Each undeclared identifier is reported only once1-1.

c:11: error: for each function it appears in.)1-1.

c:11: error: expected ‘;’ before ‘t’

注释规则小结:

1、编译器会在编译过程删除注释,但不是简单的删除而是用空格代替

2、编译器认为双引号括起来内容都是字符串,双斜杠也不例外

3、"/*........*/"型注释不能被嵌套

4、你觉得y = x/*p是什么意思?

作者本意:把x除以*p的结果赋值给y

编译器:将/*作为一段注释的开始,把/*后的内容都当成注释内容,直到*/出现为止

在编译器看来,注释和其它程序元素都是平等的,所以,作为程序员也不能看轻注释。

5、出色注释怎么来写

1、注释应该准确易懂,防止二义性,错误的注释有害而无利

2、注释是对代码的提示,避免臃肿和喧宾夺主

3、一目了然的代码避免加注释

4、不要用缩写来注释代码,这样可能会产生误解

5、注释用于阐述原因而不是用于描述程序的运行过程

下面是一个漂亮程序的截取部分:

/*
  ========================================================================

  FILE:  Form.c
  
  SERVICES:  

  GENERAL DESCRIPTION: Concrete implementation of RootForm and base IForm
  methods

  ========================================================================
  ========================================================================
    
               Copyright ?1999-2005 QUALCOMM Incorporated 
                     All Rights Reserved.
                   QUALCOMM Proprietary/GTDR
    
  ========================================================================
  ========================================================================
*/
/*==================================================================================
                         XXXXXXX Confidential Proprietary
                   (c) Copyright XXXXXXX - All Rights Reserved

Revision History:
                         Modification
  Author                     Date        CR Number      Major Changes
----------------------   ------------   ------------   ----------------------------
Daniel Rossler            01/18/2007     LIBkk94550    Add check for NULL pointers
                                                       in order to avoid a panic
==================================================================================*/
   // If there are still forms on the stack, then we need to set up several things:
   //   1. The topmost form is the active form
   //   2. All other forms are not active
   //   3. The topmost form is being listened to via mlFormActive
   //   4. The topmost non-popup form is being listened to via mlFormTopmostNonPopup
   //   5. The topmost non-popup form and all popup forms on top of it are shown
   //   6. Forms below the topmost non-popup form are now shown

二、接续符和转义符

下面是C语言中程序:

#include <stdio.h>

#def\
ine MAX\
255

int main()
{
/\
/这是\
\
注释

i\
n\
t\
 *\
 p\
= \
 NULL;

printf("%0X\n", p);
    
return 0;
}

接续符的使用:

1、编译器会将反斜杠剔除,跟在反斜杠后面的字符自动解到前一行

2、在接续单词时,反斜杠之后不能有空格,反斜杠的下一行之前也不能有空格

3、接续符适合在定义宏代码块时使用

下面是一个宏代码块的使用实例:

#include <stdio.h>


#define SWAP(a,b) \ // 使用define的原因就是
{                    \ // define定义宏块必须要在一行内完成
    int temp = a;        \
    a = b;           \
    b = temp;     \
}

/*
void swap(in a, int b)
{
int tmp = a;
a = b;
b = tmp;
}*/
// 如果使用这个函数,不能实现a与b的替换,
// 因为这里进行的是参数的值传递,它实现的是a和b拷贝的实现

int main()
{
    int a = 1;
    int b = 2;
    
    SWAP(a,b);


    printf("a=%d, b=%d\n", a, b);
    
    return 0;
}

宏块的使用实际就是代码块的替换。

C语言中的转义符(\)主要用于表示无回显字符(实际代表一个动作),也可用于表示常规字符

\n 回车换行 \t 横向挑到下一制表位置  \v 竖向挑格\b 退格\r 回车\f 走纸换页\\ 反斜杠“\”\' 单引号\a 鸣铃

\ddd 1-3位八进制数所代表的字符 \xhh 1-2位十六进制数所代表的字符

小结:

1、C语言中的反斜杠(\)同时具有接续符和转义符的作用

2、当反斜杠作为接续符使用时可直接出现在程序中

3、当反斜杠作为转义符使用时需出现在字符或字符串中

三、单引号和双引号

下面是一个实例:

#include <stdio.h>

int main()
{
    //char* p1 =  1 ;  //p指向内存地址为1
    //char* p2 = '1';  //字符在内存中实际是一个整数,它指向地址为49的内存
    char* p3 = "1";

    //printf("%s, %s, %s", p1, p2, p3);


    printf('\n'); //printf的原型函数为int printf(char const *format, ... );所以理由同
          //char *p2 = '1'; \n 就是10;出现段错误
    printf("\n");
    
    return 0;
}

1、C语言中的单引号用来表示字符常量

2、C语言中的双引号用来表示字符串常量

'a'表示字符常量

在内存中占1个字节

‘a’+1表示‘a’的ASCII码加1,结果为‘b’

“a”表示字符串常量

在内存中占2个字节

“a” + 1表示指针运算,结果指向“a”结束符‘\0’

混淆概念的代码:

#include <stdio.h>

int main()
{
    //char c = " ";//将字符串赋值给一个字符,字符串实际代表一段包含字符串的
             //内存地址,而将它赋值给char,会被截断,发生无法预料的结果
     
    char c = '';
    
   // while( c=="\t" || c==" " || c=="\n" )
   while( c=='\t' || c==' ' || c='\n' )
   
    {
        scanf("%c", &c);
    }
    
    return 0;
}

小结:

1、本质上单引号括起来的一个字符代表整数

2、双引号括起来的字符代表一个指针

3、C编辑器接受字符和字符串的比较,可意义是错误的

4、C编译器允许字符串对字符变量赋值,其意义是可笑的

清晰基本概念,远离低级错误

四、逻辑运算符

逻辑运算符的短路问题分析代码:

#include <stdio.h>

int main()
{
    int i = 0;
    int j = 0;
    
    if( ++i > 0 || ++j > 0 )
    {
        printf("%d\n", i);
        printf("%d\n", j);
    }
    
    return 0;
}


#include <stdio.h>

int g = 0;
int f()
{
    return g++;
}


int main()
{
    if( f() && f() )  //第一个f()为假,后++导致返回为0,而后加加。短路了。
    {
        printf("%d\n", g);
    }
    
    printf("%d\n", g);
    
    return 0;
}

1、程序中的短路

短路规则:

》||从左向右开始计算,当遇到真的条件时停止计算,整个表达式为真;所有条件为假时表达式才为假。

》&&从左向右开始计算,当遇到假的条件时停止计算,整个表达式为假;所有条件为真时表达式才为真。

2、“!”到底是神马?

#include <stdio.h>

int main()
{
    printf("%d\n", !0);
    printf("%d\n", !1);
    printf("%d\n", !100);
    printf("%d\n", !-1000);
    
    return 0;
}

C语言中的逻辑符“!”只认得0,只知道见了0就返回1.因此当其作用的值不为0时,就返回0,可见其对0的钟爱。

3、三目运算符(a ? b : c)可以作为逻辑运算符的载体

规则:当a的值为真时,返回b的值;否则返回c的值

#include <stdio.h>

int main()
{
    int a = 1;
    int b = 2;
    int c = 0;
    
    c = a < b ? a : b;
    
    //(a < b ? a : b) = 3; //返回为a的右值,不能用3进行赋值
    *(a < b ? &a : &b) = 3; //返回a的地址,使用指针可以实现赋值操作
    
    printf("%d\n", a);
    printf("%d\n", b);
    printf("%d\n", c);
    
    return 0;
}

五、位运算符分析

在C语言中的位运算往往用于外设的操作,通过串口和并口去操作外设。

乘除余和加减的优先级都要高于位运算符

& 按位与 | 按位或^ 按位异或~ 取反<<左移>>右移

位运算符符合交换律和结合律

1、C语言中为什么还需要运算符?

位运算往往运行速度快,这对于我们的嵌入式设备来说是非常重要的。

2、左移和右移

# 左移运算符<<将运算数的二进制位左移

规则:高位丢弃,低位补0

# 右移运算符>>把运算数的二进制位右移

规则:高位补符号位,地位丢弃

3、0x1 << 2+3的值会是什么?

先算2+3,结果为32

4、位运算符分析

防错准则:

1、避免位运算符,逻辑运算符和数学运算符同时出现在一个表达式中。

2、当位运算符,逻辑运算符和数学运算符需要同时参与运算时,尽量使用括号()来表达计算次序

小技巧:

# 左移n位相当于乘以2的n次方,但效率比数学运算符高

# 右移n位相当于除以2的n次方,但效率比数学运算符高

下面是一个使用不同方式实现交换操作

#include <stdio.h>


// 要使用一个临时变量,多使用了一个资源
#define SWAP1(a,b) \
{                  \
    int temp = a;  \
    a = b;         \
    b = temp;      \
}


//进行了改进,但a+b有可能溢出。它适用于所有类型
#define SWAP2(a,b) \
{                  \
    a = a + b;     \
    b = a - b;     \
    a = a - b;     \
}

//这是一个神奇的方法,避免了溢出。但只用于int型,float不可以
#define SWAP3(a,b) \
{                  \
    a = a ^ b;     \
    b = a ^ b;     \
    a = a ^ b;     \
}

int main()
{
    int a = 1;
    int b = 2;
    
    SWAP1(a,b);
    SWAP2(a,b);
    SWAP3(a,b);
    
    return 0;
}

面试题详解:

/*
有一个数列,其中的自然数都是以偶数次形式出现,只有
一个自然数出现的次数为奇数次。编写程序找出这个自然数
*/

问题的解决方案:

A:

1、将数列排序

2、遍历数列并且计数

缺点:时间复杂度高,也就是说耗时。

B:

1、遍历数列找到最大值max

2、动态申请max+1的int型数组b

3、把b数组的每个元素清零

4、for循环

{

b[a[i]]++;

}

5、for遍历b数组,寻找奇数元素,就是奇数次出现的自然数,此自然数就等于其所在的数组下标号。

缺点:空间换时间,秏空间比较大。

C:就是下代码实现。使用位运算来实现查找奇数次自然数

#include <stdio.h>
#define DIM(a)(sizeof(a)/sizeof(*a))

int main()
{
int a[] = {2, 3, 5, 7, 2, 2, 2, 3, 5, 7, 1, 1, 1};
int find = 0;
int i = 1;

for(i = 0; i < DIM(a); i++)
{
find = find ^ a[i];   //通过数列所有数的异或操作,最后只剩余了出现奇数次的元素
}

printf("find = %d", find);

return 0;
}

课后思考:

1、&&,||,!与&,|,~ 的意义是否相同?它们可以再条件表达式中交替使用吗?为什么?

答:它们是不同的。&&,||,!是对数据整体来操作,而&,|,~ 是对数据按位来操作的,所以它们意义不同,最终结果也不同。因此不可以在条件表达式

中相互替换。

2、1<<32的结果是什么?1<<-1的结果是什么?为什么?

当使用1<<32的时候,程序会出现警告:

[Warning] left shift count >= width of type [enabled by default] 

当使用1<<-1的时候,程序也会出现警告:

 [Warning] left shift count is negative [enabled by default] 

个人解释:

我们在进行位移操作时一定要注意:

1、移动位数保证非负

2、移动位数不能超过类型宽度

六、++,--操作符使用分析

1、一对令人头疼的兄弟

int i = 3;

(++i) + (++i) + (++i);

上面这个表达式的值是不确定的,因为这是C语言的一段灰色地带,对于不同的编译器会有不同的编译方式,也就对应着不同结果。

一共有三种编译方式:

1、按从左到右的顺序依次来计算,即4+5+6

2、先从左边计算前两个的++,后依次计算。即5+5+6(5+5+6+7...)

3、先计算所有++,而后再相加,即6+6+6

gcc和g++采用的是第二种方式。

2、令人头疼的逗号表达式

逗号表达式不是C语言的灰色地带,它有自己明确的规则:从左到右的顺序求值,最后一个表达式的值就是逗号表达式的值

int x;

int i = 3;

x = (++i, i++, i+10);

运行结果为x = 15;

笔试面试中的++i+++i+++i

上式的阅读要使用标准的阅读方法,贪心法

1、编译器处理的每个符号应该尽可能多的包含字符

2、编译器以从左向右的顺序一个一个尽可能多的读入字符

3、当即将读入的字符不可能和已读入的字符组成合法符号为止(而使用空格可以阻止贪心阅读,所以我们在编写代码应该实时的使用空格)

下面是一个实例:

#include <stdio.h>


int main()
{   
    int i = 0;
   //int j = ++i+++i+++i;
   //上式使用贪心法阅读到++i++ 等效于2++ ,显然出现了错误
    
    int a = 1;
    int b = 2;
    int c = a+++b;
    printf("%d\n", c); // c的值为3
    printf("%d\n", a); //a的值为2
    
    int* p = &a;
    
    b = b/*p; //报错了,这里成了注释,空格可以终止贪心,b = b / *p;
      
    return 0;
}

六、优先级和类型转换的分析



下面是一个运算符优先级的实例:

#include <stdio.h>
#include <malloc.h>

typedef struct _demo
{
    int* pInt;
    float f;
} Demo;

int func(int v, int m)
{
    return (v & m != 0); //==和!=优先级大于位操作符 
}

int main()
{   
    Demo* pD = (Demo*)malloc(sizeof(Demo));
    int *p[5]; //[]优先级高于* ,指针数组
    int *f(); //()优先级高于* ,指针函数
    int i = 0;
    
    i = 1, 2; //所有运算符','优先级最低 
    
    //*pD.f = 0; //.运算符优先级高于* ,所以会报错 
    pD->f = 0;
    
    free(pD);
    
    return 0;
}

C语言隐式类型转换:

1、算术运算式中,低类型转换为高类型

2、赋值表达式中,表达式的值转换为左边变量的类型

3、函数调用时,实参转换为形参的类型

4、函数返回值,return表达式转换为返回值类型


代码分析:

#include <stdio.h>


int main()
{
char c = 'c';
short s = 0;

s = c;

printf("%d\n", sizeof(s+c));  //结果为4
return 0;
}


#include <stdio.h>

int main()
{
    int i = -2;
    unsigned int j = 1;
    
    if( (i + j) >= 0 )
    {
        printf("i+j>=0\n");
    }
    else
    {
        printf("i+j<0\n");
    }
    
    printf("i+j=%0x\n", i + j);
    printf("i+j=%d\n", i+j);
    
    return 0;
}

运行结果为:

i+j>=0

i+j = ffffffff

i+j = -1

说明 -1 的补码就是 ffffffff ,因为负数在计算机中是以补码的形式存储的。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值