位运算

  • 指的是1个二进制数据的每一位来参与运算
  • 位运算的前提:是这个数必须是1个二进制
  • 参与位运算的二进制数据必须是补码形式
  • 位运算的结果也是二进制的补码形式

按位与:&

  1. 参与按位与的两个二进制数。如果都为1,那么结果就是1,只要有1位为0,那么结果就为0
  2. 先得到两个数的二进制补码形式
  3. 每一对应的数码进行比较
  4. 将得到的结果连成新的二进制
  5. 任何数按位与1的结果,永远等于这个数的最低位
  6. 可以用按位与判断一个数是否是奇数或偶数

    • 因为偶数的最低位一定为0,所以偶数按位与1的结果永远为0
    • 因为奇数的最低位一定为1,所以奇数按位与1的结果永远为1
    • 所以如果需要判断一个数是奇数还是偶数,可以将这个数按位与1来判断。如果为1,这个数是奇数;如果为0,这个数是偶数。
  7. 例子

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则结果为1,两个值都为0,那么结果就为0
  2. 例子

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

按位取反: ~

  1. 单目运算符,将一个二进制数的每个数码取反。
  2. 例子

~3

3的补码:00000000 00000000 00000000 00000011
结果的补码:11111111 11111111 11111111 11111100
结果的反码:11111111 11111111 11111111 11111011
结果的原码:10000000 00000000 00000000 00000100
十进制结果:-4

按位异或:^

  1. 比较两个二进制数的对应数位的数码,相同为0,不同为1
  2. 按位异或可以用来调换两个数的值:假设int a = 3,b = 2;
  3. 例子

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
  1. 将二进制a向左移b个数位,高位溢出则舍弃,低位不够则补0
  2. 左移运算有可能改变a的正负性
  3. 将a左移b位,相当于将a乘以2的b次方
  4. 例子

3 << 2

3的补码:00000000 00000000 00000000 00000011
运算:00000000 00000000 00000000 0000001100
结果的补码:00000000 00000000 00000000 00001100
十进制结果:12
相当于:3 2^2 = 3 4 = 12

按位右移:>>

    a >> b
  1. 将二进制a向右移b位,低位溢出则舍弃,高位不足补符号位
  2. 右移运算不会改变a的正负性
  3. 将a右移b位,相当于将a除以2的b次方(商)
  4. 例子

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
相当于:???


内存中的五大区域

前提

  1. 不管哪个区域,都是用来存储数据的
  2. 不同类型的数据存储在不同的区域,便于系统的管理

五大区域

用来存储局部变量,所有的局部变量全部都声明在栈区域中

允许程序员手动申请堆中的空间进行使用

  1. BSS段

用来存储未初始化的全局变量与静态变量

  1. 数据段(常量区)

用来存储已初始化的全局变量与静态变量以及常量数据

  1. 代码段

用来存储程序的代码/指令


字符串

如何存储字符串

  • 原理:

将字符串数据的每1个字符存储到字符数组中,并在后面追加1个“0”代表字符串存储完毕

  • 最根本的存储方法:

    char name[5] = {‘j’, ‘a’, ‘c’, ‘k’, ‘\0’};

    将字符串的每一个字符存储到字符数组中,在后面追加一个“0”代表存储结束

  • 简写方式:

    char name[] = {“jack”};

系统自动的会将这个字符串中的每一个字符存储到字符数组中,并自动追加一个“0”

  • 最常用的方式:

    char name[] = “jack”;

等同于简写的方式

如何输出字符串

使用%s来输出字符串变量,其原理是:

从给定的数组的地址开始,逐个字节的读取并显示,直到遇到“0”字节,结束。

如何接收用户输入的字符串

  • scanf( )函数

    1. 定义一个字符数组用来接取输入字符串
    2. 使用%s来定义用户输入的是一个字符串值
  • 注意

    1. 接取输入内容的字符数组的长度必须比输入字符长度大
    2. 输入内容时不可以输入空格,否则系统会认为输入结束,空格后的内容将无法接收

计算字符串的长度

不可用sizeof去计算字符数组的长度来得到字符串的长度,因为有可能字符串数据存储在字符数组中只占了一部分,正确的计算方式为:

从第一个字节开始计数,直到遇到“0”结束。

字符串常用函数

stdio.h文件下的函数:

  1. puts( )函数

    • 格式:puts(字符数组名);
    • 作用:用来输出字符串
    • 优点:输出完成后自动换行
    • 缺点:只能输出字符串
  2. gets( )函数

    • 格式:gets(用来保存字符串的数组);
    • 作用:接收用户输入并赋值给给定的字符数组
    • 优点:相较于scanf函数,gets函数可以接收空格字符
    • 缺点:不安全
  3. fputs( )函数

    • 格式:fputs(要输出的字符串,指定的流);
    • 作用:将字符串数据输出到指定的流中
    • 流:

      • 标准输出流 —> 控制台,指定的流:stdout
      • 文件流 —> 磁盘上的文件
    • 先声明一个文件指针,指向磁盘上的文件:FILE *pfile = fopen(指向的文件路径,操作文件的模式)

      • 操作模式:

        • w —> 写,当文件不存在则创建;存在则覆盖
        • r —> 读
        • a —> 追加输入,在文件的最后面追加内容
      • 注意:输出完成后需要使用fclose(文件指针)关闭文件。
  4. fgets( )函数

    • 格式:fgets( );
    • 参数:

      • 字符指针:存储读取的字符床
      • int:最多接收的字符串的长度,实际接收长度为n-1个,还有1个被“0”占用了
      • 3.指定的流
    • 作用:读取指定的流中的内容
    • 比较:

      • scanf:不安全,输入的空格会被认为结束
      • gets:不安全
      • fgets:安全,空格也会接受
    • 流:

      • 标准输入流 —> 控制台,指定的流:stdin
      • 文件流 —> 磁盘上的文件
    • 注意:输出完成后需要使用fclose(文件指针)关闭文件。

string.h文件下的函数:

  1. strlen( )函数

    • 格式:int len = strlen(字符数组名);
    • 作用:得到存储在字符数组中的字符串数据的长度
  2. strcmp()函数

    • cmp —> compare 比较
    • 格式:int res = strcmp(字符数组1,字符数组2);
    • 作用:用来比较两个字符串的大小
    • 返回值:

      • 如果返回的是负数,说明字符串1比字符串2小
      • 如果返回的是证书,说明字符串1比字符串2大
      • 如果返回的是0,说明一样
    • 比较规则:比的是相同位置的字符的ASCII码的大小
  3. strcpy( )函数

    • cpy —> copy 拷贝
    • 格式:strcpy(字符数组1,字符数组2);
    • 作用:用来将字符数组2的字符串拷贝到字符数组1中
    • 可能的问题:存储字符串1的字符数组长度不够,无法存储字符串2,这个时候会报错
  4. strcat()函数

    • cat —> concat 连接
    • 格式:strcat(字符数组1,字符数组2);
    • 作用:把存储在字符数组2中的字符串数据连接到字符串1的后面
    • 可能的问题: 存储字符串1的数组剩余的长度不足以存储字符串2时,会报错

指针与字符串数据

可以使用字符指针来存储字符串数据

  char *name = “jack";

指针存储字符串与字符数组存储字符串的区别

  char name1[] = "jack";
  char *name2 = “jack”;
  1. 当他们作为局部变量时:

    • name1字符数组是申请在栈区域,字符串的每一个字符存储在这个字符数组的每一个元素中
    • name2指针变量是申请在栈区域,字符串数据是以字符数组的形式存储在常量区中,name2指针变量中存储的是字符串在常量区中的地址
  2. 当他们作为全局变量时:

    • name1字符数组是申请在常量区,字符串的每1个字符是存储在这个数组的每一个元素中
    • name2指针变量是申请在常量区,字符串数据也是以字符数组的形式存储在常量区中,name2指针变量中存储的是字符串在常量区中的地址
  3. 区别:

    • 区别1:

      • 以字符数组存储时:无论如何都是1个字符数组,并且字符串的每一个字符存储在数组的元素中
      • 以字符指针存储时:无论如何首先都有1个指针变量,字符串数据是以字符数组的形式存储在常量区
    • 区别2:可变与不变性

      • 以字符数组存储的字符串,无论全局还是局部变量,都可以使用下标修改字符数组元素的值
      • 以字符指针的形式存储字符串数据,无论全局还是局部变量,都字符指针说指向的字符数组的值是不可修改的

字符串的恒定性

  1. 大前提:以字符指针形式存储的字符串
  2. 字符指针形式存储的字符串,其字符串数据必定是存储在常量区中,且不可更改
  3. 当我们以字符指针的形式要将字符串数据存储到常量区的时候,并不是直接将字符串存储到常量区中,而是先检查常量区中是否已经存在相同内容的字符串,如果存在则直接将字符指针指向现有的字符串地址,如果不存在才会将这个字符串数据存储到常量区中。
  4. 注意:当重新为字符指针初始化1个字符串时,并不是修改原有字符串,而是重新创建了一个字符串,并将指针指向新字符串,所以这种情况并不违背字符串的恒定性

以字符指针形式存储字符串的优势

  1. 以字符数组存储的字符串数据,长度固定,一旦创建后最多就只能存储这么长的支付数据了;而以字符指针的形式存储的字符串长度不限
  2. 修改显示的值,字符指针比字符数组更便捷

字符串数组

  char *names[] = {“jack”,”rose”};

使用指针数组存储字符串数组的优势

每1个字符串的长度没有限制,而二维数组存储字符串数组时,每1个字符串的长度不能超过二维数组的列数-1

字符串数组的排序

将字符串数组当中的字符串以字母的形式排序

  1. 冒泡排序
  2. 使用strcmp( )函数比较两个字符串的大小
正文结束
本文作者:

文章标题:C语言学习笔记(二)

本文地址:https://www.yanjiayu.cn/notes/notes-on-c-language-learning-2.html

版权说明:本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。您可以自由的转载和修改,但请务必注明文章来源并且不可用于商业目的。
知识共享许可协议
最后修改:2020 年 11 月 25 日 05 : 44 PM
如果觉得我的文章对你有用,请随意赞赏