目录
数据类型与表达式
一、赋值运算符和赋值表达式
1)赋值运算符
顾名思义:就是将赋运算符右边表达式的值赋给左边的变量。
如a=3; r=x%y;
2)复合赋值运算符
在赋值运算符之前加上其他的运算符,构成复合运算符
复合运算符:+=、-=、*=、/=、%=、<<=、>>=、&=、|=和^=。
前五个运算符是由算术运算符和赋值运算符一起构成的;后五个运算符是由位运算符和赋值运算符一起构成的。参加复合运算符的操作数有两个,先进行两个操作数的算术运算或位运算,然后将其结果赋值给第一个操作数。如:
- a+=3 等价于 a=a+3;
- y*=y+z 等价于 y=y*(y+z);
- x%=3 等价于 x=x%3;
- i<<=3 等价于 i=i<<3。
3)赋值表达式
由赋值运算符将一个变量和一个表达式连接起来的式子称为赋值表达式,格式为:变量=表达式
在赋值表达式一般格式中,表达式仍然可以时一个表达式,也就是说赋值表达式可以嵌套。
如:x=(y=8),括号内的表达式也是一个赋值表达式,其运算过程为:先把常量8赋值给变量y,赋值表达式y=8的值为8,再将这个表达式的值赋给变量x,因此运算结果x和y的值都是8,整个赋值表达式的值也是8。
a=b=c=5://整个表达式的值为5,a、b、c的值也为5
a=5+(c=6)//整个表达式的值为11,a的值也为11,c的值为6
x=(y=4)/(z=3)//整个表达式的值为整数1,x的值为1,y的值为4.z的值为3
赋值表达式中的赋值运算符也可以包含复合赋值运算符。
如:a+=a-=a*a;(如果ade初始值为10)
先进行 a-=a*a 的运算,相当于a=a-a*a=10-100=-90;
再进行 a+=-90 的运算,相当于a=a+(-90)=-90+(-90)=-180。
将赋值表达式作为表达式的一种,使赋值操作符不仅可以出现在赋值中,而且可以以表达形式出现在其他语句中(输出语句、循环语句等)中
如:
printf("%d", a = b);
如果b的值为3,则输出a的值(也是表达式a=b 的值)为3。在一个语句中完成了赋值和输出双重功能。
二、逗号运算符和逗号表达式
逗号运算符“,”是c语言中一个比较特殊的运算符,它的作用是将若干个表达式连接起来。
一般形式为:
表达式1,表达式2,表达式3,………,表达式n
这样用逗号把若干独立的运算表达式结合成为一个表达式称为逗号表达式。逗号表达式的值是最后一个表达式n的值,它的求解过程是:先计算表达式1的值,再计算表达式2的值……一直计算到表达式n的值,所以逗号表达式也称为顺序求解表达式。
如:
a=3,b=5,c=a*b
它是由三个独立的表达式结合而成的。因此a的值是3,b的值是5,c的值是15,整个逗号表达式的值是15。
注:
- 逗号表达式可以嵌套,即一个逗号表达式又可以与另一个表达式组成一个新的表达式,如 :
(x = 8, x * 4), x * 2;/*整个逗号表达式的值是32,x的值为16,(x=8,x*4)表达式的值为64*/
- 逗号表达式也可以作为赋值运算符的右边表达式来使用,如:
x = (i = 4, j = 6, k = 8);/*整个表达式为赋值表达式,将逗号表达式的值赋给x,x的值为8*/
- 逗号运算符的优先级是最低的,因此下面两个表达式的作用是不同的
x = (z = 5, 5 * 2);/*赋值表达式,将一个逗号表达式的值赋给x,x的值为10,z的值为5*/ x = z = 5, 5 * 2;/*逗号表达式,它包括一个赋值表达式和一个算术表达式,整个表达式的值为10,x和z的值都为5*/
逗号表达式不常用。程序中并不是将所有的逗号都看成逗号表达式,例如,在函数调用时,各个参数间的逗号起分隔作用,就不能作为逗号运算符
如以下逗号的功能
printf("%d,%d,%d", (a, b, c), b, c);
printf("%d,%d,%d", x, y, z);
printf("a=%d,b=%d,c=%d\n", c1, c2, c3);
三、条件运算符和条件表达式
条件运算符“?:”是c语言中唯一的三目运算符,即有三个操作数参与运算。
一般形式为:
表达式1?表达式2:表达式3
其求值规则为:如果表达式1的值为真,则以表达式2的值作为条件表达式的值,否则以表达式3的值作为表达式的值
条件表达式通常用于赋值语句之中
如:
if (a > b) max = a;
else max = b;
//可用条件表达式写为
max = (a > b) ? a : b;
该语句的语义是将a和b中的值最大的数赋值给max,即如果a>b,则把a赋值给max,否则把b的值赋值给max。
注:
- 条件运算符的优先级低于关系关系运算符和算术运算符,但高于赋值运算符。因此“max=(a>b)?a:b;”可以去掉括号而写为“max= a>b?a:b ;”
- 条件运算符“?”和“:”是一对运算符,不能分开单独使用
- 条件运算符的结合方向是自右向左。如:a>b? a:c>d? c:d,应理解为a>b? a:(c>d? c:d)。这是条件表达式的嵌套形式,即其中的表达式3又是一个条件表达式。当然,表达式2也可以是一个条件表达式,在计算该式时,先判断a>b,若a>b为真,结果为a,否则结果为表达式c>d?c:d的值
输入两个数,输出其中最大的数
#include<stdio.h>
int main()
{
int a, b=0;
printf("\n Input two number:");
scanf("%d %d", &a, &b);
printf("%d", a > b ? a : b);
return 0;
}
指针
指针是c语言中一个非常重要的概念,也是c语言的一个特色。指针是一种很特殊的数据类型,它既不是简单类型也不是构造类型。利用指针变量可以表示各种数据结构;可以方便地使用数组和字符串;并能像汇编语言一样处理内存地址。
一、指针的概念
指针是一种数据类型,具有指针类型的变量称为指针变量。
要理解指针的概念,首先要理解计算机是如何存储和访问数据的。内存是按字节(8为二进制)排列的存储空间,就像一个大楼里的每一个房间都有编号一样,内存的每个字节也都有一个编号,该编号称为内存地址。为了访问某个数据,就必须知道该数据在内存中的位置(用地址描述)这和实际生活很类似,比如,当要到大楼里找某一个人的时候,就必须知道其所在的房间号
程序中的变量在内存中占用一定的空间,变量名就是其占用内存空间的名称。如定义两个变量 “int i,j;”,假设系统为i分配的空间是从地址0x2700开始的4个字节,为j分配的空间是从地址0x26FC开始的四个字节,变量名i和j就是相对应内存的 空间的名称。
变量的地址就是存储该变量的内存的首地址,如变量i的首地址是0x2700。从0x2700开始的4个字节中存放变量i的值
对变量的访问也就是对变量的存取,通常有两种方式:直接访问和间接访问
直接访问过程
程序中按变量名访问变量的方式称为直接访问。程序编译后,变量名和变量地址之间就建立了对应关系。程序中访问变量时根据对应关系,寻找到该地址并获得该变量的值。如i=5,根据变量名和其地址0x2700的对应关系,把5存入0x2700开始的四个字节的内存空间中。
间接访问过程
变量的地址也可以放在另一个变量中,则存放在该地址的变量称为指针变量,这时访问数据变量时,先经过指针变量获得该该数据变量的地址,再由数据变量地址实现对数据变量的存取。由于指针变量的值是另一个变量的地址,所以习惯上形象地称指针变量指向该变量,指针变量也称为指针
二、指针变量的定义
指针变量定义格式:
类型标识符 *指针变量名1[,*指针变量名2[指针变量名3...]];
如:
int* ip1, * ip2;
说明:
- 定义中“ * ”是表示其后的标识符为指针类型,“ * ”不是指针变量名的一部分。没有“ * ”,该标识符就成了普通的变量名了。
编译程序认为只有一个ip1是指针变量,而ip2是整型变量int* ip1, ip2;
- 定义中的“类型标识符”表示指针变量所指的数据类型,简称指针类型,而不是指针变量本身类型。在c语言中,所有指针变量都是unsigned long int 类型,即指针变量占用的字节数是sizeof(unsigned long int)。 指针变量可以指向基本类型数据和各自自定义类型数据(如结构体、数组)等。
int* ip; //定义了一个指向int型变量指针变量pi char* pch;//定义了一个指向char型变量指针变量pch float* pf;//定义了一个指向float型变量指针变量pf
三、指针运算符
c语言提供了两个与指针有关的运算符&和*,它们都是单目运算符
1)&运算符
“ & ”运算符,称为取地址运算符,作用与内存中一个可寻址的标识符,如变量名。操作的结果是获得该标识符在内存的首地址。
注:常量是不可寻址的,因此“int* p=&20;”是错误的,不可能用“&”取得常量20的地址
2)*运算符
“ * ”运算符,称为间接访问运算符,作用于一个指针类型的变量,其作用是访问该指针所指向变量的值
int a = 3,b;
int* p = &a;//p指向a
*p = 6;//给p指向的变量赋值为6,等价于a=6
b = *p;//将p指向的变量的值赋值给b,等价于b=a
全局指针变量会被系统初始化为NULL(NULL是从语言定义的一个常量,它的值为0),局部指针变量的值未初始化,它是一个随机值。因此,局部指针变量必须先赋值,确定指向的地址后,才能进行间接访问运算。
注:
“int* p=&a;”和“*p=6;”中出现的两个“ * ”号的含义是截然不同的。“int* p=&a;”中的“ * ”号表示p是一个指针变量,在这个“ * ”号前面有一个类型名(比如 int)。而“*p=6;”中的“ * ”号是一个单目运算符,它和p结合完成间接访问运算,代表p所指向的变量。
在c语言中,个别运算符具有不同的作用。如两个指针运算符“ * ”和“&”,就是具有一种以上的功能的两个运算符。间接访问运算符“ * ”与算术运算符乘相同。但是“ * ”作为间接访问运算符,它是单目运算符,它的一个操作对象一定是一个指针变量;而“ * ”作为乘运算符,他是双目运算符。
同样的取地址运算符“&”与按位与运算符相同,但在使用中也有明显的区别,“&”作为取地址运算符,它是单目运算符,它的操作对象也一定是指针变量;而“&”作为按位与运算符,它是双目运算符。
四、指针变量的初始化和运算
1)指针变量的初始值和赋值运算
和定义一个普通变量一样,在定义指针变量时可以同时初始化,语法格式为:
类型标识符 *指针变量名=地址表达式;
在定义指针变量后,也可以使用赋值语句给指针变量赋值,语法格式为:
指针变量名=地址表达式;
如:
int i, j, * ip1 = &i, * ip2 = &j;
ip2 = &j;
定义指针变量ip1的同时用变量i的地址初始化ip1,&i表示变量i的地址,ip1指向了i,对指针变量ip2,用赋值语句将j的地址赋给ip2,也就是ip2指向了j
除了可以将一个变量的地址赋给指针外,同类型的指针变量可以相互赋值,也可以用一个已经赋值的指针初始化另一个同类型的指针。
如
int v1 = 3, v2 = 5;
int* p_v1 = &v1, * p_v2 = &v2;
p_v1 = p_v2;
运算后p_v1和p_v2指向同一个变量
注:
当直接给指针变量赋一个常量时有安全隐患
可以把NULL赋值给任意类型的指针变量或初始化指针变量,此时表示该指针变量不指向任何内存单元
2)指针的算术运算
由于指针变量存放的都是内存地址,所以指针的算术运算都是整数运算
一个指针变量可以加上或减去一个整数值,包括指针变量的++、--运算,但指针变量加(减)一个整数整数并不是简单地将其地址值加(减)该整数,而是加(减)该整数倍指针变量指向的数据类型的长度。例如,p+n实际表示的地址是:p所指向的内存单元的地址+n*sizeof(指针指向的数据类型)
从图中可以看出同样的+n或-n,不同类型的指针实际加减的字节数是不同的,这也是在定义指针是必须指定指针类型的一个原因
此外,如果两个指针所指的数据类型相同,在某些情况下,这两个指针可以相减。例如,指向同一个数组的不同元素的两个指针可以相减,其差便是这两个指针之间相隔元素的个数,又例如在一个字符串里面,让指向字符串尾的指针和指向字符串首的指针相减,就可以得到这个字符串的长度。
注:
编译器可以保证将一个数组的所有元素存放在连续的存储空间中,当两个指针指向数组中不同元素时,可以进行减运算。但是编译器不保证多个普通变量在内存中一定了连续存放,因此两个只想普通变量的指针减运算没有意义。
两个指针相加也是无意义的。一般也不被允许相加。例如p1和p2指向同一数组中前、后不同元素,要计算位于这两个元素的地址,不能写成
p = (p1 + p2) / 2;//会报错
必须写成
p = p1 + (p2 - p1) / 2;
3)指针的关系运算
指针可以进行关系运算。指针间关系运算包括>、>=、<、<=、==、!=
当两个指向相同数据类型的指针相等,就说明它们指向同一个内存单元,
如:
if (p_v1 == p_v2)
printf("两个指针相等");
但两个指向同类型普通变量的指针进行小于、大于等比较没有意义(编译器不保证多个普通变量在内存中一定连续存放)。通常两个指针指向同一组数组的不同元素时,经常对指针进行小于、大于、等于等关系运算。
#include<stdio.h>
int main()
{
double d[10] = { 0,1,2,3,4,5,6,7,8,9 };
int* pd1 = &d[0], * pd2 = &d[9];
if (pd1 < pd2)
printf("pd1<pd2");
return 0;
}
程序运行结果:
一个指针可以和NULL作相等或不相等的关系运算,用来判断该指针是否为空。
#include<stdio.h>
int main()
{
int* p1, * p2, * p, a, b;
printf("请输入a和b的值:");
scanf("%d %d", &a, &b);
p1 = &a;
p2 = &b;
printf("%d\t%d\n", *p1, *p2); //间接访问p1指向的变量和p2指向的变量
if (*p1 < *p2)//如果a<b,交换p1,p2的值,使p1指向b,p2指向a
{
p = p1;
p1 = p2;
p2 = p;
}
printf("%d\t%d\n", a, b);//直接访问a,b,输出a,b的值
printf("%d\t%d\n", *p1, *p2);//间接访问p1指向的变量和p2指向的变量
return 0;
}
程序运行结果:
实现从大到小输出两个数。但程序中没有直接交换a、b的值,而是交换指向a、b变量的指针p1和p2的值,使p1指向较大的变量,p2指向较小的变量。
位运算符
位运算符指对操作数的各二进制位进行运算,c语言提供了6种位运算符
1)按位与运算符
按位与运算符“&”时双目运算符,其功能是将参与运算的两个操作数各对应的二进制位相与,只有对应的两个二进制位均位1时,结果位才为1,否则为0。
例如:
即9&5=1
按位与运算通常用来对某些位清0或保留某些位。例如,把字符变量a的高四位清0,保留低四位的值,可进行a&15运算(15的二进制位00001111)
2)按位或运算
按位或运算符“|”时双目运算符,其功能是将参与运算的两个操作数各对应的二进制位相或,只要对应的两个二进制位有一个为1时,结果位就为1,否则为0。
例如:
即9|5=13
3)按位异或运算符
按位异或运算符“^”是双目运算符,其功能是将参与运算的两个操作数各对应的二进制位相异或,当两对应的二进制位不相同时,结果为1,否则为0。
例如:
即9^5=12
4)按位非运算
按位非运算符“~”为单目运算符,具有右结合性,其功能是对参与运算符的操作数的个二进制位求反
例如:~9的运算位~(00001001),其结果位11110110
5)左移运算符
左移运算符“<<”是双目运算符,其功能是把“<<”左边的操作数的各二进制位左移“<<”右边的操作数指定的位数,左移后的高位丢弃,低位补0
例如:设a=3,求a<<4
三的二进制数为0000 0011 ,将其左移4位为0011 0000(十进制数48)。左移4位,相当于原数扩大2^4=16倍,即左移1位扩大至原来的2 倍
6)右移运算符
右移运算符“>>”是双目运算符,其功能是把“>>”左边的操作数的各二进制位右移“>>”右边的操作数指定的位数
例如:a=15,求a>>2
15的二进制数为0000 1111,将其右移2位位0000 0011(十进制数3)。右移2 位相当于原数缩小值1/2^2=1/4,即右移一位缩小至原来的1/2
注:
- 对于负数,在右移时,符号位将随同移动,并在高位补1.对于正数和0,在移位时最高位补0
- 在上述表达式a<<4和a>>2中,变量a的值在移位操作后并没有改变,仍然分别为3和15
类型转换
在c语言中,允许不同类型的常量、变量出现在一个表达式中,因此,在计算表达式时,不但要考虑运算符的优先级和结合性,还要分析操作数的数据类型。c语言规定,不同类型的数据在一起运算时,必须转换为相同的数据类型。转换的方式有两种:一种时自动类型转换(又称为隐式类型转换);一种是强制类型转换(又称为显示类型转换)
1)自动类型转换
a.赋值运算中的类型转换
当赋值运算符两边表达式的操作数类型不同时,进行自动类型转换,转换的规则是:把赋值运算符右边表达式的类型转换为左边变量的类型
当将实型数据(包括单、双精度)赋给整形变量时,通常时舍弃实型数的小数部分。
如:i为整型变量,执行“i=3.56;”的结果是i的值为3。
当将整型数据赋给实型变量时,数值不变,但以浮点数形式存储到实型变量中
b.混合运算中的类型转换
由于c语言允许整型、实型和字符型数据进行混合运算,所以下面表达式是合法的
5 / 2 + 10 + 'a' + 5.6 * 3;
说明:计算5/2,因两个操作数都为整型数,表达式5/2的结果为2,表达式为2+10+'a'+5.6*3
计算2+10结果为12,表达式为12+'a'+5.6*3
计算12+'a',字符'a'转换为整型数97,然后与12相加结果为109,表达式为109+5.6*3
计算5.6*3,浮点数5.6和整型数3分别转换为double型数,然后相乘,其结果16.800000,表达式为109+16.800000
计算109+16.800000,整数109转换为double型数,然后相加结果为125.800000,最后结果为double型
运算时,c语言编译系统自动将运算符两边的操作数转换成同一种类型。转换规则是:
- float类型必须转换成double类型,char、short类型必须转换为int类型
- 若数据类型按照char、int、float和double的顺序从左到右排列,则当两个不同类型的操作数进行算术运算时,先按排列规则将排在前面的操作数的数据类型转换成排在后面的数据类型,然后在参加运算。例如:char、int、float和double变量进行运算时,都将转换成double类型
2)强制类型转换
在c语言中,可以利用强制类型转换运算符将一个表达式转换成所需类型。
一般形式为:
(类型名)(表达式);
(double)(a);/*将a转换成double类型*/ (float)(5%3)/*将5%3的值转换成float型*/
注:表达式应该用括号括起来。如果写成(int)x+y,则只将x转换成整数,然后与y相加
在强制类型转换时,得到一个所需的中间变量,原来变量的类型未发生变化
如:(int)x;
如果x原指定为float类型,进行强制类型转换运算后得到一个int类型的中间变量,它的值等于x的整数部分,而x的类型不变,仍是float类型
#include<stdio.h>
int main()
{
float x;
int i;
x = 13.36;
i = (int)x;
printf("x=%f,i=%d", x, i);
return 0;
}
程序运行结果:
x类型仍为float型,值仍为13.36
有的表达式必须借助强制类型转换运算,否则不能运算。如:%运算符要求两侧均为整型量,若x为float类型,则x%3不合法,必须强制转换为(int)x%3。强制类型转化运算优先级高于%运算符,因此先进行(int)x的运算,得到一个整型的中间变量,然后再对3求余。此外,在函数调用时,有时为了使实参类型与形参类型一致,也可以用强制类型转换运算符得到一个所需类型的参数