进制
进制是一种计数方式 ,数值的表现形式
常见的进制 : 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进行&操作,得到这个数的最低位
- 按位与运算通常用来对某些位清0或保留某些位。例如把a的高位都清0,保留低八位,那么就a&255
|按位或
特点:是要对应的两位其中一位是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类型的数组来存储
- 一个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
注意: 如果数组作为函数的形参, 元素的个数可以省略
如果形参是数组, 那么在函数中修改形参的值, 会影响到实参的值
折半查找
基本思路
在有序表中,取中间元素作为比较对象,若给定值与中间元素的要查找的数相等,则查找成功;
若给定值小于中间元素的要查找的数,则在中间元素的左半区继续查找;若给定值大于中间元素的要查找的数,则在中间元素的右半区继续查找。不断重复上述查找过 程,直到查找成功,或所查找的区域无数据元素,查找失败。
实现步骤
- 在有序表中,取中间元素作为比较对象,若给定值与中间元素的要查找的数相等,则查找成功;
- 若给定值小于中间元素的要查找的数,则在中间元素的左半区继续查找;
- 若给定值大于中间元素的要查找的数,则在中间元素的右半区继续查找。不断重复上述查找过 程,直到查找成功,或所查找的区域无数据元素,查找失败