[华为云在线课程][C语言基础][二][类型运算符与表达式][学习笔记]
变量和常量是程序处理的两种基本数据对象。声明语句说明变量的名字及类型,也可以指定变量的初值。运算符指定将要进行的操作。表达式则把变量与常量结合起来生成新的值。对象的类型决定该对象可取值的集合以及可以对该对象执行的操作。
1.变量名
名字由字母和数字组成的序列,第一个字符必须为字母。下划线被看作是字母,通常用于命名较长的变量名,以提高可读性。C语言是严格区分大小写的,在传统的C语言用法中,变量名使用小写字母,符号常量名全部使用大写字母。
选择的变量名要能够尽量从字面上表达变量的用途,这样不容易引起混淆。局部变量一般使用较短的变量名(尤其是循环控制变量),外部变量使用较长的名字。
2.数据类型及长度
C语言只提供了下列几种基本数据类型:
- char;字符型,占用一个字节,可以存放本地字符集中的一个字符。
- int;整型,通常反映了所用机器中整数的最自然长度。
- float;单精度浮点型。
- double;双精度浮点型。
还可以在基本数据类型前加上限定符。short与long两个限定符用于限定整型,如short int sh; long int counter
。这两个限定符的引入可以为我们提供满足实际需要的不同长度的整型数。int通常代表特定机器中整数的自然长度。short通常为16位,long通常为32位,int类型可以为16位或32位。
不同编译器可以根据硬件特性自主选择合适的类型长度,但要遵循以下限制:short与int至少为16位,long至少32位,并且short不得长于int,int不得长于long。
类型限定符signed与unsigned可用于限定char类型或任何整型。unsigned类型的数总是正值或0,遵循2的n次方定律,n是类型占用的位数。如果char对象占用8位,那么unsigned char类型变量的取值范围为0~255,signed char类型变量的取值范围为-128~127(在采用对二的补码的机器上)。不带限定符的char类型是否带符号取决于具体机器,但可打印字符总是正值。
long double类型表示高精度的浮点数。同整型一样,浮点型的长度取决于具体的实现。这些类型长度定义的符号常量及其它与机器和编译器有关的属性可以在标准头文件<limits.h>
与<float.h>
中找到。
练习2-1:
/*
* 编写一个程序以确定分别由signed及unsigned限定的char、short、int与long类型变量的取值范围。
* */
#include <stdio.h>
#include <limits.h>
int main(){
printf("char的大小为: %d\n", CHAR_BIT);
printf("char的最大值为:%d\n", CHAR_MAX);
printf("char的最小值为:%d\n", CHAR_MIN);
printf("int的最小值为:%d\n", INT_MIN);
printf("int的最大值为:%d\n", INT_MAX);
printf("long的最小值为:%ld\n", LONG_MIN);
printf("long的最大值为:%ld\n", LONG_MAX);
printf("short的最小值为:%d\n", SHRT_MIN);
printf("short的最大值为:%d\n", SHRT_MAX);
printf("unsigned char的值为:%u\n", UCHAR_MAX);
printf("unsigned long的值为:%lu\n", ULONG_MAX);
printf("unsigned int的值为:%u\n", UINT_MAX);
printf("unsigned short的值为:%u\n", USHRT_MAX);
return 0;
/*
* char的大小为: 8
char的最大值为:127
char的最小值为:-128
int的最小值为:-2147483648
int的最大值为:2147483647
long的最小值为:-2147483648
long的最大值为:2147483647
short的最小值为:-32768
short的最大值为:32767
unsigned char的值为:255
unsigned long的值为:4294967295
unsigned int的值为:4294967295
unsigned short的值为:65535
* */
}
3.常量
一般的整数常量属于int类型。long类型的常量以字母l或者L结尾。如果一个整数太大也被当作long类型处理。无符号常量以u或者U结尾。后缀ul或者UL表明是unsigned long类型。
浮点数常量包含一个小数点或者一个指数,没有后缀的浮点数常量为double类型。后缀f或F表示float类型,后缀l或L表示long double类型。
整型数还可以用把八进制或十六进制表示。前缀0的整型常量表示八进制,前缀0x或者0X表示十六进制。八进制与十六进制的常量也可以用后缀L表示long类型,使用后缀U表示unsigned类型。
一个字符常量是一个整数,书写时将一个字符括在单引号中,字符在机器字符集中的数值就是字符常量的值。某些字符可以通过转义字符序列表示为字符和字符串常量。转义字符序列看起来像两个字符,但只表示一个字符。C语言中的全部转义字符如下:
转义字符 | 含义 |
---|---|
\a | 响铃符 |
\b | 回退符 |
\f | 换页符 |
\n | 换行符 |
\r | 回车符 |
\t | 横向制表符 |
\v | 纵向制表符 |
\\ | 反斜杠 |
\? | 问号 |
\’ | 单引号 |
\\" | 双引号 |
\ooo | 八进制数 |
\xhh | 十六进制数 |
常量表达式是仅仅只包含常量的表达式。这种表达式在编译时求值,而不在运行时求值。例如:
#define MAXLINE 1000
char line[MAXLINE+1]; //1001
字符串常量也叫字符串字面值,是用双引号括起来的0个或多个字符组成的字符序列。例如:Hello world
。字符串常量就是字符数组。字符串内部表示使用一个空字符’\0’作为串的结尾。存储字符串的物理存储单元数比括在双引号中的字符数多一个。标准库strlen(s)可以返回字符串参数s的长度,但长度不包括末尾的’\0’。
int strlen(char s[])
{
int i;
while(s[i]!='\0')
{
++i;
}
return i;
}
‘x’与"x"是不同的。前者是一个整数,其值是字母x在机器字符集中对应的数值(内部表示值);后者是一个包含一个字符(即字母x)以及一个结束符’\0’的字符数组。
枚举常量是另外一个类型的常量。枚举是一个常量整形值得列表,例如:enum boolean {NO, YES};
。
注意:在没有显式说明的情况下,如果只指定了部分枚举名的值,那么未指定值的枚举名的值将依照最后一个指定值向后递增。不同枚举中的名字必须互不相同,同一枚举中不同的名字可以具有相同的值。
4.声明
所有变量都必须先声明再使用。一个声明指定一种变量类型,后面所带的变量表可以包含一个或多个该类型的变量。例如:
int lower,upper,step;
char c,line[1000];
一个声明语句中的多个变量可以拆开在多个声明语句中声明。也可以在声明的同时对变量进行初始化。如果变量不是自动变量(局部变量),则只能进行一次初始化操作。默认情况下,外部变量和静态变量将被初始化为0。未经显式初始化的自动变量(局部变量)的值为未定义值(无效值)。
任何变量的声明都可以使用const限定符限定。指定变量的值不能被修改。对数组而言,const指定数组所有元素的值不能被修改。
const doube e=1.234;
5.算术运算符
二元算术符包括:+、-、*、/、%。整数除法会截断结果中的小数部分。
取模运算符%不能用于float或者double类型。在有负操作数的情况下,整数除法截取的方向以及取模运算结果的符号取决于具体机器的实现,和处理上溢或下溢的情况是一样的。
6.关系运算符与逻辑运算符
关系运算符包括:大于,大于等于,小于,小于等于。优先级相同。关系运算符优先级低于算术运算符。
练习2-2:
/**
* 编写一个程序,将输入的字符存进数组中。
*/
#include <stdio.h>
#define MAX_STRING_LENGTH 100
int main()
{
int i = 0;
int lim = MAX_STRING_LENGTH;
int c;
char s[MAX_STRING_LENGTH];
while (i < (lim - 1))
{
c = getchar();
if (c == EOF)
{
break;
}
else if (c == '\n')
{
break;
}
s[i++] = c;
}
//加入结束符
s[i] = '\0';
}
7.类型转换
当一个运算符的几个操作数类型不同时,就需要通过一些规则把它们转换为某种共同的类型。一般来说,自动转换是指把"比较窄的"操作数转换为"比较宽的"操作数,并且不会丢失信息的转换。不允许使用无意义的表达式,比如不允许把float类型的表达式作为下标。针对可能导致信息丢失的表达式,编译器可能会给出警告信息,比如把较长的整型值赋给较短的整型变量,把浮点型值赋值给整型变量等等。
C语言中,很多情况下会进行隐式的算术类型转换。一般来说,如果二元运算符(具有两个操作数的运算符称为二元运算符,比如 + 或 * )的两个操作数具有不同的类型,那么在进行运算之前先要把"较低"的类型提升为"较高"的类型,运算的结果为较高的类型。如果没有unsigned类型的操作数,则只要使用下面这些非正式的规则就可以了:
- 如果其中一个操作数的类型为long double,则将另一个操作数转换为long double类型。
- 如果其中一个操作数的类型为double,则将另一个操作数转换为double类型。
- 如果其中一个操作数的类型为float,则将另一个操作数转换为float类型。
- 将char与short类型的操作数转换为int类型。
- 如果其中一个操作数的类型为long,则将另一个操作数也转换为long类型。
注意,表达式中float类型的操作数不会自动转换为double类型,一般来说,数学函数(如标准头文件<math.h>中定义的函数)使用双精度类型的变量。使用float类型主要是为了在使用较大的数组时节省存储空间,有时也为了节省机器执行时间(双精度算术运算特别费时)。
当表达式中包含unsigned类型的操作数时,转换规则要复杂一些。原因在于,带符号值与无符号值之间的比较运算是与机器相关的,因为它们取决于机器中不同整数类型的大小。
赋值时也要进行类型转换。赋值运算符右边的值需要转换为左边变量的类型,左边变量的类型即赋值表达式结果的类型。
无论是否进行符号拓展,字符型变量都将被转换为整型变量。当把较长的整数转换为较短的整数或char类型时,超出的高位部分将被丢弃。
最后,在任何表达式中都可以使用一个称为强制类型转换的一元运算符强制进行显式类型转换。表达式将按照上述转换规则被转换为类型名指定的类型:(类型名)表达式
。
练习2-3:
/*
编写函数htoi(s),把由十六进制数字组成的字符串(包含可选的前缀0x或者0X)转换为与之等价的整形值。
字符串中允许包含的数字包括:0~9, a~f, A-F
*/
#include <stdio.h>
#include <stdlib.h>
int hexalpha_to_int(int c)
{
char hexalpha[] = "aAbBcCdDeEfF";
int i;
int answer = 0;
for (i = 0; answer == 0 && hexalpha[i] != '\0'; i++)
{
if (hexalpha[i] == c)
{
answer = 10 + (i / 2);
}
}
return answer;
}
unsigned int htoi(const char s[])
{
unsigned int answer = 0;
int i = 0;
int valid = 1;
int hexit;
if (s[i] == '0')
{
++i;
if (s[i] == 'x' || s[i] == 'X')
{
++i;
}
}
while (valid && s[i] != '\0')
{
answer = answer * 16;
if (s[i] >= '0' && s[i] <= '9')
{
answer = answer + (s[i] - '0');
}
else
{
hexit = hexalpha_to_int(s[i]);
if (hexit == 0)
{
valid = 0;
}
else
{
answer = answer + hexit;
}
}
++i;
}
if (!valid)
{
answer = 0;
}
return answer;
}
int main()
{
char *endp = NULL;
char *test[] =
{
"F00",
"bar",
"0100",
"0x1",
"0XA",
"0X0C0BE",
"abcdef",
"123456",
"0x123456",
"deadbeef",
"zog_c"};
unsigned int result;
unsigned int check;
size_t numtests = sizeof test / sizeof test[0];
size_t thistest;
for (thistest = 0; thistest < numtests; thistest++)
{
result = htoi(test[thistest]);
check = (unsigned int)strtoul(test[thistest], &endp, 16);
if ((*endp != '\0' && result == 0) || result == check)
{
printf("Testing %s. Correct. %u\n", test[thistest], result);
}
else
{
printf("Testing %s. Incorrect. %u\n", test[thistest], result);
}
}
return 0;
}
8.自增运算符与自减运算符
C语言提供了两个用于变量递增与递减的特殊运算符。自增运算符 ++ 使其操作数递增1,自减运算符使其操作数递减1。
++ 与 – 这两个运算符特殊的地方主要表现在:它们既可以用作前缀运算符(用在变量前面,如 ++n)。也可以用作后缀运算符(用在变量后面,如 n++)。不同之处在于,表达式 ++n 先将n的值递增1,然后再使用变量n的值,而表达式 n++ 则是先使用变量n的值,然后再将n的值递增1。也就是说,对于使用变量n的值的上下文来说,++n 和 n++ 效果是不同的。
//如果n的值是5,语句执行后x的值为5
x=n++;
//如果n的值是5,语句执行后x的值为6
x=++n;
练习2-4:
/*
squeeze(s1,s2),将字符串s1中任何与字符串s2中字符匹配的字符都删除。
*/
void squeeze(char s1[], char s2[])
{
int i, j, k;
int instr2 = 0;
for (i = j = 0; s1[i] != '\0'; i++)
{
instr2 = 0;
for (k = 0; s2[k] != '\0' && !instr2; k++)
{
if (s2[k] == s1[k])
{
instr2 = 1;
}
}
if (!instr2)
{
s1[j++] = s1[i];
}
}
s1[j] = '\0';
}
#include <stdio.h>
#include <string.h>
int main()
{
char *leftstr[] =
{
"",
"a",
"antidisestablishmentarianism",
"beautifications",
"characteristically",
"deterministically",
"electroencephalography",
"familiarisation",
"gastrointestinal",
"heterogeneousness",
"incomprehensibility",
"justifications",
"knowledgeable",
"lexicographically",
"microarchitectures",
"nondeterministically",
"organizationally",
"phenomenologically",
"quantifications",
"representationally",
"straightforwardness",
"telecommunications",
"uncontrollability",
"vulnerabilities",
"wholeheartedly",
"xylophonically",
"youthfulness",
"zoologically"};
char *rightstr[] =
{
"",
"a",
"the",
"quick",
"brown",
"dog",
"jumps",
"over",
"lazy",
"fox",
"get",
"rid",
"of",
"windows",
"and",
"install",
"linux"};
char buffer[32];
size_t numlefts = sizeof leftstr / sizeof leftstr[0];
size_t numrights = sizeof rightstr / sizeof rightstr[0];
size_t left = 0;
size_t right = 0;
for (left = 0; left < numlefts; left++)
{
for (right = 0; right < numrights; right++)
{
strcpy(buffer, leftstr[left]);
squeeze(buffer, rightstr[right]);
printf("[%s] - [%s] = [%s]\n", leftstr[left], rightstr[right], buffer);
}
}
return 0;
}
练习2-5:
/*
编写函数any(s1,s2),将字符串s2中的任一字符在字符串s1中第一次出现的位置作为结果返回。
如果s1中不包含s2中的字符,则返回-1。(标准库函数strpbrk具有同样的功能,但返回的是指向该位置的指针)
*/
int any(char s1[], char s2[])
{
int i, j, pos;
pos = -1;
for (i = 0; pos == -1 && s1[i] != '\0'; i++)
{
for (j = 0; pos == -1 && s2[j] != '\0'; j++)
{
if (s2[j] == s1[i])
{
pos = i;
}
}
}
return pos;
}
#include <stdio.h>
#include <string.h>
int main()
{
char *leftstr[] =
{
"",
"a",
"antidisestablishmentarianism",
"beautifications",
"characteristically",
"deterministically",
"electroencephalography",
"familiarisation",
"gastrointestinal",
"heterogeneousness",
"incomprehensibility",
"justifications",
"knowledgeable",
"lexicographically",
"microarchitectures",
"nondeterministically",
"organizationally",
"phenomenologically",
"quantifications",
"representationally",
"straightforwardness",
"telecommunications",
"uncontrollability",
"vulnerabilities",
"wholeheartedly",
"xylophonically",
"youthfulness",
"zoologically"};
char *rightstr[] =
{
"",
"a",
"the",
"quick",
"brown",
"dog",
"jumps",
"over",
"lazy",
"fox",
"get",
"rid",
"of",
"windows",
"and",
"install",
"linux"};
size_t numlefts = sizeof leftstr / sizeof leftstr[0];
size_t numrights = sizeof rightstr / sizeof rightstr[0];
size_t left = 0;
size_t right = 0;
int passed = 0;
int failed = 0;
int pos = -1;
char *ptr = NULL;
for (left = 0; left < numlefts; left++)
{
for (right = 0; right < numrights; right++)
{
pos = any(leftstr[left], rightstr[right]);
ptr = strpbrk(leftstr[left], rightstr[right]);
if (-1 == pos)
{
if (ptr != NULL)
{
printf("Test %d/%d failed.\n", left, right);
++failed;
}
else
{
printf("Test %d/%d passed.\n", left, right);
++passed;
}
}
else
{
if (ptr == NULL)
{
printf("Test %d/%d failed.\n", left, right);
++failed;
}
else
{
if (ptr - leftstr[left] == pos)
{
printf("Test %d/%d passed.\n", left, right);
++passed;
}
else
{
printf("Test %d/%d failed.\n", left, right);
++failed;
}
}
}
}
}
printf("\n\nTotal passes %d, fails %d, total tests %d\n", passed, failed, passed + failed);
return 0;
}
9.按位运算符
C语言提供了6个位操作运算符。这些运算符只能作用于整形操作数,即只能作用于带符号或无符号char、short、int、long类型:
符号 | 含义 |
---|---|
& | 按位与(AND) |
| | 按位或(OR) |
^ | 按位异或(XOR) |
<< | 左移 |
>> | 右移 |
~ | 按位求反(一元运算符) |
按位与运算符 & 经常用于屏蔽某些二进制位;按位或运算符 | 常用于将某些二进制位置为1;按位异或运算符 ^ 当两个操作数的对应位不相同时将该位置设置为1,否则,将该位置设置为0。
必须将位运算符 &,| 同逻辑运算符 && || 区分开,后者用于从左到右求表达式的真值。
注意:移位运算符 << 与 >> 分别用于将运算的左操作数左移与右移,移动的位数则由右操作数指定(右操作数的值必须是非负值)。表达式 x<<2 将把x的值左移2位,右边空出的2位用0填补,该表达式等价于对左操作数乘以4.在对unsigned类型的无符号值进行右移位时,左边空出的部分将用0填补;当对signed类型的带符号值进行右移时,某些机器将对左边空出的部分用符号位填补(算术移位),而另一些机器则对左边空出的部分用0填补(逻辑移位)。
一元运算符 ~ 用于求整数的二进制反码,即分别将操作数各二进制位上的1变为0,0变为1。
以函数 getbits(x,p,n)为例子,返回x中从右边数第p位开始向右数n位的字段。假定最右边的一位是第0位,n与p都是合理的正值。getbits(x,4,3)返回x中第4,3,2三位的值。
unsigned getbits(unsigned x,int p,int n)
{
return (x>>(p+1-n)) & ~(~0<<n);
}
其中,表达式m<<(p+1-n)将期望获得的字段移位到字的最右端。~0的所有位都为1,这里使用语句~0 << n将~0左移n位,并将最右边的n位用0填补。再使用~运算对它按位取反,这样就建立了最右边n位全为1的屏蔽码。
练习2-6:
/*
编写一个函数setbits(x,p,n,y),该函数返回对x执行下列操作后的结果:
将x中从第p位开始的n个(二进制)位设置为y中最右边n位的值,x的其余各位保持不变。
*/
#include <stdio.h>
unsigned setbits(unsigned x, int p, int n, unsigned y)
{
return (x & ((~0 << (p + 1)) | (~(~0 << (p + 1 - n))))) | ((y & ~(~0 << n)) << (p + 1 - n));
}
int main()
{
unsigned i, j, k;
int p, n;
for (i = 0; i < 30000; i += 511)
{
for (j = 0; j < 1000; j += 37)
{
for (p = 0; p < 16; p++)
{
for (n = 1; n <= p + 1; n++)
{
k = setbits(i, p, n, j);
printf("setbits(%u,%d,%d,%u)=%u\n", i, p, n, j, k);
}
}
}
}
return 0;
}
练习2-7:
/*
编写一个函数invert(x,p,n),该函数返回对x执行下列操作后的结果值:
将x中从第p位开始的n个(二进制)位求反(即,1变成0,0变成1),x的其余
各位保持不变。
*/
#include <stdio.h>
unsigned invert(unsigned x, int p, int n)
{
return x ^ (~(~0U << n) << p);
}
int main()
{
unsigned x;
int p, n;
for (x = 0; x < 700; x += 49)
{
for (n = 1; n < 8; n++)
{
for (p = 1; p < 8; p++)
{
printf("%u, %d, %d: %u\n", x, n, p, invert(x, n, p));
}
}
}
return 0;
}
练习2-8:
/*
编写一个函数rightrot(x,n),该函数返回将x循环右移(即从最右端
移出的位将从最左端移入)n(二进制)位后所得到的值。
*/
#include <stdio.h>
unsigned rightrot(unsigned x, unsigned n)
{
while (n > 0)
{
if ((x & 1) == 1)
{
x = (x >> 1) | ~(~0U >> 1);
}
else
{
x = (x >> 1);
}
n--;
}
return x;
}
int main()
{
unsigned x;
int n;
for (x = 0; x < 700; x += 49)
{
for (n = 1; n < 8; n++)
{
printf("%u, %d,: %u\n", x, n, rightrot(x, n));
}
}
return 0;
}
10.赋值运算符与表达式
如果表达式左边的变量重复出现在表达式的右边,如:i=i+2可以缩写为i+=2,其中运算符+=称为赋值运算符。
大多数二元运算符(即有左右两个操作数的运算符)都有一个相应的赋值运算符op=,其中op可以是以下运算符之一:+,-,*,/,%,<<,>>,&,^,|。
如果expr1和expr2是表达式,那么expr1 op= expr2等价于expr1=(expr1) op (expr2),区别在于,前一种形式expr1只计算一次。注意,在第二种形式中,expr2两边的圆括号必须有,例如:x *= y + 1
等价于x = x * (y + 1)
。
赋值运算符除了简洁外,表示方式与人们的思维习惯比较接近。使程序代码更好理解,有助于编译器产生高效代码。
11.条件表达式
条件表达式(使用三元运算符 ?:)提供了另外一种方法编写这段程序:expr1 ? expr2 : expr3中,首先计算expr1,如果其值不等于0(为真),计算expr2的值,并以该值作为条件表达式的值,否则计算expr3的值,expr2和expr3只能有一个表达式被计算。
注意:如果碰到表达式类型不同,还是按照转换规则进行转换。条件表达式中第一个表达式两边的圆括号并不是必须的,这是因为条件表达式?:的优先级非常低,仅高于赋值运算符。但我们还是建议使用圆括号,因为可以使表达式更易于阅读。
12.运算符优先级与求值次序
运算符 | 结合性 |
---|---|
() [] -> . | 从左到右 |
! ~ ++ – + - * (type)sizeof | 从右到左 |
* / % | 从左到右 |
+ - | 从左到右 |
<< >> | 从左到右 |
< <= > >= | 从左到右 |
== != | 从左到右 |
& | 从左到右 |
^ | 从左到右 |
| | 从左到右 |
&& | 从左到右 |
|| | 从左到右 |
? : | 从左到右 |
= += -= *= /= %= &= ^= |= <<= >>= | 从右到左 |
, | 从右到左 |
注意:一元运算符+、-、&与 * 比相应的二元运算符+、-、&与 * 的优先级高。
- 点赞
- 收藏
- 关注作者
评论(0)