c语言符号深度理解和再认识(1)

简介: 一、注释符号初步了解一下;/* */这个是c语言风格,//是c++风格。(一)、注释的本质首先run代码引入主题:

一、注释符号

初步了解一下;/* */这个是c语言风格,//是c++风格。

(一)、注释的本质

首先run代码引入主题:

int main()
{
  int /*   */ i;//ok
  char* s = "abcdefgh     //hijklmn";//ok
  //Is it a\
  valid comment? //ok
  int /* */t j;//err
  return 0;
}

我们发现int /* */t j;是错误的。用Linux看预处理:

image.png

那么就是编译出错了。

结论:注释被替换,本质是替换成空格


有了这个总结,下面我们再来看一个例子:


image.png

这样就足以看出我们的注释是替换成了空格。

(二)、c语言注释风格嵌套问题

#include <stdio.h>
int main()
{
  /*
  printf("hello world!");
  printf("hello world!");*/ //不报错
  printf("hello world!");
  printf("hello world!");
  */   //报错
}

说明/* 总是与离它最近的 */ 匹配。

(三)、/和*的连用问题

#include <stdio.h>
int main()
{
  int x = 10;
  int y = 0;
  int z = 5;
  int* p = &z;
  y = x/*p;  //err 具有二义性
  return 0;
}

这里的x/*p,本以为是对x和 *p除法运算,实际上编译器把/ *当作了注释。最好对 *p加上(),这样就可以解决问题了。

(四)、注释的基本要求

注释应该准确、易懂,防止有二义性。

注释是对代码的“提示”,并不是文档。

对于全局数据(全局变量、常量定义)都必须加上注释。

注释的位置应与被描述代码相邻,必须在上方。

注释的缩进要与代码的缩进一致。

代码注释段时应注重why而不是how.

数值的单位一定要注释。

对于函数的入口/出口数据、条件语句、分支语句给注释。

在复杂的函数中,在分支、循环语句结束之后需要给注释。

例如:

#include <stdio.h>
int main()
{
    while(1)
    {
        if(){
        }
        else if{
        }
        else{
        }//end of else(对其进行注释)
        //........
        //非常多的判断代码
    }//end of while(对其进行注释)
    return 0;
}
  1. 对变量的范围进行注释,尤其是数。

具体就是这么多的推荐,主要是了解,后期可以做项目了,自然就会了。

二、条件编译

通过两个图可以直观了解条件编译是什么?

f56517d5854120914c800482862fb01d.png











e9f27b8441eb4289f02f1aef04a0d762.png

两个图进行对比,很容易知道条件编译的用途。

其实,在我们手机中,我们手机对应的设置中有个语言选项,有很多语言选项,在设置的时候,其原理就是条件编译实现的,代码并没有变化,而是类似于条件编译这种思维完成的。

二、斜杠符号(续航符)

(一)、介绍

#include <stdio.h>
int main()
{
    int a = 1;
    int b = 2;
    int c = 3;
    if(1 == a &&\
    2 == b &&\
    3 == c)
    {
        printf("hello human");
    }
    return 0;
}

那么我们可不可以在\之前带空格?

这个是可以的。

可不可以在\之后带空格?

这个是不行的,在续航符之后最好任何符号都不要带上。

(二)、续航符和换行符是有区别的

续航符号顾名思义就是对这一行后面补充,而换行符则是换行

(三)、转义

转义有两种转法:

  1. 字面转特殊
  2. 特殊转字面
#include <stdio.h>
int main()
{
  printf("\"");// "转义就是特殊转字面
  printf("hello \n human");// \n就是字面转特殊
  return 0;
}

(四)、有趣的换行:\n和回车:\r

首先理解回车和换行两个概念:

回车和换行并不是一回事,回车就是光标回到当前行的开始,换行就是光标换到下一行

了解了这,我们就知道我们按下键盘enter键的时候其实是回车换行或者说换行回车。

1.旋转符号例子

#include <stdio.h>
int main()
{
  int i = 0;
  const char *c = "|/-\\";
  while (1)
  {
    i %= 4; 
    printf("[%c]\r", c[i]);//\r是回车
    i++;
    Sleep(200);
  }
  return 0;
}

2.倒计时例子

#include <stdio.h>
int main()
{
  int i = 10;
  for (; i >= 0; i--)
  {
    printf("[%2d]\r", i);
    Sleep(500);
  }
  return 0;
}

三、单引号和双引号

(一)、介绍

单引号是字符,双引号是字符串

"abcd";//ok
"a";//ok
"";//ok
'abcd';//ok
'a';//ok
'';//err 报错原因是单引号中必须要有至少一个字符

上面我们对’abcd’这个语法看上去肯定很陌生,不应该单引号中只能有一个字符吗,为什么有四个字符并没有报错呢,还有一个有意思的,看代码:

#include <stdio.h>
int main()
{
    char c = 'a';//ok
    char a = 'ab';//ok
    char v = 'abc';//ok
    char x = 'abcd';//ok
    char y = 'abcde';//err
    return 0;
}

超出了四个字符就会报错,提示常量中字符太多。


下面我们用sizeof关键字来理解单引号和双引号。


daff38b93beac29c62297d6544d9f068.png

第一个sizeof中是1这个整型;第二个是字符串(含有\0);第三个按常理来说应该是字符1,打印出四个空间的大小,不可思议;第四个是字符打印出一个空间,没问题。

原因在于,c99的标准规定,'a’叫做整型字符常量,别看成是int类型

这样们再来看第三个就很简单了,来看第四个,sizeof©为什么是1个空间,原因就是本来是int类型的,但是c是char类型,当int类型转化为char类型时,存储会发生截断。所以是1个空间的大小。

(二)、补充

#include <stdio.h>
int main()
{
    char c = 'abcd';
    printf("%d\n",c);
    return 0;
}

输出结果是d,这里’a’是int类型,为什么输出d?就和大小端有关系,暂且不做研究。

四、为什么计算机需要ASCII

(一)、初步认识

计算机中只认识二进制,为了解决人的问题,人不擅长二进制,只认识语言文字,所以在数据传在显示器上也不可能是二进制,对应的是我们认识的文字,所以每个二进制对应的就有大众认识的文字,这就是映射关系,ASCII码表对应的就是映射关系。(映射是由显卡完成的)

(二)、ASCII表

注意:A-a=32,空格十进制数对应的是32

五、逻辑运算符

前提非零为真,零为假

(一)、 逻辑与符号 (&&)

&逻辑与就是两个或者多个表达式必须同时为真的时候,结果才为真

用一个实例说明:

#include <stdio.h>
int main()
{
  int i = 0;
  int j = 0;
  if ((++i > 0) && (++j > 0))
  {
    printf("yes!\n");
  }
  printf("%d,%d", i, j);
  return 0;
}//输出结果是yes! 1,1

(二)、逻辑或符号 (||)

逻辑或就是两个或多个表达式,必须至少一个为真,结果才为真

int main()
{
  int i = 0;
  int j = 0;
  if ((++i > 0) || (++j > 0))
  {
    printf("yes!\n");
  }
  printf("%d,%d", i, j);
  return 0;
}//输出结果是yes! 1,0

这段代码,我们发现表达式中只执行了前面的(++i>0)并没有执行后面是(++j>0),这种情况我们叫短路。

(三)、短路问题

#include <stdio.h>
int show()
{
  printf("yes!\n");
  return 1;
}
int main()
{
  int flag = 0;
  scanf("%d", &flag); 
  flag || show();
}输入0后打印出yes!

短路问题依据就是表达式从左依次执行

六、位运算符号

(一)、基本了解

按位与(&),按位或(|)、按位异或(^)、按位取反(~)。都是二进制进行运算的。

int main()
{
  printf("%d\n", 2 | 3);
  printf("%d\n", 2 & 3);
  printf("%d\n", 2 ^ 3);
  printf("%d\n", ~0);
  return 0;
}//输出结果是3 2 1 -1.

说明:

2|3就是2和3的按位或,2(10)|3(11) = 3(11),二进制中两个对应的值都为0,则为0,否则为1。

2&3就是2和3的按位与,2(10)|3(11) = 2(10),二进制中两个对应的值都为1,则为1,否则为0。

2^3就是2和3的按位异或,2(10) ^3(11) = 2(01),二进制中两个对应的值不相同时,则为1,否则为0。

~0就是按位取反,0(0000 0000 0000 0000 0000 0000 0000 0000),~0(1111 1111 1111 1111 1111 1111 1111 1111(补码)=1000 0000 0000 0000 0000 0000 0000 0001(原码) =-1 )

(二)、逻辑运算符和按位运算符区别

逻辑运算符对应的逻辑表达式,需要的是真假。而按位运算符是逐个比特位进行计算


(三)、按位异或(^)

  1. 任何数和0的按位异或都是这个数本身。


image.png

两个相同的数的异或是0。

image.png

按位异或支持交换律和结合律。

609b6fcb2bd708494da9efaf9274aa56.png

(四)、实例:交换两个数

  1. 第一种方法;创建临时变量
#include <stdio.h>
int main()
{
  int temp = 0;
  int a = 10;
  int b = 20;
  temp = a;
  a = b;
  b = temp;
  printf("%d,%d\n", a, b);
  return 0;
}
  1. 第二种方法:加法运算

注意:加饭运算中二进制有进位现象,如果两个足够大的数相加,那么返回时就会出现溢出问题

3.第三种方法:按位异或方法

#include <stdio.h>
int main()
{
  int a = 10;
  int b = 20;
  a = a ^ b;
  b = a ^ b;
  a = a ^ b;
  printf("%d,%d\n", a, b);
  return 0;
}

注意:异或运算只是对比特位进行比较判断,并不会出现二进制进位溢出问题


(五)、建议

位操作需要用宏定义好后再使用

实例

1.实现一个宏,对任意一个比特位设置为1

#include <stdio.h>
#define SetBit(x,n)  (x |= (1<<(n-1)))//1<<n表示1这个比特位向左移动n位
extern void ShowBit(int x);
//函数
void ShowBit(int x)
{
  int num = sizeof(x) * 8-1;//控制循环次数
  while (num>=0)
  {
    if (x & 1<<num)//先从最高位检验
    {
      printf("1");
    }
    else
    {
      printf("0");
    }
    num--;
  }
  printf("\n");
}
//主体
int main()
{
  int x = 0;
  //设置指定比特位为1,这里使用按位或
  //x表示那个数据设置比特位,n表示设置第几个比特位(从右往左)
  SetBit(x, 5);
  //SetBit(x,6);
  //SetBit(x,1);
  //等等....
  //显示32位比特位
  ShowBit(x);
  return 0;
}//输出结果是00000000000000000000000000010000,移动五位,第五位变为1

也可以对多个比特位设置,这里有宏定义,可以直接多次设置宏。

2.实现一个宏,对任意一个比特位设置为0

#include <stdio.h>
#define ClrBit(x,n) (x &= ~(1<<(n-1)))
extern void ShowBit(int x);
//ShowBit函数输出比特位
void ShowBit(int x)
{
  int num = sizeof(x) * 8-1;//控制循环次数
  while (num>=0)
  {
    if (x & 1<<num)//先从最高位检验
    {
      printf("1");
    }
    else
    {
      printf("0");
    }
    num--;
  }
  printf("\n");
}
//主体
int main()
{
  int x = 0xFFFFFFFF;
  //设置比特位为0
  ClrBit(x, 2);
  ClrBit(x, 3);
  ClrBit(x, 4);
  ClrBit(x, 5);
  //显示比特位
  ShowBit(x);
  return 0;
}//输出结果是11111111111111111111111111100001

(六)、整型提升问题

先run代码:

#include <stdio.h>
int main()
{
  char c = 0;
  printf("%d\n", sizeof(c));
  printf("%d\n", sizeof(~c));
  printf("%d\n", sizeof(c<<1));
  printf("%d\n", sizeof(c>>1));
  return 0;
}//输出结果是1 4 4 4 

这里发生了整型提升,这里整形提升就不细讲了,后期我们再深入讲解整型提升。

这里我们只需要知道计算机计算是在CPU中的寄存器进行的,一般情况下,在32位下,寄存器的位数也是32位,char类型只有八个比特位,只能填补低8位,那么高24位呢?就需要进行整型提升

(七)、左移和右移

1.基本了解

左移:最高位丢弃,最低为补零

右移:1. 无符号数:最低位丢弃,最高位补零(逻辑右移) 2.有符号数:最低为丢弃,最高位补符号位(算术右移)

//左移
#include <stdio.h>
void ShowBit(int x)
{
  int num = sizeof(x) * 8 - 1;//控制循环次数
  while (num >= 0)
  {
    if (x & 1 << num)//先从最高位检验
    {
      printf("1");
    }
    else
    {
      printf("0");
    }
    num--;
  }
  printf("\n");
}
int main()
{
  unsigned int x = 0xFFFFFFFF;
  x <<= 1;
  ShowBit(x);
  return 0;
}//输出结果:11111111111111111111111111111110,有符号数也是此结果,左移没有符号位差别
//右移
#include <stdio.h>
void ShowBit(int x)
{
 int num = sizeof(x) * 8 - 1;//控制循环次数
 while (num >= 0)
 {
  if (x & 1 << num)//先从最高位检验
  {
   printf("1");
  }
  else
  {
   printf("0");
  }
  num--;
 }
 printf("\n");
}
int main()
{
  signed int x = 0xFFFFFFFE;
  //1111 1111 1111 1111 1111 1111 1111 1110(原本的x)
  x >>= 1;
  ShowBit(x);
  return 0;
}//输出结果:11111111111111111111111111111111

丢弃问题

左移和右移都是在CPU中进行,参与移动的变量是在内存中的,所以需要把数据移动到CPU寄存器中,在进行移动。在大小固定的单元内,一定会有位置到“外边”的情况。(外边的理解就是超出了寄存器储存单元大小)

2.深入理解

//左移
#include <stdio.h>
int main()
{
  unsigned int a = 1;
  printf("%u\n", a << 1);
  printf("%u\n", a << 2);
  printf("%u\n", a << 3);
  return 0;
}//输出结果:2 4 8
//右移
#include <stdio.h>
int main()
{
  unsigned int b = 100;
  printf("%u\n", b >> 1);
  printf("%u\n", b >> 2);
  printf("%u\n", b >> 3);
  return 0;
}//输出结果:50 25 12
//算术右移
#include <stdio.h>
int main()
{
  int c = -1;
  //1111 1111 1111 1111 1111 1111 1111 1111(补码)
  printf("%d\n", c >> 1);
  printf("%d\n", c >> 2);
  printf("%d\n", c >> 3);
  return 0;
}//输出结果:-1 -1 -1 
//算术右移
#include <stdio.h>
int main()
{
  unsigned int d = -1;
  printf("%d\n", d >> 1);
  printf("%d\n", d >> 2);
  printf("%d\n", d >> 3);
  return 0;
}//不会报错,最高位补0




















相关文章
|
4月前
|
C语言
c语言编程练习题:7-16 计算符号函数的值
请编写程序计算该函数对任一输入整数的值。
82 0
|
4月前
|
C语言 C++ 索引
C语言符号——操作符详解
C语言符号——操作符详解
33 0
C语言符号——操作符详解
|
4月前
|
存储 机器学习/深度学习 自然语言处理
【进阶C语言】编译与链接、预处理符号详解
【进阶C语言】编译与链接、预处理符号详解
51 0
|
4月前
|
C语言
C语言陷阱——无符号数和有符号数的大小比较
C语言陷阱——无符号数和有符号数的大小比较
|
4月前
|
C语言
【C语言】C语言中的符号重载
【C语言】C语言中的符号重载
82 0
|
11月前
|
存储 小程序 程序员
8k字详解整型(int)/字符型(char)/浮点型(float)/有符号(signed)/无符号(unsigned)数据在内存中的存储【程序员内功修炼/C语言】
8k字详解整型(int)/字符型(char)/浮点型(float)/有符号(signed)/无符号(unsigned)数据在内存中的存储【程序员内功修炼/C语言】
99 0
|
11月前
|
存储 人工智能 编译器
C语言之(有关%d和%u的有关内容,输出方法)(有符号和无符号在内存中的存储情况)(整形无符号数和有符号数是如何进行计算的,整形无符号数和有符号数在循环中的应用举例)
C语言之(有关%d和%u的有关内容,输出方法)(有符号和无符号在内存中的存储情况)(整形无符号数和有符号数是如何进行计算的,整形无符号数和有符号数在循环中的应用举例)
375 0
|
算法 编译器 C语言
c语言符号深度理解和再认识(2)
一、注释符号 初步了解一下;/* */这个是c语言风格,//是c++风格。 (一)、注释的本质 首先run代码引入主题
102 0
|
机器学习/深度学习 C语言
C语言中常见的符号和注释的用法
C语言中常见的符号和注释的用法
222 0
【C语言】符号的深入理解(第二期)
上期我们讲到过逻辑或和逻辑与,他们得到的结果是真假值,但我们一定要区分清楚,按位运算符 "|" 和 "&" 与逻辑运算符 "||" "&&" 是完全两个概念。
【C语言】符号的深入理解(第二期)