学习《C程序设计语言》笔记——类型、运算符与表达式

变量常量是程序处理的两个基本数据对象。
声明语句说明变量的名字及类型,也可以指定变量的初值。
运算符指定将要进行的操作。
表达式则把变量与常量组合起来生成新的值。
对象的类型决定该对象可取值的集合以及可以对该对象执行的操作。

2.1 变量名

变量名的命名规则:
1.名字只能由字母和数字组成,下划线被看做是字母。
2.名字开始第一个字符不能是数字,注意:由于库例程的名字通常是以下划线开头,因此变量名不要以下划线开头。
3.C语言区分字母大小写。通常情况下,变量名为小写,符号变量全是大写;
4.变量名不能是C语言关键字或者是保留字;
注意:书上指出ANSI C标准指定内部名的有效长度在31个字符内,不过现在编译器应该没有这个限制;**函数名与外部变量名包含的字符数目可能小于31,这是因为汇编程序和加载程序可能会使用这些外部名,而语言本身是无法控制加载和汇编程序的。**对于外部名,ANSI标准仅保证前6个字符的唯一性,并且不区分大小写。(现在编译器怎么样,需要试验)
注意:局部变量一般使用较短的变量名(尤其是循环控制变量),外部变量使用较长的名字;

2.2 数据类型及长度

C语言提供的数据类型有以下几种:

类型 说明 备注
char 字符型 占用一个字节
int 整型 细分为short int,long int类型,即小整型和大整型
float 单精度浮点型
double 双精度浮点型

short与long两个限定符的引入可以将int类型限制为不同长度的整数型;
int通常代表了特定机器中整数的自然长度。
short类型通常为16位,long类型通常为32位,int类型可以为16位或32位;
类型限定符signed与unsigned可用于限定char类型或任何整型。
signed表示有符号类型,可以忽略不写,默认类型;
unsigned表示无符号类型,必须标识;unsigned类型额数总是正值或0,并遵守算术模2n定律,其中n是该类型占用的位数。
注意:有关数据类型长度定义的符号常量以及其他与机器和编译器有关的属性可以在标准头文件<limits.h>与<float.h>中找到;
注意:有无符号类型的区别在于变量的最高位(最左边第一位)是否为符号位,如果是符号位,则不为值的一部分;
编写一个程序以确定分别由signed及unsigned限定的char、short、int、long类型变量的取值范围;

#include <stdio.h>
#define ONE_BYTES 8
long long power(int base, int n);
void print_range_of_tyep(int sizeof_type);
int main()
{
    
    
	int char_byte_size = sizeof(char) * ONE_BYTES;
	int short_byte_size = sizeof(short) * ONE_BYTES;
	int int_byte_size = sizeof(int) * ONE_BYTES;
	int long_byte_size = sizeof(long) * ONE_BYTES;
	printf("print char type range\n");
	print_range_of_tyep(char_byte_size);
	printf("print short type range\n");
	print_range_of_tyep(short_byte_size);
	printf("print int type range\n");
	print_range_of_tyep(int_byte_size);
	printf("print long type range\n");
	print_range_of_tyep(long_byte_size);

	return 0;
}

void print_range_of_tyep(int sizeof_type)
{
    
    
	long long unsigned_max = power(2, sizeof_type) - 1;
	long long unsigned_min = 0;
	long long signed_max = power(2, sizeof_type - 1) - 1;
	long long signed_min = -1 * power(2, sizeof_type - 1);
	printf("signed min:%lld,max:%lld; ", signed_min, signed_max);
	printf("unsigned min:%lld,max:%lld\n", unsigned_min, unsigned_max);
}

long long power(int base, int n)
{
    
    
	long long p;
	for (p = 1; n > 0; --n)
		p = p * base;
	return p;
}

在这里插入图片描述

2.3常量

long类型的常量以字母l或L结尾。
无符号常量以字母u或U结尾,后缀ul或UL表明是unsigned long类型。
没有后缀的浮点数常量为double类型,后缀f或F表示float类型,而后缀l或L则表示long double类型;
整型数除了用十进制表示外,还可以用八进制或十六制表示;带前缀0的整型常量表示它为八进制形式;前缀为0x或0X,则表示它为十六进制。八进制与十六进制的常量也可以使用后缀L表示long类型,使用后缀U表示unsigned类型;
一个字符常量是一个整数,书写时将一个字符括在单引号中;
某些字符可以通过转义字符序列表示为字符和字符串常量。转义字符序列看起来像两个字符,但只表示一个字符。
'\ooo'表示任意的字节大小的位模式,其中ooo表示1~3个八进制数字。
'\xhh'表示一个或多个十六进制数字。

#define VTAB '\013'// ASCII纵向制表符
#define BELL '\007'// ACSII响铃符
#define VTAB '\xb'// ASCII纵向制表符
#define BELL '\x7'// ACSII响铃符

ANSI C语言中的全部转义字符序列如下:

\a 响铃符
\b 回退符
\f 换页符
\n 换行符
\r 回车符
\t 横向制表符
\v 纵向制表符
\ 反斜杠
? 问号
单引号
" 双引号
\ooo 八进制数
\xhh 十六进制数

字符常量'\0'表示值为0的字符,也就是空字符(null)。
常量表达式是仅仅只包含常量的表达式。这种表达式在编译时求值,而不在运行时求值。

#define MAXLINE 1000
char line[MAXLINE + 1];

字符串常量也就字符串字面值,使用双引号括起来的0个或多个字符组成的字符序列;
双引号不是字符串的一部分,它只用于限定字符串。
字符常量中使用的转义字符序列同样可以用在字符串中。编译时可以将多个字符串常量连接起来。

"hello,""world"==>"hello,world"

字符串常量的连接为将较长的字符串分散在若干个源文件行中提供了支持;
字符串常量就是字符数组。字符串的内部表示使用一个空字符'\0'作为串的结尾,所以,存储字符串的物理存储单元数比括在双引号中的字符数多一个。
C语言对字符串的长度没有限制,但程序必须扫描完整个字符串后才能确定字符串的长度。
标准库函数strlen(s)可以返回字符串参数s的长度,但长度不包括末尾的'\0'
自己实现一下strlen函数:

int strlen(char s[]) {
    
    
	int i;
	
	i = 0;
	while (s[i] != '\0')
		++i;
	return i;
}

标准头文件<string.h>中声明了strlen和其他字符串函数;
字符常量和只有一个字符的字符串的区别是:
1.'x'``"x"是不同的;
2.'x'是一个整数,其值是字母x在机器字符集中对应的数值(内部表示值);
3."x"是包含一个字符x以及一个结束符'\0'的字符数组;
枚举常量是另外一种类型的常量。枚举是一个常量整型值的列表。

enum boolean{
    
    No,Yes};

在没有显示说明的情况下,enum类型中第一个枚举名的值为0,第二个为1,依次类推;
如果只指定了部分枚举名的值,那么未指定值的枚举名的值将依着最后一个指定值向后递增。
不同枚举中的名字必须互不相同;
同一枚举中不同的名字可以具有相同的值;
枚举是一种常量与名字之间的关联方式,相比#define符号变量来说,枚举的常量值可以自动生成。枚举在编译时会检查类型是否匹配,因此可以使用枚举来代替#define;

2.4 声明

所有变量都必须先声明后使用,尽管某些变量可以通过上下文隐式地声明。
一个声明指定一种变量类型,后面所带的变量表可以包含一个或多个该类型的变量。

int lower,upper,step;
char c,line[1000];

一个声明语句中的多个变量可以拆开在多个声明语句中声明;我比较习惯这种方式,这样每个变量的类型都清晰可见,不会混淆;

int lower;
int upper;
int step;
char c;
char line[1000];

还可以在声明变量的同时,对变量进行初始化。
在声明中,如果变量名的后面紧跟了一个等号以及一个表达式,该表达式就充当对变量进行初始化的初始化表达式;

int lower = 0;
int upper = 0;
int step = 5;
char c = 'a';
char line[1000] = {
    
    0};

每个变量都只能进行一次初始化操作,函数内部的局部变量都是在函数开始初始化,在函数执行完成后释放;
需要注意的是:默认情况下,外部变量与静态变量将被初始化为0;未经显示初始化的自动变量的值为未定义值(即无效值)。
const变量:任何变量的声明都可以使用const限定符限定。该限定符指定变量的值不能被修改。对数组而言,const限定符指定数组内所有元素的值都不能被修改;

const double e = 2.71;
const char msg[] = "warning";

const限定符也可以配合数组参数使用,它表明函数不能修改数组元素的值:

int strlen(const char[])

2.5 算术运算符

二元算术运算符包括:+-*/%(取模运算符)。
注意:
1.整数的除法会截断结果中的小数部分。
2.取模运算符%不能应用于float或double类型;
3.算术运算符的优先级顺序为:(*/%)> (+-);
4.算术运算符采用从左到右的结合规则;

2.6 关系运算符与逻辑运算符

关系运算符有:>>=<<=
相等性运算符==!=
关系运算符的优先级小于相等性运算符,相等性运算符优先级小于算术运算符。
逻辑运算符&&||连接的表达式从左到右的顺序求值,并且在指定结果为真或为假后立即停止运算,被称作是“短路”。
逻辑运算符&&的优先级比||的优先级高。
根据定义,在关系表达式或逻辑表达式中,如果关系为真,则表达式的结果值为数值1;如果为假,则结果值为数值0;
逻辑非运算符!的作用是将非0操作数转换为0,将操作数0转换为1;

2.7类型转换

类型转换是指一个运算符的几个操作数类型不同时,就需要通过一些规则把它们转换为某种共同的类型;
自动转换是指把“比较窄的”操作数转换为“比较宽的”操作数,并且不丢失信息的转换。
在自动转换时,可能出现导致信息丢失的表达式,编译器可能会给出警告信息,比如:把较长的整型值赋给较短的整型变量,把浮点型值赋值给整型变量。
注意:由于char类型就是较小的整型,因此在算术表达式中可以自由使用char类型的变量,这就为实现某些字符转换提供了很大的灵活性;

// atoi函数:将字符串s转换为相应的整数
int atoi(char s[]) {
    
    
	int i, n;

	n = 0;
	for (i = 0; s[i] >= '0' && s[i] <= '9'; ++i) {
    
    
		n = 10 * n + (s[i] - '0');
	}
	return n;
}

在ASCII字符集中,大写字母与对应的小写字母作为数字值来说具有固定的间隔,并且每个字母表都是连续的。

// low函数:把字符c转换为小写形式;只对ASCII字符集有效
int lower(int c){
    
    
	if (c >= 'A' && c <= 'Z')
		return c + 'a' - 'A';
	else 
		return c;
}

标准头文件<ctype.h>定义了一组与字符集无关的测试和转换函数;
C语言的定义保证了机器的标准打印字符集中的字符不会是负值,因此,在表达式中这些字符总是正值。
注意:如果要在char类型的变量中存储非字符数据,最好指定signed或unsigned限定符。
当关系表达式以及由&&||连接的逻辑表达式的判断结果为真时,表达式的值为1;当判定结果为假时,表达式的值为0;
任意的非0值也表达了这是真的意思,在if、while、for等语句的测试部分中,“真”就意味着“非0”;
C语言中,很多情况下会进行隐式的算术类型转换。如果二元运算符的两个操作数具有不同的类型,那么在进行运算之前先要把“较低”的类型提升为“较高”的类型,运算的结果也是较高的类型;
操作数中没有unsigned类型时,提升的规则:
1.如果其中一个操作数的类型为long double,则将另一个操作数转换为long double类型;
2.如果其中一个操作数的类型为double,则将另一个操作数转换为double类型;
3.如果其中一个操作数的类型为float,则将另一个操作数转换为float类型;
4.将char与short类型的操作数转换为int类型;
5.如果其中一个操作数的类型为long,则将另一个操作数转换为long类型;
注意:表达式中float类型的操作数不会自动转换为double类型;
一般来说,数学函数使用双精度类型的变量。使用float类型主要是为了在使用较大的数组时节省存储空间,有时也为了节省机器执行时间(双精度算术运算特别费时)。
注意:操作数中有unsigned类型时,提升的规则较为复杂;带符号值与无符号值之间的比较运算是与机器相关的,因为它们取决于机器中不同整数类型的大小;当操作数占用的字节个数不同时,占用多字节的操作数为signed,少字节的操作数为unsigned,少字节signed提升到多字节类型,没有问题;占用多字节的操作数为unsigned,占用少字节的操作数为signed时,在提升时,可能会将负数提升为一个很大的正数;
赋值时也要进行类型的转换,赋值运算符右边的值需要转换为左边变量的类型,左边变量的类型即赋值表达式结果的类型;
无论是否进行符号扩展,字符型变量都将被转换为整数变量;
当把较长的整数转换为较短的整数或char类型时,超出的高位那部分将被丢弃。
注意:浮点类型转换为整数类型时,会将小数点后的数值丢弃,直接截取;当把double类型转换为float类型时,是进行四舍五入还是截取取决于具体的实现。
在函数调用时,也会进行参数的类型转换。在没有函数原型的情况下,char与short类型都将被转换为int类型,float类型将被转化为double类型;因此,即使调用函数的参数为char或float类型,我们也把函数参数声明为int或double类型;
在任何表达式中都可以使用一个成为强制类型转换的一元运算符强制进行显示类型转换。
在下列语句中,表达式将按照上述转换规则被转换为类型名指定的类型:

(类型名)表达式

在上述语句中,表达式首先被赋值给类型名指定的类型的某个变量,然后再用该变量替换上述整条语句。

sqrt((double)n)

如果n是整数,则在上式中,在把n传递给函数sqrt之前先将其转换为double类型。
注意:强制类型转换只是生成一个指定类型的n的值,n本身的值并没有改变。强制类型转换运算符与其他一元运算符具有相同的优先级。

2.8自增运算符与自减运算符

C语言提供了两个用于变量递增与递减的特殊运算符。
自增运算符++使其操作数递增1,自减运算符--使其操作数递减1。
++--这个两个运算符特殊的地方主要表现在:它们既可以用作前缀运算符(用在变量前面,如++n),也可以用作后缀运算符(用在变量后面,如n++),效果都是将变量n的值加1;两者区别在于递增和值的使用的顺序,前缀运算符是先递增后使用;后缀运算符是先使用后递增;
注意:自增与自减运算符都只能作用于变量;
在不需要使用任何具体值且仅需要递增变量的情况下,前缀方式和后缀方式的效果相同。

// squeeze函数:从字符串s中删除字符c
void squeeze(char s[], int c){
    
    
	int i,j;
	for ( i = j = 0; s[i] != '\0'; i++)
		if (s[i] != c)
			s[j++] = s[i];
	s[j] = '\0';
}

在squeeze函数的实现中使用了s[j++] = s[i],在给s[j]赋值后,将j自增1;
编写strcat函数连接两个字符串;

// strcat函数:将字符串t连接到字符串s的尾部;s必须有足够大的空间;
void strcat(char s[], char t[]) {
    
    
	int i, j;
	i = j = 0;
	while (s[i] != '\0')
		i++;
	while ((s[i++] = t[j++]) != '\0')
		;
}

2.9按位运算符

C语言提供了6个位操作运算符。这些运算符只能作用于整型操作数,即只能作用于带符号或无符号的char、short、int与long类型;
位操作运算符列表如下:

符号 含义
& 按位与(AND)
| 按位或(OR)
^ 按位异或(XOR)
<< 左移
>> 右移
~ 按位求反(一元运算符)

按位与运算符经常用于屏蔽某些二进制位;

n = n & 0177;

按位或运算符常用于将某些二进制位置为1;
移位运算符<<>>分别用于将运算的左操作数左移与右移,移动的位数则由右操作数指定(右操作数的值必须是非负值)。
在对unsigned类型的无符号值进行右移位时,左边空出的部分将用0填补;当对signed类型的带符号值进行右移时,某些机器将对左边空出的部分用符号位填充(算术移位),而另一些机器则对左边空出的部分用0填补(逻辑移位)。
一元运算符~用于求整数的二进制反码,即分别将操作数各二进制位上的1变为0,0变为1。
编写getbits函数:返回x中从第p位开始的n位;

unsigned getbits(unsigned x, int p, int n) {
    
    
	return (x >> (p + 1 - n)) & ~(~0 << n);
}

2.10赋值运算符与表达式

在赋值表达式中,如果表达式左边的变量重复出现在表达式的右边,如

i = i + 2

就可以将这种表达式缩写为下列形式:i += 2,其中的运算符+=称为赋值运算符
大多数二元运算符(即有左、右两个操作数的运算符,比如+)都有一个相应的赋值运算符op=op=有以下:

+	-	*	/	%	<<	>>	&	^	|

赋值运算符expr1 op = expr2;等价于expr1 = (expr1) op (expr2);
赋值运算符的好处是前一种的expr1只计算一次,代码更为简洁。除了简洁外,赋值运算符还要一个优点:表示方式与人们的思维习惯比较接近。赋值运算符还有助于编译器产生高效代码。
赋值语句具有值,且可以用在表达式中。
在所有的这类表达式中,赋值表达式的类型是它的左操作数的类型,其值是赋值操作完成后的值;

	while((c = getchar())!=EOF)

2.11条件表达式

条件表达式使用if-else 以及三元运算符(? :);
if-else语句

if (a > b)
	z = a;
else 
	z = b; 

三元运算符

z = (a > b)?a:b;

条件表达式实际上就是种表达式,它可以在其他表达式可以使用的任何地方。
条件表达式中的第一个表达式两边的圆括号并不是必须的,这是因为条件运算符?:的优先级非常低,仅高于赋值运算符。

2.12运算符优先级与求值次序

运算符的优先级与结合性表

运算符 结合性
() [] -> . 从左至右
! ~ ++ -- + - * & (type) sizeof 从右至左
* / % 从左至右
+ - 从左至右
<< >> 从左至右
< <= > >= 从左至右
== != 从左至右
& 从左至右
^ 从左至右
| 从左至右
&& 从左至右
|| 从左至右
?: 从右至左
= += -= *= /= %= &= 从右至左
^= |= <<= >>= 从左至右
, 从左至右

注意:一元运算符+-&*比相应的二元运算符+-&*的优先级高;
注意:位运算符&^|的优先级比运算符==!=的低。
在位测试表达式if ((x & mask) == 0)必须yoga圆括号括起来才能得到正确结果;
注意:同大多数语言一样,C语言没有指定同一运算符中多个操作数的计算顺序(&&||?:,运算符除外)。
函数调用、嵌套赋值语句、自增与自减运算符都有可能产生“副作用”——在对表达式求值的同时,修改了某些变量的值。在有副作用影响的表达式中,其执行结果同表达式中的变量被修改的顺序之间存在着微妙的依赖关系。
在任何一种编程语言中,如果代码的执行结果与求值顺序相关,则都是不好的程序设计风格。
如果不知道这些问题在各种机器上是如何解决的,就最好不要尝试运用某种特殊的实现方式。

猜你喜欢

转载自blog.csdn.net/liushao1031177/article/details/120111266