《C语言深度剖析》学习笔记----C语言中的符号

时间:2023-09-20 15:37:38

本节主要讲C语言中的各种符号,包括注释符、单引号双信号以及逻辑运算符等。

一、注释符

注释符号和注释在程序的预编译期就已经被解决了,在预编译期间,编译器会将注释符号和注释符号之间的部分简单的替换成为空格。

1.在程序中使用注释符号有以下几点需要注意

(1)如果注释符号在数据类型和变量之间,那么将不会影响程序的正常运行。

int/*...*/i;

(2)如果注释符号位于数据类型之间,那么程序将不会正常运行,因为一个数据类型关键字中间出现一个空格,那么数据类型关键字也就不再是关键字了。

/*不能正常运行*/
in/*...*/t i;

(3)如果字符串之间出现注释符号,程序仍然可以正常运行。

 char* s = "abcdefgh      //hijklmn";

(4)/**/这个注释符号是不能够被嵌套的,同时这个注释符是C90标准之前的C语言正式的注释符,在C90标准之后增加了//这个注释符。

(5)关于除法的运算在C语言中也要额外的注意

x/*p

这个程序的本意假如是我么想对x比上(*p)进行运算,但是最后的程序运行效果将是从x之后所有的代码全部被注释掉了。所以在写程序的时候多加一些括号吧,无论是运算还是优先级,利人利己。别像某考试那样。

2.注释符号是程序中必须存在的,某位大神说过,一个优秀的代码注释会在程序中占四分之一到三分之一,我没有看过太多的代码,所以自己先这么记了。还有就是注释不要乱用,而且注释也要讲求规范,同时写程序注释的时候要注释说明代码实现的功能,而不是去说明代码实现了什么操作,真的能去做编程的人都懂代码是怎么工作的。

二、接续符

1.定义:接续符号就是一个斜杠,用来通知编译器程序在这一行并没有结束,\之后的内容仍然属于这一行。同时注意,\绝对不可以有空格。同时在下一行的开始也绝对不可以有空格。

2.接续符号也要慎重使用,因为过多的使用接续符号会让程序显得很乱,同时,我们不是大神,不是大牛,只是菜鸟,所以淡定。

3.在编译器中,编译器会将程序的\剔除,那么在\后面的代码会自动提到这一行。

4.可以把一整块的代码定义成为一个宏,调用宏的时候就像调用函数一样。

5.\还有一个重要的使用方法,那么就是在字符和字符串中作为转义字符使用。

6.使用\来修饰宏代码块的历程如下:

#include <stdio.h>

#define SWAP(a,b) \
{ \
int temp = a; \
a = b; \
b = temp; \
} int main()
{
int a = 1;
int b = 2; SWAP(a,b); printf("a=%d, b=%d\n", a, b); return 0;
}

这个宏的主要操作是交换两个数的值,作为参数是进行复制的函数功能,我们只能用指针来完成这个功能,但是宏的使用可以使我们很简单来完成这个功能。在定义这个宏的时候使用接续符会让程序更加清晰,如果不使用接续符号,那么这个宏的本色其实是这样的:

#define SWAP(a,b) {int temp = a; a = b;b = temp;}

这样还好一些,如果宏的功能更多呢?那么是不是会更乱一些呢?

7.在C语言中转义字符主要用于表示无回显字符,也可以用于表示常规字符。常用的转义字符如下:

《C语言深度剖析》学习笔记----C语言中的符号

8.老唐总结:

(1)C语言中的\,有转义符和接续符两种作用。

(2)当\作为接续符的时候可以直接出现在程序中。

(3)当\作为转义符的时候需要出现在字符或者字符串中。

三、单引号和双引号

1.始终认为字符串和字符是C语言中的一个大难点,自己一直也没有好好看,包括scanf和printf的问题,看C primer的时候也给略过了,希望过几天强迫给自己一些时间去看吧。

2.在C语言中,单引号代表一个字符,双引号代表一个字符串常量。

(1)'a'在内存中占用一个字节。(2)"a"占用两个字节,因为字符串的结尾都会有\0作为结束,所以占用的内存是两个字节。(3)'a'+1表示'a'的ASCII码加1,结果为'b'

(4)"a"+1表示指针运算,结果指向"a"结束符'\0'

3.在C语言中,我们在注意野指针的同时还要注意给指针赋值的问题。首先看一个例程

#include <stdio.h>
#include <stdlib.h> int main ()
{
char *p1 = 1;
char *p2 = '1';
char *p3 = "1"; printf (" %d %d %d", *p1, *p2,*p3);
printf (" %d %d %d", p1, p2,p3); }

这个程序运行之后会崩溃,导致崩溃的语句是我们通过printf函数要打印出指针p1,p2,p3所指向的内容。但是当我们单独打印p1,p2,p3所代表的地址的时候,我们会发现,我们打印出来的地址截图如下:

《C语言深度剖析》学习笔记----C语言中的符号

通过上图可以发现,char *p1 = 1;其实就是把地址1给了指针变量p1,char *p2 = '1'是把1这个字符的ASC码(49)赋值给了指针p2,而第三个是把一个字符串的首地址赋值给了指针p3,如果我们只打印p3指针指向的内容,那么程序不会崩溃,因为p3指向的地址是一个合法的地址。而p1,p2的地址并不是合法的,因为无论什么系统中,很低的地址都是留给操作系统来使用的。

4.我最深刻的感受就是如果没有深刻理解字符和字符串,那么我们也会进行错误的赋值

#include <stdio.h>
int main()
{
char a = " ";
while (a=="\t" || a == " " || a == "\n")
{
scanf ("%c",&a);
}
return 1;
}

上面的程序无论在任何编译器中都不会进入while循环,因为在程序的第一个赋值语句中

char a = " ";

我们实际上是把一个字符串赋值给了一个字符,但是一个字符在32位机下占的是8位,而字符串占用的是32位,所以会发生截断,也就是说当进入while循环的时候,实际上就是将一个4字节长度的字符串在和一个1字节长的字符在比较,所以肯定进入不了while循环,导致程序直接退出。

5.老唐总结:

《C语言深度剖析》学习笔记----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;
}

打印结果如下图所示:

《C语言深度剖析》学习笔记----C语言中的符号

很难受吧?优先级,顺序点没有一个是好朋友。

1.&&

&&运算符代表的与运算,它是用来判断真假的,而&是进行按位与。&&运算符的规则是:如果&&符号前面的为真,那么继续判断&&符号后面的内容,如果为真,那么返回真,如果为假,那么返回假。如果&&符号前面的为假,那么将不再运行&&y运算符后面的内容,这个表达式直接被判为假。

#include <stdio.h>
#include <stdlib.h> int main()
{
int i = 0;
int j = 1; if (i && j++)
{
printf ("%d", j);
}
return 0;
}

在上述程序中,最后的j值也没有打印,因为程序执行到i = 0的时候就已经判定为假直接返回了。

在C语言中,0为假非0为真!

2.||运算符代表或运算。||运算符的规则是:如果||运算符前面的内容为真,那么将不再判断||运算符后面的内容,程序直接返回真。如果||前面的运算符为假,那么程序将会继续执行,判定||运算符后面的内容是否为真,如果为真,那么程序返回真,如果为假那么程序最后返回假。

#include <stdio.h>
#include <stdlib.h> int main()
{
int i = 0;
int j = 1; if (i || j++)
{
printf ("%d", j);
}
return 0;
}

程序最后运行的结果如下:

《C语言深度剖析》学习笔记----C语言中的符号

从程序的运行结果中可以看出:当程序判定i 为假以后程序还是继续执行了的,所以我们才能够打印出j的值为2.

3.!

取反运算符在很多条件判定的时候是很常用的。

首先说一下C语言中一个很容易混淆的规则:C语言中的逻辑运算符!只认得0,所以如果我们对0取反它将会返回1,如果对非0取反那么它将会返回0 。简单的一句话:在C语言中只有0和非0
例程如下:

#include <stdio.h>

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

程序打印结果如下所示:

《C语言深度剖析》学习笔记----C语言中的符号

4.三目运算符

三目运算符在C语言中不是特别常用,不过有的时候使用它会使程序更加简便。

三目运算符的执行规则(a ?b:c ),当a为真时执行b中的内容,当a为假时执行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; printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c); return 0;
}

五、位运算符

C语言中的位运算符如下图所示:

《C语言深度剖析》学习笔记----C语言中的符号

(1)异或(^)

异或在的运算原则是两个数有不同的位的时候进行或运算,如果两个数的所有位都相同,那么最后结果为0.

例程:

#include <stdio.h>
#include <stdlib.h> int main()
{
int a = 1;
int b = 2; int c = 2;
int d = 2; printf ("a^b = %d", a^b);
printf ("c^d = %d", c^d);
}

程序运行结果如下:

《C语言深度剖析》学习笔记----C语言中的符号

(2)左移右移(<< >>)

运算规则:左移:规则,高位丢弃,低位补0.
                    右移:高位补符号位,低位丢弃。

如果要单纯的计算是数值,那么可以使用如下计算方式:

左移n位,就是乘以2的n次方
右移n位,就是除以2的n次方如果不够除了那么就直接归0
上面的计算方式仅仅适用于计算具体的数值,但是很多硬件操作中是使用位移这种方式来操作寄存器的,这个时候这种计算放式就不行了。

(3)交换两个数的数值的方法

交换两个数的数值有很多种方法,其中包括宏定义的方式,还有函数传址的方式。这里主要来说一下宏的操作方式。

主函数代码如下:

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

宏SWAP1的定义如下:

#define SWAP1(a,b) \
{ \
int temp = a; \
a = b; \
b = temp; \
}

宏SWAP2的定义如下:

#define SWAP2(a,b) \
{ \
a = a + b; \
b = a - b; \
a = a - b; \
}

宏SWAP3的定义如下:

#define SWAP3(a,b) \
{ \
a = a ^ b; \
b = a ^ b; \
a = a ^ b; \
}

第一种方法是比较普通的方法,我们相当于给程序找来一个杯子,然后利用这第三个杯子完成了另外两个杯子中内容的交换。

第二种方式是一种巧妙的运算,但是假如我们的a ,b 数值都很大的话,那么第二种方法不如第一种方法,因为第二种方法的第一步是a+b,假如a和b的数值都很大的话那么a+b的值很有可能导致程序溢出。

第三种方法是将两个数进行异或运算,最后实现两个数的交换,这种方法不会造成溢出,同时执行效率也很高,不过这种方式仅仅适用于整型数。
(4)在使用位运算符的时候一定要注意位运算符和其他运算符的优先级问题,例如:

#include <stdio.h>
#include <stdlib.h> int main()
{
int a = 0x1;
int b; b = 0x1<<2 + 3;
printf ("%d", b);
}

b = 0x1<<2 + 3。原来程序的本意可能是将0x1左移两位然后加3,但是程序实际的执行结果是将0x1左移5位。所以我们最好加上括号来区别开来。同时真心希望编译器能够让程序员们强制性的加上括号,没有优先级的C语言的世界该是多么的完美啊!

(5)在gcc编译器下如果我们将一个整数执行左移负位,那么实际上是进行右移操作。

#include <stdio.h>
#include <stdlib.h> int main()
{
int a = 0x1;
int b; b = 0x1<< (-1);
printf ("%d", b);
}

程序的执行结果是:b = 0

六、++,--操作符号

++,--操作符号至今我也没弄太懂,因为这里不单单是一个简单的运算操作问题,里面还涉及到顺序点的问题。

(1)陈正冲老师和唐老师都给了这样一个题:

i = 3;
(++i) + (++i) + (++i);

求上面执行完运算以后的结果是多少?

其他的编译器就不说了,我只说gcc编译下打印出来的值是16.原因是这样的,编译器先对前两个i进行自加操作,那么执行完前两个i以后,程序运行到10,然后再执行最后一个++i操作,程序最后运行结果是16.

(2)++操作符号和逗号表达式混合

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

程序最后的打印结果是13,也就是程序执行了最后一个表达式并且打印了,这里还有其他的问题,也就是在什么时候执行++,哪一个符号才是真正的顺序点,哪里才真正的开始执行++操作,这些问题我一直都没弄清楚,大神们希望给我指导。

#include <stdio.h>
int main()
{
int i = 2;
int b = 4;
int x; x = i+++b;
printf ("%d\n",i);
printf ("%d\n",b);
printf ("%d\n", x);
}

程序最后打印的是6,顺序点问题!

(3)在没有顺序点的前提下我们可以单纯的使用贪心法来进行计算

贪心法:

《C语言深度剖析》学习笔记----C语言中的符号

贪心法:使用贪心法i一直在吃+,但是当出现++i++的时候,这个时候是一个整数2++,这完全不符合C的规范。在编译器贪心的过程中,空格是一个很好的结束标志。

贪心法例程:

#include <stdio.h>
#include <stdlib.h> int main()
{
int a = 0;
int i = 0;
a = ++i+ ++i + ++i;
printf ("%d", a);
}

这里面涉及到的是顺序点和++运算问题,所以最后打印结果是7

七、优先级和类型转换

1.优先级仍然是C语言中一大难点

《C语言深度剖析》学习笔记----C语言中的符号

C语言中的优先级问题总是把人搞晕,所以我们在编写程序的过程中一定要注意这一部分,同时也要不断的积累,不过唐老师说没有一个人会把这个优先级背下来,不过我真的想不到其他的好办法了。

2.类型的转换

(1)在算术运算式中,低类型转换为高类型 (2)在赋值表达式中,表达式的值转换为左边的变量类型 (3)函数调用中,实参转换为形参的类型 (4)返回值中,return返回值的类型转换为返回值类型

无论我们的函数return后面的是什么类型,函数的最终返回值都以函数的返回值类型为准。

例程:

#include <stdio.h>
#include <stdlib.h> int f()
{
double a = 10.0;
return a;
}
int main()
{
int b;
b = f();
printf ("%d", b);
}

程序最后打印结果为10

(5)函数的隐式类型转换的方向

《C语言深度剖析》学习笔记----C语言中的符号

例程:

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 ("%d\n",(i + j));
return 0;
}

程序最后的运行结果:

《C语言深度剖析》学习笔记----C语言中的符号

分析:i为int型的,j为 unsigned int型的,所以它们两个相加根据那个截图可知 都应该转换成为unsigned型的,又因为-2无符号补码是一个很大的值,同时再+1,是一个比较大的值,所以最后i + j>0;但是最后输出是-1的原因是因为%d起的作用,%d输出的是整型,自然而然的将输出的数转换为-1。