第六天学习笔记

时间:2022-11-10 23:09:04

进制

进制是一种计数方式 ,数值的表现形式

常见的进制 : 10进制,2进制 ,8进制 、16进制

进制数字进位方法

  • 10进制 0、1、2、3、4、5、6、7、8、9。逢10进1

  • 2进制 0 、1。逢2进1

    • 书写形式要以0b或者0B开头 ,如 0b101
  • 8进制0、1、2、3、4、5、6、7。逢8进1

    • 书写形式:在前面加0 ,比如045
  • 16进制 0、1、2、3、4、5、6、7、8、9、A 、B 、C 、D 、E 、F 。逢 16 进 1

    • 16进制就是逢16进一,但是数字字只有0~9这十个 ,所以用A ,B ,C ,D ,E ,F来表示10~15这五个数。字母不区分大小写。
    • 书写形式:在前面加个0x或者0X,如0x45

编程打印各种进制

  • 假设有整形变量a,初始值为13,打印其8进制,16进制
int a = 13
printf("10->8:%o\n",a);
printf("10->16:%x\n",a);
  • 定义一个2进制数、8进制数、16进制数,打印其对应的10进制
int a = 0b00000000000000000000000000001101
printf("2->10:%d\n",a);

a = 015;
printf("8->10:%d\n",a);

a = 0xd;
printf("16->10:%d\n",a);

输出结果:
2->10:13
8->10:13
16->10:13
#include<stdio.h>
int main()
{
    //   默认就是10进制
    int number = 12;
    //   在前面加0就代表8进制
    int number1 = 014;
    //   %d是以10进制的方式输出一个整数
    printf("%d\n",number1);
    //  %o是以8进制的方式输出一个整数
    printf("%o",number);
    //  在前面加上0b就代表2进制
    int number2 = 0b1100
    printf("%d\n",number);
    //  在前面加上0x就代表16进制
    int number3 = 0xc
    printf("%d\n",number3);
    //  %x是以16进制的方式输出一个整数
    printf("%x\n",number);

    return 0;
}

进制转换

  • 10进制转2进制

    • 除2取余,余数倒序得到的序列就是2进制的表示形式
  • 2进制转10进制

    • 每一位进制位的值*2的幂数(幂数从0开始),将所有位求出的值相加
  • 2进制转8进制

    • 三个2进制位代表一个8进制位,因为三个2进制位的最大值是7,而8进制是逢8进1
  • 8进制转2进制

    • 每一位8进制位的值*8,将所有求出的值相加
  • 2进制转16进制

    • 四个2进制位代表一个16进制位,因为四个2进制位的最大值是15,而16进制是逢16进1
  • 16进制转2进制

    • 将16进制的每一位拆成四位2进制位

原码、反码、补码的基本概念

12的二进制
10000000 00000000 00000000 00001100
正数的特点:(三码合一) 正数的原码就是TA的反码就是TA的补码

 -12
 二进制的最高位我们称之为符号位 
 如果符号位是0代表是一个正数,
 如果符号位是1代表是一个负数

 10000000  00000000 00000000 00001100 (-12的原码)
 11111111  11111111 11111111 11110011(反码, 符号位不变其它位取反)

 11111111  11111111 11111111 11110011
+00000000  00000000 00000000 00000001
 _____________________________________________
 11111111  11111111 11111111 11110100(补码 , 反码+1)

 结论:无论正数负数在内存中存储的都是补码



 11111111  11111111 11111111 11110101 (补码)
-00000000  00000000 00000000 00000001  (-1)
 _____________________________________________
 11111111  11111111 11111111 11110100 (反码)
 10000000  00000000 00000000 000010112在内存中存储的是它的补码
 0

位运算

位运算是指按二进制进行的运算。在系统软件中,常常需要处理二进制位的问题。 C语言提供了6个位操作运算符。这些运算符只能用于整型操作数,即只能用于带符号或无符号的 char,short,int与long类型。

& 按位与

特点:只有对应的两位都是1才返回1,否则返回0

口诀:一假则假

规律:任何数按位与上1结果还是那个数与0相&就为0

  • 应用场景:

    • 按位与运算通常用来对某些位清0或保留某些位。例如把a的高位都清0,保留低八位,那么就a&255
      判断奇偶: 将变量a与1做位与运算,若结果是1,则 a是奇数;若结果是0,则 a是偶数
      任何数和1进行&操作,得到这个数的最低位

|按位或

特点:是要对应的两位其中一位是1就返回1

口诀:一真则真

^按位异或

特点:对应的两个不相同返回1,相同返回0

  • 多个整数按位异或的结果和顺序无关
  • 相同整数按位异或结果是0
  • 任何数按位异或上0结果不变
  • 任何数按位异或身上另一个整数两次,结果还是那个数

~按位取反

特点:0变1,1变0

<<左移

a << n 把整数a的2进制位往左边移n位,移出的位去掉,低位补0

左移会把原有的数值变大

左移的应用场景:当要计算某个数乘以2的n次方的适合就用左移,效率最高

*左移有可能改变数值的正负性

>>右移

a >> n 把整数a的2进制位往右边移动n位,移出的位去掉,缺少的以最高位是0就补0,是1就补1(是在当前操作系统下)

右移的应用场景:当要计算某个数除以2的n次方的时候就用右移,效率最高


变量的存储细节

变量为什么要有类型?每种类型占用的空间不一样

一个变量所占用的存储空间,不仅跟变量类型有关,而且还跟编译器环境有关系。同一种类型的变量,在不同编译器环境下所占用的存储空间又是不一样的

只要定义变量系统就会开辟存储空间给变量储存数据,内存寻址从大到小,越先定义的变量内存地址越大,变量地址就是占用存储空间最小的字节地址

由于内存寻址是从大到小,所以储存数据也是从大到小的储存(先储存2进制的高位,再储存低位)

  • %p:输出地址
  • &变量名称:取出变量地址

字符6和数字6就是完全不相同的两个数

char c1 = 6; // 00000110
char c2 = '6';// 00110110

char类型在某些情况下可以当做整型来用

如果对内存要求特别严格, 而且需要存储的整数不超过char类型的取值范围, 那么就可以使用char类型来代替int类型

// -2(7)~2(7)-1   == -128 ~ 127
char c = 129; // 1000 0000
printf("%i\n", c);

char型使用注意事项

当把一个字符赋值给一个char类型变量,那么系统首先查这个字符所对应的ASCII码,然后把这个ASCII值放到变量中

char c = 'a';
printf("%d\n",c);
输出结果: 97

根据变量中存储的ASCII值,去查ASCII表中对应字符,然后把这个字符打印控制台上,整形和 字符型可以互相转换。
int c = 97;
printf("%c\n",c);
输出结果:a

字符型变量不能用来存储汉字

char c = '我'; char字节,一个中文字符占3字节(unicode表),所有char不可以存储中文
不支持多个字符,多个字符是字符串

char c = 'ac'; // 错误写法
printf("%c\n",c);

类型说明符

类型说明符基本概念

  • C语言提供了以下4种说明符,4个都属于关键字:
    • short 短型 等价于 short int
    • long 长型 等价于 long int
    • signed 有符号型
    • unsigned 无符号型
  • 这些说明符一般就是用来修饰int类型的,所以在使用时可以省略int

short和long

  • short和long可以提供不同长度的整型数,也就是可以改变整型数的取值范围。

    • 在64bit编译器环境下,int占用4个字节(32bit),取值范围是-2^31~2^31-1;
    • short占用2个字节(16bit),取值范围是-2^15~2^15-1;
    • long占用8个字节(64bit),取值范围是-2^63~2^63-1
  • 在64位编译器环境下:

    • short占2个字节(16位)
    • int占4个字节(32位)
    • long占8个字节(64位)。
    • 因此,如果使用的整数不是很大的话,可以使用short代替int,这样的话,更节省内存开销。
  • 不同编译器环境下,int、short、long的取值范围和占用的长度又是不一样的。比如在16bit编译器环境下,long只占用4个字节。ANSI \ ISO制定了以下规则:

    • short跟int至少为16位(2字节)
    • long至少为32位(4字节)
    • short的长度不能大于int,int的长度不能大于long
    • char一定为为8位(1字节),毕竟char是我们编程能用的最小数据类型
  • 可以连续使用2个long,也就是long long。一般来说,long long的范围是不小于long的,比如在32bit编译器环境下,long long占用8个字节,long占用4个字节。不过在64bit编译器环境下,long long跟long是一样的,都占用8个字节。

    • long long int等价于long long

signed和unsigned

首先要明确的:signed int等价于signed,unsigned int等价于unsigned

signed和unsigned的区别就是它们的最高位是否要当做符号位,并不会像short和long那样改变数据的长度,即所占的字节数。

signed:表示有符号,也就是说最高位要当做符号位,所以包括正数、负数和0。默认情况下所有变量都是有符号的(signed)(其实int的最高位本来就是符号位,已经包括了正负数和0了),因此signed和int是一样的,signed等价于signed int,也等价于int。signed的取值范围是-2^31 ~ 2^31 - 1
unsigned:表示无符号,也就是说最高位并不当做符号位,所 以不包括负数。如果给变量加上修饰符unsigned, 就代表”不”把二进制的最高位作为符号位

在64bit编译器环境下面,int占用4个字节(32bit),因此unsigned的取值范围是:0000 0000 0000 0000 0000 0000 0000 0000 ~ 1111 1111 1111 1111 1111 1111 1111 1111,也就是0 ~ 2^32 - 1

  • 如果想打印无符号的变量, 只能用%u
  unsigned int num1 = -12;
    printf("num1 = %u", num1);

不同类型的说明符可以混合使用,相同类型的说明符不能同时在一起使用


数组的基本概念

  • 数组,从字面上看,就是一组数据的意思,没错,数组就是用来存储一组数据的
    数组的定义格式:
    1.数据类型 变量名称;
    2.数据类型 数组名称[数据的个数];
    3.元素类型 数组名称[元素个数];

    元素类型: 就是数组中需要存储的数据类型, 一旦指定, 数组中就只能存储该类型的数据
    元素个数: 就是数组中能够存储的数据(元素)的个数
    只要定义一个C语言的数组, 系统就自动会给数组中的每一块小得存储空间一个编号
    这个编号从0开始, 一次递增
    数组中系统自动绑定的编号, 我们称之为 索引

  • 在C语言中,数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。

    • 注意:只能存放一种类型的数据
  • 数组的几个名词

    • 数组:一组具有相同数据类型的数据的有序的集合
    • 数组元素:构成数组的数据。数组中的每一个数组元素具有相同的名称,不同的下标,可以作 为单个变量使用,所以也称为下标变量。
    • 数组的下标:是数组元素的位置的一个索引或指示。(从0开始)
    • 数组的维数:数组元素下标的个数。根据数组的维数可以将数组分为一维、二维、三维、多维 数组。
  • 数组的应用场景

    • 一个int类型的变量能保存一个人的年龄,如果想保存整个班的年龄呢?
      • 第一种方法是定义很多个int类型的变量来存储
      • 第二种方法是只需要定义一个int类型的数组来存储

数组的分类

  • 按存储的内容分类
    • 数值数组:用来存储数值得
    • 字符数组:用来存储字符 ‘a’
    • 指针数组:用来存放指针(地址)的
    • 结构数组:用来存放一个结构体类型的数据

数组的初始化

先定义再初始化

int scores[5];
scores[0] = 99;
scores[1] = 88;
scores[2] = 77;
scores[3] = 66;
scores[4] = 100;

依次将{}中的每一个值赋值给数组中的每一个元素
并且从0开始赋值
也称之为数组的初始化(完全初始化)

int scores[5] = {99,88,77,66,100};

部分初始化
默认从0开始初始化, 依次赋值
注意: 如果”在部分初始化中”对应的内存没有被初始化, 那么默认是0

int scores1[3] = {11, 22};
printf("0 = %i\n", scores1[0]);
printf("1 = %i\n", scores1[1]);
printf("2 = %i\n", scores1[2]);

printf("-------\n");

注意: 如果没有对数组进行初始化(完全和部分), 那么不要随便使用数组中的数据, 可能是一段垃圾数据(随机值)

int scores2[3];
printf("0 = %i\n", scores2[0]);
printf("1 = %i\n", scores2[1]);
printf("2 = %i\n", scores2[2]);

printf("-------\n");

注意: 定义数组的时候, 数组的元素个数不能使用变量, 如果使用变量, 那么数组中是一些随机值

int num = 10;
int scores3[num];
printf("0 = %i\n", scores3[0]);
printf("1 = %i\n", scores3[1]);
printf("2 = %i\n", scores3[2]);

注意: 不建议使用变量定义数组, 如果使用了变量定义数组, 作为数组的元素个数, 不初始化的情况下是随机值, 如果初始化会直接报错
int num2 = 10;
int scores4[num2] = {11, 12};
printf(“——-\n”);

注意: 如果定义的同时进行初始化, 那么元素的个数可以省略
省略之后, 初始化赋值几个数据, 那么数组的长度就是几. 也就是说数组将来就能存储几个数据

int scores5[] = {1, 3};
printf("0 = %i\n", scores5[0]);
printf("1 = %i\n", scores5[1]);
printf("-------\n");

注意; 如果定义数组时没有进行初始化, 那么不能省略元素个数

 int scores6[];
 0 1 2 3 4
 int socres7[101] = {0, 0, 0, 1, 3};
 int socres7[101];
 socres7[99] = 1;
 socres7[100] = 3;

可以通过[索引] = 的方式, 给指定索引的元素赋值

int socres7[101] = {[99] = 1, [100] = 3};
printf("3 = %i\n", socres7[99]);
printf("4 = %i\n", socres7[100]);

注意: 只能在定义的同时利用{}进行初始化, 如果是先定义那么就不能使用{}进行初始化
如果先定义那么就不能再进行整体赋值, 只能单个赋值

数组的遍历

取出数组中所有元素的值, 称之为遍历
注意: 在遍历数组的时候, 尽量不要把遍历的次数写死
遍历多少次应该由数组来决定, 也就是说遍历多少次应该通过数组计算得出

include <stdio.h>

int main(int argc, const char * argv[]) 
{
   int scores[6] = {1, 23, 44, 66, 71, 88, 99 , 2};
    // 动态计算数组的元素个数
    int length = sizeof(scores) / sizeof(scores[0]);
    for (int i = 0; i < length; i++) 
    {
        printf("scores[%i] = %i\n", i,scores[i]);
    }
    return 0;
}

数组内部存储细节

  • 存储方式:

    • 1)计算机会给数组分配一块连续的存储空间
    • 2)数组名代表数组的首地址,从首地址位置,依次存入数组的第1个、第2个….、第n个元素
    • 3)每个元素占用相同的字节数(取决于数组类型)
    • 4)并且数组中元素之间的地址是连续。
  • 示例

模拟该数组的内存存储细节如下: int x[2]={1,2};
int ca[5]={'a','A','B','C','D'};

数组的地址

  • 在内存中,内存从大到小进行寻址,为数组分配了存储空间后,数组的元素自然的从上往下排列 存储,整个数组的地址为首元素的地址。

数组的越界问题

  • 数组越界导致的问题
    • 约错对象
    • 程序崩溃
    char cs1[2] = {1, 2};
    char cs2[3] = {3, 4, 5};
    cs2[3] = 88; // 注意:这句访问到了不属于cs1的内存
    printf("cs1[0] = %d\n", cs1[0] );
输出结果: 88

数组元素作为函数参数

  • 数组可以作为函数的参数使用,进行数据传送。数组用作函数参数有两种形式:
    • 一种是把数组元素(下标变量)作为实参使用
    • 一种是把数组名作为函数的形参和实参使用

数组元素作为函数参数

  • 数组元素就是下标变量,它与普通变量并无区别。 因此它作为函数实参使用与普通变量是完全相
    同的,在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送。

  • 数组的元素作为函数实参,与同类型的简单变量作为实参一样,是单向的值传递,即数组元素的值传给形参,形参的改变不影响实参

void change(int val)// int val = number
{
    val = 55;
}
int main(int argc, const char * argv[])
{
    int ages[3] = {1, 5, 8};
    printf("ages[0] = %d", ages[0]);// 1
    change(ages[0]);
    printf("ages[0] = %d", ages[0]);// 1
}
  • 用数组元素作函数参数不要求形参也必须是数组元素

数组名作为函数参数

  • 在C语言中,数组名除作为变量的标识符之外,数组名还代表了该数组在内存中的起始地址, 因此,当数组名作函数参数时,实参与形参之间不是”值传递”,而是”地址传递”,实参数组名将 该数组的起始地址传递给形参数组,两个数组共享一段内存单元,编译系统不再为形参数组分配 存储单元。

  • 数组的名字作为函数实参,传递的是整个数组,即形参数组和实参数组完全等同,是存放在同一存储空间的同一个数组。这样形参数组修改时,实参数组也同时被修改了。形参数组的元素个数可以省略

void change2(int array[3])// int array = 0ffd1
{
    array[0] = 88;
}
int main(int argc, const char * argv[])
{
    int ages[3] = {1, 5, 8};
    printf("ages[0] = %d", ages[0]);// 1
    change(ages[0]);
    printf("ages[0] = %d", ages[0]);// 88
}

数组名作函数参数的注意点

  • 在函数形参表中,允许不给出形参数组的长度
void change2(int array[])
{
    array[0] = 88;
}
  • 形参数组和实参数组的类型必须一致,否则将引起错误。
void prtArray(double array[3]) // 错误写法
{
    for (int i = 0; i < 3; i++) {
        printf("array[%d], %f", i, array[i]);
    }
}
int main(int argc, const char * argv[])
{
    int ages[3] = {1, 5, 8};
    prtArray(ages[0]);
}
  • 当数组名作为函数参数时, 因为自动转换为了指针类型,所以在函数中无法动态计算除数组的元素个数
void printArray(int array[])
{
    printf("printArray size = %lu\n", sizeof(array)); // 8
    int length = sizeof(array)/ sizeof(int); // 2
    printf("length = %d", length);
}

注意: 数组名作为函数的参数传递, 是传递的数组的地址

因为数组名就是数组的地址 &number = &number[0] == number

注意: 如果数组作为函数的形参, 元素的个数可以省略

如果形参是数组, 那么在函数中修改形参的值, 会影响到实参的值


折半查找

基本思路

  • 在有序表中,取中间元素作为比较对象,若给定值与中间元素的要查找的数相等,则查找成功;
    若给定值小于中间元素的要查找的数,则在中间元素的左半区继续查找;

  • 若给定值大于中间元素的要查找的数,则在中间元素的右半区继续查找。不断重复上述查找过 程,直到查找成功,或所查找的区域无数据元素,查找失败。

实现步骤


  • 在有序表中,取中间元素作为比较对象,若给定值与中间元素的要查找的数相等,则查找成功;
  • 若给定值小于中间元素的要查找的数,则在中间元素的左半区继续查找;
  • 若给定值大于中间元素的要查找的数,则在中间元素的右半区继续查找。不断重复上述查找过 程,直到查找成功,或所查找的区域无数据元素,查找失败