位运算
- 指的是1个二进制数据的每一位来参与运算
- 位运算的前提:是这个数必须是1个二进制
- 参与位运算的二进制数据必须是补码形式
- 位运算的结果也是二进制的补码形式
按位与:&
- 参与按位与的两个二进制数。如果都为1,那么结果就是1,只要有1位为0,那么结果就为0
- 先得到两个数的二进制补码形式
- 每一对应的数码进行比较
- 将得到的结果连成新的二进制
- 任何数按位与1的结果,永远等于这个数的最低位
可以用按位与判断一个数是否是奇数或偶数
- 因为偶数的最低位一定为0,所以偶数按位与1的结果永远为0
- 因为奇数的最低位一定为1,所以奇数按位与1的结果永远为1
- 所以如果需要判断一个数是奇数还是偶数,可以将这个数按位与1来判断。如果为1,这个数是奇数;如果为0,这个数是偶数。
- 例子
3 & 2
3的补码:00000000 00000000 00000000 00000011
2的补码:00000000 00000000 00000000 00000010
结果的补码:00000000 00000000 00000000 00000010
十进制结果:2
-3 & 4
-3的原码: 10000000 00000000 00000000 00000011
-3的反码: 11111111 11111111 11111111 11111100
-3的补码: 11111111 11111111 11111111 11111101
4的补码: 00000000 00000000 00000000 00000100
结果的补码:00000000 00000000 00000000 00000100
十进制结果:4
-3 & -4
-4的原码: 10000000 00000000 00000000 00000100
-4的反码: 11111111 11111111 11111111 11111011
-4的补码: 11111111 11111111 11111111 11111100
-3的补码: 11111111 11111111 11111111 11111101
结果的补码:11111111 11111111 11111111 11111100
结果的反码:11111111 11111111 11111111 11111011
结果的原码:10000000 00000000 00000000 00000100
十进制结果:-4
按位与的优先级比判断运算符低(==、>、<、…),所以如果需要做判断时应该用括号将算式括起来以改变运算顺序,如:(num & 1) == 0
按位或:|
- 参与按位与的两个二进制数。如果有一个为1则结果为1,两个值都为0,那么结果就为0
- 例子
3 | 2
3的补码: 00000000 00000000 00000000 00000011
2的补码: 00000000 00000000 00000000 00000010
结果的补码:00000000 00000000 00000000 00000011
十进制结果:3
-3 & 4
-3的原码: 10000000 00000000 00000000 00000011
-3的反码: 11111111 11111111 11111111 11111100
-3的补码: 11111111 11111111 11111111 11111101
4的补码: 00000000 00000000 00000000 00000100
结果的补码:11111111 11111111 11111111 11111101
结果的反码:11111111 11111111 11111111 11111100
结果的原码:10000000 00000000 00000000 00000011
十进制结果:-3
-3 & -4
-4的原码: 10000000 00000000 00000000 00000100
-4的反码: 11111111 11111111 11111111 11111011
-4的补码: 11111111 11111111 11111111 11111100
-3的补码: 11111111 11111111 11111111 11111101
结果的补码:11111111 11111111 11111111 11111101
结果的反码:11111111 11111111 11111111 11111100
结果的原码:10000000 00000000 00000000 00000011
十进制结果:-3
按位取反: ~
- 单目运算符,将一个二进制数的每个数码取反。
- 例子
~3
3的补码:00000000 00000000 00000000 00000011
结果的补码:11111111 11111111 11111111 11111100
结果的反码:11111111 11111111 11111111 11111011
结果的原码:10000000 00000000 00000000 00000100
十进制结果:-4
按位异或:^
- 比较两个二进制数的对应数位的数码,相同为0,不同为1
- 按位异或可以用来调换两个数的值:假设int a = 3,b = 2;
- 例子
a = a ^ b = 3 ^ 2
3的补码: 00000000 00000000 00000000 00000011
2的补码: 00000000 00000000 00000000 00000010
结果的补码:00000000 00000000 00000000 00000001
十进制结果:a = 1
b = a ^ b = 1 ^ 2
1的补码: 00000000 00000000 00000000 00000001
2的补码: 00000000 00000000 00000000 00000010
结果的补码:00000000 00000000 00000000 00000011
十进制结果:b = 3
a = a ^ b = 1 ^ 3
1的补码: 00000000 00000000 00000000 00000001
3的补码: 00000000 00000000 00000000 00000011
结果的补码:00000000 00000000 00000000 00000010
十进制结果:a = 2
按位左移:<<
a << b
- 将二进制a向左移b个数位,高位溢出则舍弃,低位不够则补0
- 左移运算有可能改变a的正负性
- 将a左移b位,相当于将a乘以2的b次方
- 例子
3 << 2
3的补码:00000000 00000000 00000000 00000011
运算:00000000 00000000 00000000 0000001100
结果的补码:00000000 00000000 00000000 00001100
十进制结果:12
相当于:3 2^2 = 3 4 = 12
按位右移:>>
a >> b
- 将二进制a向右移b位,低位溢出则舍弃,高位不足补符号位
- 右移运算不会改变a的正负性
- 将a右移b位,相当于将a除以2的b次方(商)
- 例子
3 >> 2
3的补码: 00000000 00000000 00000000 00000011
运算: 0000000000 00000000 00000000 00000011
结果的补码:00000000 00000000 00000000 00000000
十进制结果:0
相当于:3 / 2^2 = 3 / 4 = 0
-3 >> 4
-3的补码: 11111111 11111111 11111111 11111101
运算: 111111111111 11111111 11111111 11111101
结果的补码:11111111 11111111 11111111 11111111
结果的反码:11111111 11111111 11111111 11111110
结果的原码:10000000 00000000 00000000 00000001
十进制结果:-1
相当于:???
内存中的五大区域
前提
- 不管哪个区域,都是用来存储数据的
- 不同类型的数据存储在不同的区域,便于系统的管理
五大区域
- 栈
用来存储局部变量,所有的局部变量全部都声明在栈区域中
- 堆
允许程序员手动申请堆中的空间进行使用
- BSS段
用来存储未初始化的全局变量与静态变量
- 数据段(常量区)
用来存储已初始化的全局变量与静态变量以及常量数据
- 代码段
用来存储程序的代码/指令
字符串
如何存储字符串
- 原理:
将字符串数据的每1个字符存储到字符数组中,并在后面追加1个“0”代表字符串存储完毕
最根本的存储方法:
char name[5] = {‘j’, ‘a’, ‘c’, ‘k’, ‘\0’};
将字符串的每一个字符存储到字符数组中,在后面追加一个“0”代表存储结束
简写方式:
char name[] = {“jack”};
系统自动的会将这个字符串中的每一个字符存储到字符数组中,并自动追加一个“0”
最常用的方式:
char name[] = “jack”;
等同于简写的方式
如何输出字符串
使用%s来输出字符串变量,其原理是:
从给定的数组的地址开始,逐个字节的读取并显示,直到遇到“0”字节,结束。
如何接收用户输入的字符串
scanf( )函数
- 定义一个字符数组用来接取输入字符串
- 使用%s来定义用户输入的是一个字符串值
注意
- 接取输入内容的字符数组的长度必须比输入字符长度大
- 输入内容时不可以输入空格,否则系统会认为输入结束,空格后的内容将无法接收
计算字符串的长度
不可用sizeof去计算字符数组的长度来得到字符串的长度,因为有可能字符串数据存储在字符数组中只占了一部分,正确的计算方式为:
从第一个字节开始计数,直到遇到“0”结束。
字符串常用函数
stdio.h文件下的函数:
puts( )函数
- 格式:puts(字符数组名);
- 作用:用来输出字符串
- 优点:输出完成后自动换行
- 缺点:只能输出字符串
gets( )函数
- 格式:gets(用来保存字符串的数组);
- 作用:接收用户输入并赋值给给定的字符数组
- 优点:相较于scanf函数,gets函数可以接收空格字符
- 缺点:不安全
fputs( )函数
- 格式:fputs(要输出的字符串,指定的流);
- 作用:将字符串数据输出到指定的流中
流:
- 标准输出流 —> 控制台,指定的流:stdout
- 文件流 —> 磁盘上的文件
先声明一个文件指针,指向磁盘上的文件:FILE *pfile = fopen(指向的文件路径,操作文件的模式)
操作模式:
- w —> 写,当文件不存在则创建;存在则覆盖
- r —> 读
- a —> 追加输入,在文件的最后面追加内容
- 注意:输出完成后需要使用fclose(文件指针)关闭文件。
fgets( )函数
- 格式:fgets( );
参数:
- 字符指针:存储读取的字符床
- int:最多接收的字符串的长度,实际接收长度为n-1个,还有1个被“0”占用了
- 3.指定的流
- 作用:读取指定的流中的内容
比较:
- scanf:不安全,输入的空格会被认为结束
- gets:不安全
- fgets:安全,空格也会接受
流:
- 标准输入流 —> 控制台,指定的流:stdin
- 文件流 —> 磁盘上的文件
- 注意:输出完成后需要使用fclose(文件指针)关闭文件。
string.h文件下的函数:
strlen( )函数
- 格式:int len = strlen(字符数组名);
- 作用:得到存储在字符数组中的字符串数据的长度
strcmp()函数
- cmp —> compare 比较
- 格式:int res = strcmp(字符数组1,字符数组2);
- 作用:用来比较两个字符串的大小
返回值:
- 如果返回的是负数,说明字符串1比字符串2小
- 如果返回的是证书,说明字符串1比字符串2大
- 如果返回的是0,说明一样
- 比较规则:比的是相同位置的字符的ASCII码的大小
strcpy( )函数
- cpy —> copy 拷贝
- 格式:strcpy(字符数组1,字符数组2);
- 作用:用来将字符数组2的字符串拷贝到字符数组1中
- 可能的问题:存储字符串1的字符数组长度不够,无法存储字符串2,这个时候会报错
strcat()函数
- cat —> concat 连接
- 格式:strcat(字符数组1,字符数组2);
- 作用:把存储在字符数组2中的字符串数据连接到字符串1的后面
- 可能的问题: 存储字符串1的数组剩余的长度不足以存储字符串2时,会报错
指针与字符串数据
可以使用字符指针来存储字符串数据
char *name = “jack";
指针存储字符串与字符数组存储字符串的区别
char name1[] = "jack";
char *name2 = “jack”;
当他们作为局部变量时:
- name1字符数组是申请在栈区域,字符串的每一个字符存储在这个字符数组的每一个元素中
- name2指针变量是申请在栈区域,字符串数据是以字符数组的形式存储在常量区中,name2指针变量中存储的是字符串在常量区中的地址
当他们作为全局变量时:
- name1字符数组是申请在常量区,字符串的每1个字符是存储在这个数组的每一个元素中
- name2指针变量是申请在常量区,字符串数据也是以字符数组的形式存储在常量区中,name2指针变量中存储的是字符串在常量区中的地址
区别:
区别1:
- 以字符数组存储时:无论如何都是1个字符数组,并且字符串的每一个字符存储在数组的元素中
- 以字符指针存储时:无论如何首先都有1个指针变量,字符串数据是以字符数组的形式存储在常量区
区别2:可变与不变性
- 以字符数组存储的字符串,无论全局还是局部变量,都可以使用下标修改字符数组元素的值
- 以字符指针的形式存储字符串数据,无论全局还是局部变量,都字符指针说指向的字符数组的值是不可修改的
字符串的恒定性
- 大前提:以字符指针形式存储的字符串
- 字符指针形式存储的字符串,其字符串数据必定是存储在常量区中,且不可更改
- 当我们以字符指针的形式要将字符串数据存储到常量区的时候,并不是直接将字符串存储到常量区中,而是先检查常量区中是否已经存在相同内容的字符串,如果存在则直接将字符指针指向现有的字符串地址,如果不存在才会将这个字符串数据存储到常量区中。
- 注意:当重新为字符指针初始化1个字符串时,并不是修改原有字符串,而是重新创建了一个字符串,并将指针指向新字符串,所以这种情况并不违背字符串的恒定性
以字符指针形式存储字符串的优势
- 以字符数组存储的字符串数据,长度固定,一旦创建后最多就只能存储这么长的支付数据了;而以字符指针的形式存储的字符串长度不限
- 修改显示的值,字符指针比字符数组更便捷
字符串数组
char *names[] = {“jack”,”rose”};
使用指针数组存储字符串数组的优势
每1个字符串的长度没有限制,而二维数组存储字符串数组时,每1个字符串的长度不能超过二维数组的列数-1
字符串数组的排序
将字符串数组当中的字符串以字母的形式排序
- 冒泡排序
- 使用strcmp( )函数比较两个字符串的大小