一.数据类型补充
1.类型转换
1)概念:不一致但相互兼容的数据类型,在同一表达式中将会发生类型转换。
2)转换模式:
-
隐式转换:系统按照隐式规则自动进行的转换
-
强制转换:用户显式自定义进行的转换
3)规则: 从小类型向大类型转换,目的是保证不丢失表达式中数据的精度。
隐式转换:
char a = 'a';
int b = 12;
float c = 3.14;
float x = a + b - c; // 在该表达式中将发生隐式转换,所有操作数被提升为float强制转换:(用户强行将某类型的数据转换为另一种类型,此过程可能丢失精度 )
char a = 'a';
int b = 12;
float c = 3.14;
int d = (int)c;
float x = a + b - (int)c; // 在该表达式中a隐式自动转换为int,c被强制转为int
4)注意: 不管是隐式转换,还是强制转换,变换的都是操作数在运算过程中的类型,是临时的,操作数本身的类型不会改变,也无法改变。
-
类型转换,实际上是对先前定义时候的约定,做了一个临时的打破。
-
理论上,可以对任意的数据做任意的类型转换,但转换之后的数据不一定有意义
2.可移植性整型
-
同样的代码,放在不同的系统中,可能会由于数据尺寸发生变化而无法正常运行。
-
因此,系统标准整型数据类型,是不可移植的,这个问题在底层代码中尤为突出。
1)概念:不管放到什么系统,尺寸保持不变的整型数据,称为可移植性整型
2)关键字:typedef
typedef int int32_t; // 将类型 int 取个别名,称为 int32_t
typedef long int64_t; // 将类型 long 取个别名,称为 int64_t
#include <stdio.h>
// 给变量取别名称为可移植数据类型
typedef char int8_t;
int main(int argc, char const *argv[])
{
int8_t a = 'p';
printf("%c\n",a);
return 0;
}
练习:有时候我们需要使用 int32_t 类型变量代替 int 类型变量的原因是什么?
int是系统基本的数据类型,其长度在不同平台下的大小尺寸是有区别的,为了使同一份代码能够在不同的操作系统下面运行,并且尺寸不发生改变,一般使用类似于int32_t这样的可移植类型来定义数据,这些类型是不同平台下对基本数据类型的封装,然后统一发布,这些移植的数据类型一般是放在头文件中,比如/usr/include/stdin.h
二.运算符
1.算术运算符
运算符 | 功能说明 | 举例 |
---|---|---|
+ | 加法,一目取正 | a+b |
- | 减法,一目取负 | a-b |
* | 乘法 | a*b |
/ | 除法 | a/b |
% | 取模(求余) | a%b |
++ | 自加1 | a++, ++b |
– | 自减1 | a–, --b |
// demo1:产生一个随机数,并将此数控制在5的范围内
#include <stdio.h>
#include <stdlib.h>
#include <time.h>int main(int argc, char const *argv[])
{
// 产生随机因子
srand(time(NULL));// 产生随机数,控制在15以内
int ret = rand() % 15;
printf("%d\n",ret);
return 0;
}
注意: 取模运算要求左右两边操作数必须是整型数据,自加自减运算不仅可以对整型操作,也可以对浮点数、指针操作
2.单双目运算
前后缀运算:
前缀自加自减运算:先进行自加自减,再参与表达式运算;
后缀自加自减运算:先参与表达式运算,在进行自加自减
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a = 10;
int b = 20;
int c = ++a; // a先自加再赋值给c
int d = b++; // b先赋值给d再自加
printf("%d,%d,%d,%d\n",a,c,b,d);
int e = 2;
int x = (e++)+(e++);// 错误的写法,两个单目运算符不能进行双目运算
printf("x:%d,e:%d\n",x,e);
int b1 = 2;
int y = (b1++)+(++b1);//(错误的)
printf("b1:%d,e:%d\n",b1,y);// b1:4 y : 6
return 0;
}
printf("%d\n",3/2);// 一个整数除以另一个整数,小数被舍弃
printf("%f\n",3*1.0/2);// 如果想要小数,那么可以*1.0,隐式类型转换1.500000
printf("%d\n",10%3);//%取余运算符的左右两边必须都是整数
// 一般取余运算符在我们编程开发中用于控制数据的大小范围
srand(time(NULL));// 获取随机数因子
int a9 = rand()%5;// rand()得到一个随机数0-4
printf("%d\n",a9);
3.关系运算
运算符 | 功能 | 举例 | 说明 |
---|---|---|---|
> | 大于 | a > b | 判断a是否大于b |
>= | 大于或等于 | a >= 5 | 判断a是否大于或等于5 |
< | 小于 | 3 < x | 判断3是否小于x |
<= | 小于或等于 | x <= (y+1) | 判断x是否小于或等于y+1 |
== | 等于 | (x+1) == 0 | 判断x+1是否等于0 |
!= | 不等于 | c != '\0' | 判断c是否不等于’\0’ |
注意:
注意关系运算符的值为布尔值 也就是说要么关系成立(1) 要么不成立(0)
1 > 10 20 < 30
int a = 10; int b = 20;
a != b;// 1
a == b; // 0
demo:
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a = 10, b = 5;
printf("%d\n",a > b);// 10大于5,为真结果为1
printf("%d\n",a >= b);// 10大于或者等于5,为真,结果为1
printf("%d\n",a < b);// 10小于5,为假,结果0
printf("%d\n",a <= b);// 10小于5或者等于5,为假,结果0
printf("%d\n",a == b);// 10等于5,为假,结果0
printf("%d\n",a != b);// 10不等于5,为真,结果1
return 0;
}
4.逻辑运算
运算符 | 功能说明 | 举例 |
---|---|---|
! | 逻辑反 | !(x==0) |
&& | 逻辑与 | x > 0 && x < 10 |
|| | 逻辑或 | y < 10 || x > 10 |
1)运算规则
-
逻辑反:将逻辑真、假翻转,即真变假,假变真
-
逻辑与:将两个关系表达式串联起来,当且仅当左右两个表达式都为真时,结果为真。
-
逻辑或:将两个关系表达式并联起来,当且仅当左右两个表达式都为假时,结果为假
-
在逻辑与运算中,如果左边表达式的值为假,那么右边表达式将不被执行。(笔试)
-
在逻辑或运算中,如果左边表达式的值为真,那么右边表达式将不被执行。(笔试)
2)非0 为真,0 为假;逻辑表达式结果有两种 1为真 0为假
int a = 10;
int b = 20;
//c语言 惰性运算,也就是说 如果前面的结果为假,后面就不会运行--知难而退
if(a > b && ++a)
{
printf("111\n");
}
printf("%d\n",a);
5.位运算
运算符 | 名称 | 举例 | 功能说明 |
---|---|---|---|
~ | 位逻辑反 | ~a | 将变量 a 中的每一位取反 |
& | 位逻辑与 | a & b | 将变量 a 和 b 逐位进行与操作 |
| | 位逻辑或 | a | b | 将变量 a 和 b 逐位进行或操作 |
^ | 位逻辑异或 | a ^ b | 将变量 a 和 b 逐位进行异或操作 |
<< | 左移 | a << 4 | 将变量 a 中的每一位向左移动4位 |
>> | 右移 | x >> n | 将变量 x 中的每一位向右移动4位 |
1)&:按位与,两个都为1,结果才为1
-
1 & 1 == 1
-
1 & 0 == 0
-
0 & 0 == 0
-
0 & 1 == 0
-
| :按位或,两个都为1,结果才为1
-
1 | 1 == 1
-
1 | 0 == 1
-
0 | 1 == 1
-
0 | 0 == 0
-
-
^ : 按位异或 --- 不同为1,相同为0
-
1 ^ 0 == 1
-
0 ^ 1 == 1
-
0 ^ 0 == 0
-
1 ^ 1 == 0
-
2)移位<< >>
-
无符号左移 :不越界前提下满足规律:number*2^n,移除的丢弃,空出来的补0.
- 有符号左移:先二进制表示(正数直接二进,负数二进制是加个符号位),然后取反,再加一,再移位(空的补0),再减一,再取反。
- 无符号右移:满足number/2^n。移除的丢弃,空出来的补0;
- 有符号右移(特殊一点):先二进制表示(正数直接二进,负数二进制是加个符号位),然后取反,再加一,再移位(空的补1),再减一,再取反
- 注意:符号位(负的时候‘1’)取反时不要变号。
结论:
无符号整数左移,并且有效数据1不丢失时,假设 data << n,等于 data * 2^n
无符号整数右移,并且有效数据1不丢失时,假设 data >> n,等于 data / 2^n
练习:
假设有一个无符号32位整型数据
unsigned int data = 0x12ff0045
请编写一个程序用位运算把data的第14、15位修改1,把22、23位修改为0, 并且输出修改后的数据。
#include <stdio.h>
int main()
{
unsigned int data = 0x12ff0045;
// 将第14,15位置为1
printf("%x\n",data | 0x03 << 14);
// 将22,23位修改为0
printf("%x\n",data & ~(0x03 << 22));
return 0;
}
6.特殊运算符
1)赋值运算符
-
不能对常量赋值
-
只能对变量赋值
-
不能对数组赋值
-
可以连续赋值,从右往左
-
赋值运算符的左边(左操作数)必须是可写的地址空间
7.条件运算符
-
唯一需要三个操作数的运算符
-
语法:表达式1?表达式2:表达式3
-
释义:当表达式1为真时,去表达式2,否则取表达式3
int a = 200;
int b = 100;
int m = (a>b) ? a : b; // 如果 a>b 为真,则 m 取 a 的值,否则取 b 的值
8.sizeo运算符
-
含义:计算指定数据类型或者变量所占据内存的字节数
-
语法:sizeof(类型)、sizeof(变量),计算变量的字节数时圆括号可以省略
printf("%d\n", sizeof(int));// 4
printf("%d\n", sizeof(long double));//12int a[5];
printf("%d\n", sizeof(a));//4*5=20
printf("%d\n", sizeof a);
9.逗号表达式
(表达式1,表达式2,表达式3,... 表达式n)
求值顺序:
先求表达式1,再求表达式2,再求表达式3,最后求表达式n
整个逗号表达式的值为n的值
注意:
1.都好表达式的优先级最低
2.运算顺序是从左往右
3.整个都好表达式的值取决于最右边的表达式的值
10.优先级(笔试)
三.控制流
1.二路分支
-
if语句:表达一种 如果-则的条件执行关系
-
if-else语句:表达一种 如果-否则 的互斥分支关系
练习:打分系统:
60分以下:评级为D
60-80分 : 评级为C
80-90分 : 评级为B
90-100分: 评级为A
不在0-100范围内:错误
#include <stdio.h>
int main(int argc, char const *argv[])
{
float score;
printf("请输入成绩: ");
scanf("%f",&score);
if(score == -1)
{
printf("不好意思,你的卷子丢了,请查监控\n");
}
else if(score >= 0 && score < 60)
{
printf("等级D,不及格,抓紧时间学习,别玩了\n");
}
else if(score >= 60 && score < 80)
{
printf("等级C\n");
}
else if(score >= 80 && score < 90)
{
printf("等级B\n");
}
else if(score >= 90 && score < 100)
{
printf("等级A\n");
}
else
{
printf("成绩输入错误\n");
}
return 0;
}
2.多路分支
switch(n)//n只能是整数以及字符
{
case 1:
printf("one\n");
break;
case 2:
printf("two\n");
break;
case 3:
printf("three\n");
break;
default:
printf("其他数字\n");
}
注意:
-
switch(n)语句中的n必须是一个整型表达式,即switch判断的数据必须是整型或者字符
-
case语句只能带整型常量,包括普通整型或字符,不包括const型数据
-
break语句的作用是跳出整一个switch结构,没有break程序会略过case往下执行
-
default语句不是必须的,一般放在最后面(因此不需要break)
3.const
-
逻辑:使一个变量不可修饰
-
作用:
-
修饰普通变量,使之不可修改
-
修饰指针变量,使之不可修改或者使其指向的目标不可修改
-
int const a = 100; // 定义了一个不可修改的变量a
const int b = 200; // 定义了一个不可修改的变量ba = 101; // 错误
b = 202; // 错误
4.while与do……while
// 循环输出一系列整数,直到100为止
int a;
scanf("%d", &a);
do
{
printf("%d\n", a);
a++;
} while(a <= 100);
// 循环输出一系列整数,直到100为止
int a;
scanf("%d", &a);
while(a <= 100)
{
printf("%d\n", a);
a++;
}
注意:
-
while 循环先进行判断,条件为真后才执行循环体,因此循环体可能一遍也不执行。
-
do-while 循环先执行循环体,再进行判断,因此循环体至少会执行一遍。
-
do-while 循环中的 while 语句后面有分号;
练习:每次从键盘输入两个整型数据,并且比较两个数据的最大值进行输出,当输入相等时结束输入,退出程序。
#include <stdio.h>
int main(int argc, char const *argv[])
{
int num1, num2;
while (1)
{
printf("输入两个整数: ");
int ret = scanf("%d%d",&num1,&num2);
// 缓冲区的数据与scanf需要取的数据不匹配
if(ret != 2)
{
// 清空缓冲区
while (getchar() != '\n');
printf("输入错误,请重新输入\n");
// 重新跑到while(1)
continue;
}
if(num1 == num2)
break;
printf("max = %d\n",num1 > num2 ? num1 : num2);
}
return 0;
}
5.for循环
for(初始表达式1;判断表达式2;循环操作表达式3)
{
循环体;
}
// 第一条表达式只执行一遍i = 0只执行一遍
// 然后执行表达式2,判断i 是否小于等于5,如果为真则执行{}里面的内容
// 最后执行表达式3,i++
// 然后一次循环完成再重新执行表达式2,判断i是否小于5,如果为真会执行
// {}里面的内容,最后执行表达式3,i++,依次类推,直到表达式2为假
// 则退出循环体
for(int i = 0; i <= 5; i++)
{
printf("i:%d\n",i);
}
// 死循环
for(;;)
{
}
或者
for(;;);
//多变量
for(int i = 0,j = 0; i < 5; i++,j++)
{
printf("i:%d j:%d\n",i,j);
}
6.break与continue
-
运用场景与特点
-
break关键字只能用于循环(while for do...while)和switch语句中,表示结束循环或者跳出switch语句
-
break关键字只能跳出最近一层的循环,也就是说,如果有多重循环,只能跳出最靠近break语句那层循环
-
break关键字 不能 单独用于 if语句中,除非if语句外面包含了循环
-
-
逻辑:
-
continue关键字只能用于循环体中(while do...while for),用于提前结束当前循环体后,又从最近一层循环体开始执行
-
continue关键字不能 单独用于 if语句或者switch 语句中,除非外面包含了循环
-
break:① 跳出 switch 语句; ② 跳出当层循环体while(1){while(1){break;}}
-
continue:结束当次循环,进入下次循环
-
7.goto语句
goto 语句标号;//程序会直接跳转到语句标号的地方执行
语句标号:
实例:
int main()
{
printf("%d\n", __LINE__); // 打印第3行
// 无条件跳转到label处
goto label;
printf("%d\n", __LINE__); // 打印第7行,此处被略过
label:
printf("%d\n", __LINE__); // 打印第9行
}
注意:
-
语法:
-
goto语句直接跳转到本代码块中的标签处
-
标签指的是以冒号结尾的标识符
-
-
作用:
-
goto语句的无条件跳转不利于程序的可读性,一般不建议使用
-
goto语句常被用在程序的错误处理中
-
四.数组初识
1)概念:
-
由相同类型的多个元素所组成的一种复合数据类型
-
在工程中同时定义多个相同类型的变量时,重复定义,可以使用数组
2)逻辑(重要):
3)初始化:
// 正常初始化
int a[5] = {100,200,300,400,500};int a[5] = {100,200,300,400,500,600}; // 错误,越界了
int a[ ] = {100,200,300}; // OK,自动根据初始化列表分配数组元素个数
int a[5] = {100,200,300}; // OK,只初始化数组元素的一部分
// 不能在使用变长数组的情况下初始化数组
int a[size] = {1,2,3};//编译出错
// 变长数组只能先定义再使用
int a[size]; // 正确的
a[0] = 10;
4)语法句:
#include <stdio.h>
int main(int argc, char const *argv[])
{
// 定义数组并初始化
int Array[5] = {10,25,31,48,60};
printf("%d\n",Array[2]);// 初始化的时候确定数组空间大小
int Array0[] = {10,20,39};
printf("%d\n",Array0[2]);
// 初始化一部分空间,剩余的空间默认初始化为0
int Array1[10] = {68,70};// 定义数组并清空数组
int Array2[10] = {0};// 变长数组
int len = 3;
// 错误,初始化的时候,数组大小必须为常量
//int Array3[len] = {100,200,300};
// 哪怕不确定len的大小,但是可以确定Array3的空间最小值为一个
// int类型的空间,所以至少可以存放一个int类型的数据
int Array3[len];
Array3[0] = 100;
printf("Array3[0] = %d\n",Array3[0]);
return 0;
5)测量大小
测量数组的总大小:sizeof(array)
测量数组元素个数:sizeof(array)/sizeof(array[0])
#include <stdio.h>
int main(int argc, char const *argv[])
{
// 根据初始化的时候分配空间从而确定数组的大小
int Array[] = {10,20};
printf("%d\n",sizeof(Array));
// 计算数组的元素个数
int count = sizeof(Array) / sizeof(Array[0]);
printf("数组空间个数: %d\n",count);
return 0;
}
6)数组元素引用
数组名[下标]
"下标":C语言的下标是从0开始,下标必须是>=0的整数
a[0]、a[1]、a[n]引用数组元素a[i]和普通变量一样,既可以作为左值,也可以作为右值
下标最小值为0,最大值为 元素个数 -1int a[5]; // 有效的下标范围是 0 ~ 4
a[0] = 1;
a[1] = 66;
a[2] = 21;
a[3] = 4;
a[4] = 934;a[5] = 62; // 错误,越界了
a = 10; // 错误,不可对数组名赋值
练习:
#include <stdio.h>
int main()
{
char buf[16]="0123456789ABCDEF";
int index[16]={0};
int i = 0;
int j;
int data = 123;
while (data != 0)
{
index[i] = data % 16;
data = data /16;
i++;
}
for(j=i-1;j>=0;j--)
{
printf("%c",buf[index[j]]);
}
return 0;
}