`
txf2004
  • 浏览: 6855045 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

《C程序设计语言》 第二章 类型、运算符与表达式

 
阅读更多

ANSI标准对语言的基本类型与表达式做了许多小的修改与增补。
所有整型都包括signed(带符号)和unsigned(无符号)两种形式。
浮点运算可以以单精度进行,还可以使用更高精度的long double类型运算。
字符串常量可以在编译时连接。
ANSI C还支持枚举类型。
对象可以声明为const类型,表明其值不能修改。


2.1 变量名

名字是由字母和数字组成的序列,但其第一个字符必须为字母。
下划线“_”被看做是字母,例程的名字通常以下划线开头。
对于内部名,至少前31个字符是有效的。
函数名和外部变量名包含的字符数目可能小于31,因为汇编程序和加载程序可能会使用这些外部名。
ANSI标准仅保证前6个字符的唯一性,并且不区分大小写。


2.2 数据类型及长度

C语言只提供了下列几种基本数据类型:char, int, float, double。
此外还可以在这些基本数据类型的前面加上一些限定符。

short与long两个限定符用于限定整型,关键字int可以省略。
short int sh; long int counter;
short与long限定符的引用提供了满足实际需要的不同长度的整型数。
各编译器遵循下列限制:
short与int至少16位;long至少32位。short不得长于int;int不得长于long。

signed与unsigned可用于限定char类型或任何整型。
如果char占用8位,那么unsigned char取值范围为0~255,signed char则为-128~127
(在采用对二补码的机器上)。不带限定符的char是否带符号取决于机器,但打印字符总是正值。

float、double、long double长度也取决于具体实现。

练习2-1 编写一个程序,确定分别由signed及unsigned限定的char、short、int与long类型取值范围。
采用打印标准头文件中的相应值以及直接计算两种方式实现。
答:
#include <stdio.h>
#include <limits.h>
main()
{

printf("signed char min = %d\n", SCHAR_MIN);
printf("signed char max = %d\n", SCHAR_MAX);
printf("signed short min = %d\n", SHRT_MIN);
printf("signed short max = %d\n", SHRT_MAX);
printf("signed int min = %d\n", INT_MIN);
printf("signed int max = %d\n", INT_MAX);
printf("signed long min = %ld\n", LONG_MIN);
printf("signed long max = %ld\n", LONG_MAX);

printf("unsigned char max = %u\n", UCHAR_MAX);
printf("unsigned short max = %u\n", USHRT_MAX);
printf("unsigned int max = %u\n", UINT_MAX);
printf("unsigned long max = %lu\n", ULONG_MAX);
}


2.3 常量

long类型常量以字母l或L结尾。无符号常量以字母u或U结尾。后缀ul或UL表明是unsigned long类型。
浮点数常量包含一个小数点如123.4或一个指数1e-2,也可以两者都有。
没有后缀的浮点数常量为double类型。后缀f或F表示float类型,而后缀l或L则表示long double类型。
带前缀0的整型常量为八进制形式;前缀为0x或0X,则为十六进制形式。

转义字符序列看起来像两个字符,但只表示一个字符。另外可以用'\ooo'表示任意的字节大小的位模式。
ooo代表1~3个八进制数字。还可以用'\xhh'表示,hh是十六进制数字。

#define VTAB '\013' (或'\xb')
#define BELL '\007' (或'\x7')

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


常量表达式是仅仅只包含常量的表达式,在编译时求值,而不在运行时求值。
字符串常量也叫字符串字面值,是用双引号括起来的字符序列。
字符常量与仅包含一个字符的字符串之间的区别:'x'与"x"是不同的。
'x'是一个整数,其值是字母x在字符集中对应的数值;后者是一个包含一个字符x以及结束符'\0'的字符数组。

枚举是一个常量整型值的列表,如 enum boolean { NO, YES }
没有显示说明的情况下,enum类型中第一个枚举名的值为0,第二个为1。
如果只指定了部分枚举名的值,那么未指定的枚举名的值将依照最后一个指定值向后递增。

enum escapes { BELL = '\a', BACKSPACE = '\b', TAB = '\t' };
enum months { JAN = 1, FEB, MAR ... NOV, DEC };

不同枚举中的名字必须互不相同。同一枚举中不同的名字可以具有相同的值。
相比于#define语句来说,enum的优势在于常量值可以自动生成。


2.4 声明

一个声明指定一种变量类型,但后面所带的变量表可以包含一个或多个该类型的变量。
int lower, upper, step;

默认情况下,外部变量与静态变量将被初始化为0。未经显示初始化的自动变量的值为未定义值。

const限定符指定变量的值不能被修改。对数组而言,const限定符指定数组所有元素的值都不能被修改。
const double e = 2.71828;
const char msg[] = "warning:";

const也可配合数组参数使用,表明函数不能修改数组元素的值。
int strlen(const char[])


2.5 算术运算符

取模运算符%不能应用于float或double类型。
在有负数操作符的情况下,整数除法截取的方向以及取模运算结果的符号取决于具体机器的实现。


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

由逻辑运算符&&与||连接的表达式按从左到右的顺序进行求值,在知道结果值为真或假后立即停止计算。

练习2-2 在不使用运算符&&或||的条件下编写一个与上面for循环语句等价的循环语句。
for (i=0; i<lim-1 && (c=getchar()) != '\n' && c != EOF; ++i)
答:
for (i=0; i<lim-1; ++i) {
if (c=getchar() == '\n')
break;
else if (c == EOF)
break;
...
}


2.7 类型转换

自动转换是指把“比较窄的”操作数转换为“比较宽的”,并且不丢失信息的转换。
针对可能导致信息丢失的表达式,编译器可能会给出警告信息,但这些表达式并不非法。

由于char类型就是较小的整型,因此在算术表达式中可以自由使用char类型的变量。

/* atoi: convert s to integer */
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;
}

/* lower: convert c to lower case; ASCII only */
int lower(int c)
{
if (c >= 'A' && c <= 'Z')
return c + 'a' - 'A';
else
return c;
}

标准头文件<ctype.h>定义了一组与字符集无关的测试和转换函数。
tolower(c)函数将c转换为小写形式,可以替代上述lower函数。
测试语句c >= '0' && c <= '9'可以用函数isdigit(c)替换。

C语言中没有指定char类型的变量是无符号还是带符号变量。
当把一个char类型的值转换为int类型时,结果有可能是负整数。
C语言的定义保证了机器的标准打印字符集中的字符不会是负值。???

注意,表达式中float类型的操作数不会自动转换为double类型。
使用float类型主要是为了在使用较大的数组时节省存储空间,有时也为了节省机器执行时间
(双精度算术运算特别费时)。

当表达式中包含unsigned类型的操作数时,转换规则要复杂一些。因为带符号值与无符号值之间的
比较运算是与机器相关的。假如int类型占16位,long类型占32位。那么-1L < 1U,因为unsigned int
类型1U被提升为signed long类型;但-1L > 1UL,因为1L将被提升为unsigned long类型而成为一个比较大的正数。

练习2-3 编写函数htoi(s),把由十六进制数字组成的字符串(包含可选的前缀0x或0X)转换为等价的
整数值。字符串中允许包含的数字包括:0~9、a~f以及A~F。
答:
#include <stdio.h>
int htoi(char s[]);
main()
{
char s1[] = "10";
char s2[] = "2D";
char s3[] = "3f";
char s4[] = "0X4F";
char s5[] = "0x3a";

printf("%s -> %d\n", s1, htoi(s1));
printf("%s -> %d\n", s2, htoi(s2));
printf("%s -> %d\n", s3, htoi(s3));
printf("%s -> %d\n", s4, htoi(s4));
printf("%s -> %d\n", s5, htoi(s5));
}
int htoi(char s[])
{
int n = 0;
int i = -1;

while (s[++i] != '\0') {
if (i == 0 && s[i] == '0')
continue;
else if (s[i] == 'x' || s[i] == 'X')
continue;
else if ('0'<= s[i] && s[i] <= '9')
n = n * 16 + (s[i] - '0');
else if ('a'<= s[i] && s[i] <= 'f')
n = n * 16 + (s[i] - 'a' + 10);
else if ('A' <= s[i] && s[i] <= 'F')
n = n * 16 + (s[i] - 'A' + 10);
else
return -1;
}
return n;
}


2.8 自增与自减运算符

++与--特殊的地方表现在:它们既可以用作前缀运算符,也可以用作后缀。效果都是将变量值加1。
区别是++n先将n的值加1,然后再使用变量n的值;表达式n++则是先使用变量n的值,然后再加1。

自增与自减运算符只能作用于变量,类似表达式(i+j)++是非法的。
/* strcat: concatenate t to end of s; s must be big enough */
void strcat(char s[], char t[])
{
int i, j;
i = j = 0;
while (s[i] != '\0') /* find end of s */
i++;
while ((s[i++] = t[j++]) != '\0') /* copy t */
;
}

练习2-4 squeeze(s1, s2),将字符串s1中任何与字符串s2中字符匹配的字符都删除。
答:
#include <stdio.h>
void squeeze(char s1[], char s2[]);
main()
{
char s1[] = "thisiscdai,doyouknowcdai.cdaiiscoder.";
char s2[] = "cdai";
squeeze(s1, s2);
printf("%s\n", s1);
}
void squeeze(char s1[], char s2[])
{
int i, j, k;

for (i = k = 0; s1[i] != '\0'; i++) {
for (j = 0; s2[j] != '\0' && s2[j] != s1[i]; j++)
;
if (s2[j] == '\0')
s1[k++] = s1[i];
}
s1[k] = '\0';
}
结果为:thss,oyouknow.soer.

练习2-5 编写函数any(s1, s2),将字符串s2中的任一字符在字符串s1中第一次出现的位置作为结果返回。
如果s1中不包含s2中的字符,则返回-1。
答:
#include <stdio.h>
int any(char[], char[]);
main()
{
char s1[] = "thisiscdaiacoder";
char s2[] = "cdai";
printf("%d\n", any(s1, s2));

char s3[] = "thisiscoder";
printf("%d\n", any(s3, s2));
}
int any(char s1[], char s2[])
{
int i, j;
for (i = 0; s1[i] != '\0'; ++i) {
for (j = 0; s1[i+j] == s2[j] && s2[j] != '\0'; ++j)
;
if (s2[j] == '\0')
return i;
}
return -1;
}


2.9 按位运算符

C语言提供了6个位操作运算符。只能作用于整型,带符号或无符号的char、short、int、long。

& 按位与
经常用于屏蔽某些二进制位,例如:n = n & 0177; 将n中除7个低二进制位外的其他各位均置为0。

| 按位或
常用于将某些二进制位置为1,例如:x= x | SET_ON; 将x中对应于SET_ON中为1的那些二进制位置为1。

^ 按位异或
当两个操作数的对应为不相同时将该位设置为1。

必须将位运算符&、|同逻辑运算符&&、||区分开来。例如,x=1,y=2,那么x & y = 0, x && y = 1。

移位运算符<<和>>
x << 2将把x的值左移2位,右边空出的2位用0填补。该表达式等价于对左操作数乘以4。
当对signed类型的带符号值进行右移时,某些机器将对左边空出的部分用符号位填补(即“算术移位”),
而另一些机器则对左边空出的部分用0填补(“逻辑移位”)。

~ 按位求反
用于求整数的二进制反码,即分别将操作数各二进制位上的1变为0,0变为1。
例如x = x & ~077 将把x的最后6位设置为0。注意,表达式x & ~077与机器字长无关,它比形式
为x & 017700的表达式更好,后者假定x是16位的数值。这种可移植的形式并没有增加额外开销,
因为~077是常量表达式,可以在编译时求值。

例子:getbits(x,4, 3)返回x中第4、3、2三位的值。

/* getbits: get n bits from position p */
unsigned getbits(unsigned x, int p, int n)
{
return (x >> (p-n+1)) & ~(~0 << n);
}

x >> (p-n+1)将期望获得的字段移位到字的最右端。
~0的所有位都是1。~(~0 << n)建立了最右边n位全为1的屏蔽码。

练习2-6 编写一个函数setbits(x, p, n, y),返回对x执行下列操作后的结果值:
将x中从第p位开始的n位设置为y中最右边n位的值,x的其余各位保持不变。

练习2-7 编写一个函数invert(x, p, n),返回对x执行下列操作后的结果值:
将x中从第p位开始的n位求反,x的其余各位保持不变。

练习2-8 编写一个函数rightrot(x, n),返回将x循环右移(即从最右端移出的位将
从最左端移入)n位后所得到的值。

答:
#include <stdio.h>
main()
{
unsigned x = 0645; // 110_100_101
int p = 5;
int n = 3;

int leftMask = ~(~0 << (p+1-n)); // 111
int rightMask = ~0 << (p+1); // 11...1000000
int midMask = ~(leftMask | rightMask); // 111000

int leftBit = x & leftMask; // 101
int midBit = x & midMask; // 100000
int rightBit = x & rightMask; // 110000000

// Practice 2-6: replace [p, p-n+1]
int replace = 02;
int replaceBit = replace << (p+1-n); // 10000
int resultBit1 = rightBit | replaceBit | leftBit;
printf("%o\n", resultBit1);

// Practice 2-7: invert [p, p-n+1]
int midInvert = ~(leftMask | midBit | rightMask);//11000
int resultBit2 = rightBit | midInvert | leftBit;
printf("%o\n", resultBit2);

// Improve 2-6
// xxx...x000x...xxx x
// 000...0nnn0...000 y
// ( x & ~(~(~0 << n) << (p+1-n) ) | ( (y & ~(~0 << n)) << (p+1-n) )

// Improve 2-7 using XOR
int xorMask = (~(~0 << n)) << (p-n+1);
printf("%o\n", x ^ xorMask);
}


2.10 赋值运算符与表达式

统计整型参数的值为1的二进制位的个数。

/* bitcount: count 1 bits in x */
int bitcount(unsigned x)
{
int b;
for (b = 0; x != 0; x >>= 1)
if (x & 01)
b++;
return b;
}

将x声明为无符号类型是为了保证将x右移时,无论该程序在什么机器上运行,左边空出的位都用0
(而不是符号位)填补。

练习2-9 在求对二的补码时,表达式x &= (x - 1)可以删除x中最右边值为1的一个二进制位。
请解释这样做的道理。用这一方法重写bitcount函数,以加快其执行速度。
答:
// If x like XXX1, it should be XXX0 & XXX1 = XXX0
// Example: 110_100_101 => 110_100_100
// Else x like XX10..00, it should be XX01..11 & XX10..00 = XX00..00
// Example: 110_100_100 => 110_100_000

#include <stdio.h>
main()
{
unsigned x = 0645; // 110_100_101
int count = 0;
while (x != 0) {
x &= (x-1);
count++;
}
printf("1-bit count: %d\n", count);
}


2.11 条件表达式

如果expr2与expr3的类型不同,结果的类型将由转换规则决定。
例如f为float类型,n为int类型,则表达式(n > 0) ? f : n是float类型,与n是否为正值无关。

for (i = 0; i < n; i++)
printf("%6d%c", a[i], (i%10==9 || i==n-1) ? '\n' : ' ';

在每10个元素之后以及第n个元素之后都要打印一个换行符,所有其他元素后打印一个空格。

练习2-10 重新编写将大写字母转换为小写字母的函数lower,用条件表达式替代其中的if-else结构。
答:
#include <stdio.h>
char lower(char c)
{
return ('A'<=c && c<='Z') ? c-('A'-'a') : c;
}
main()
{
printf("%c\n", lower('f'));
printf("%c\n", lower('Y'));
printf("%c\n", lower('5'));
}


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

位运算符&、^与|的优先级比==与!=低。这意味着,位测试表达式,如if ((x & mask) == 0)
必须用圆括号括起来才能得到正确的结果。

同大多数语言一样,C语言没有指定同一运算符中多个操作数的计算顺序(&&、||、?:和,)除外。
例如x = f() + g();的语句中,f()可以在g()之前计算,也可以在g()之后计算。因此,如果函数f或g
改变了另一个函数所使用的变量,那么x的结果可能会依赖于这两个函数的计算顺序。

类似地,C语言也没有指定函数各参数的求值顺序。
printf("%d %d\n", ++n, power(2, n));
在不同的编译器中可能会产生不同的结果,取决于++n在power调用之前还是之后。

函数调用、嵌套赋值语句、自增与自减都有可能产生“副作用”。
表达式何时产生副作用由编译器决定,因为最佳的求值顺序同机器结构有很大关系。


分享到:
评论

相关推荐

    Python程序设计第二章-Python语言数据类型运算符和表达式2022优秀文档.ppt

    Python程序设计第二章-Python语言数据类型运算符和表达式2022优秀文档.ppt

    Python程序设计-第二章-Python语言数据类型、运算符和表达式.ppt

    Python程序设计-第二章-Python语言数据类型、运算符和表达式.ppt该文档详细且完整,值得借鉴下载使用,欢迎下载使用,有问题可以第一时间联系作者~

    C程序设计语言[第2版].pdf

    在计算机发展的历史上,没有哪一种程序设计语言像C语言这样应用如此广泛。 本书原著 即为C语言的设计者之一Dennis M.Ritchie和著名的计算机科学家Brian W.Kernighan合著的 一本介绍C语言的权威经典著作。我们...

    C程序设计语言官方题解

    C程序设计语言官方题解。 第一张 导言 第二章 类型、运算符与表达式 第三章 控制流 第四章 函数与程序结构 第五章 指针与数组 第六章 结构 第七章 输入与输出 第八章 UNIX系统接口

    C语言程序设计(PDF格式)

    1.1 程序设计语言的发展 1 1.2 C语言的特点 2 1.2.1 C语言是中级语言 2 1.2.2 C语言是结构化语言 3 1.2.3 C语言是程序员的语言 3 1.3 C语言的程序结构 4 1.3.1 基本程序结构 4 1.3.2 函数库和链接 6 1.3.3 开发一个C...

    c语言程序设计基础课件_东北大学

    本课程的教学目标是通过学习用一种典型的程序设计语言——C语言,建立起程序设计的概念,初步掌握程序设计方法,掌握程序设计的基本方法和技巧,养成良好的程序设计风格,从而具备应用程序设计解决相关专业领域内...

    C程序设计语言_第2版(带书签目录)

    第二章 类型、运算符与表达式 2.1 变量名 2.2 数据类型与长度 2.3 常量 2.4 声明 2.5 算术运算符 2.6 关系运算符与逻辑运算符 2.7 类型转换 2.8 自增运算符与自减运算符 2.9 按位运算符 2.10 赋值运算符与...

    Python程序设计课件第2章-Python基本语法.pptx

    基本元素 Python语法特点 标识符与变量、常量 基本数据类型 基本输入和输出 常见的运算符与表达式 第二章 Python基本语法 参考书目《Python 程序设计》 Python程序设计课件第2章-Python基本语法全文共85页,当前为第...

    谭浩强 (第三版) C 程序设计

    C语言程序设计[谭浩强]第三版目录 第一章 C语言概述 1 C语言概述 1.1 C语言的发展过程 1.2 当代最优秀的程序设计语言 1.3 C语言版本 1.4 C语言的特点 1.5 面向对象的程序设计语言 1.6 C和C++ 1.7 简单的C...

    C程序设计语言(第2版)

    C程序设计语言(第2版) 第一章 导言 第二章 类型、运算符与表达式 ……

    C语言程序设计(第二版)谭浩强编写

    第一章 C语言程序设计语言概述 第二章 数据类型 第三章 基础语句 第四章 运算符和表达式 第五章 输入输出 第六章 分支结构 第七章 循环结构 第八章 转移语句 第九章 数组 第十章 函数 第十一章 指针的概念 第十二章 ...

    第2章-Python基本语法-Python程序设计基础案例教程-李辉-清华大学出版社.pptx

    基本元素 Python语法特点 标识符与变量、常量 基本数据类型 基本输入和输出 常见的运算符与表达式 第二章 Python基本语法 参考书目《Python 程序设计》 第2章-Python基本语法-Python程序设计基础案例教程-李辉-清华...

    C程序设计语言(第2版·新版中文)

    《C程序设计语言》(第2版新版)讲述深入浅出,配合典型例证,通俗易懂,实用性强,适合作为大专院校计算机专业或非计算机专业的C语言教材,也可以作为从事计算机相关软硬件开发的技术人员的参考书。《C程序设计语言》...

    c语言教程 PDF.rar

    2第二章 C 语言的数据类型、运算符与表达式.pdf 3第三章 顺序结构程序设计.pdf 4第四章 流程控制语句.pdf 5第五章 数组.pdf 6第六章 函 数.pdf 7第七章 指 针.pdf 8第八章 结构体、共同体和枚举.pdf 9第九章...

    李爱华、程磊_面向对象程序设计第二章案例源程序

    李爱华 程磊 面向对象程序设计 中 第二章 C++语言基础 中的所有的案例源程序,包括C++语言概述、基本数据类型、运算符和表达式等

    C程序设计语言

    C语言经典教材,第一章,概念;第二章,运算符、表达式;第三章,流控制;第四章,函数与程序结构;第五章,指针与数组;第六章,结构;第七章,输入与输出;第八章,UNIX系统接口

    C程序设计语言(第2版)

    第一章 入门 导言 第二章 类型、运算符与表达式 第三章 流控制 第四章 函数与程序结构 第五章 指针与数组 第六章 结构与函数 第七章 输入输出 第八章 unix系统接口

    完整版 Java初级教程 Java语言程序设计 第4章 类和对象(共22页).ppt

    完整版 Java初级教程 Java语言程序设计 第3章 运算符、表达式、语句(共16页).ppt 完整版 Java初级教程 Java语言程序设计 第4章 类和对象(共22页).ppt 完整版 Java初级教程 Java语言程序设计 第5章 继承与接口...

Global site tag (gtag.js) - Google Analytics