格式控制符总结
字符数据在内存中是如何存储的?
char ch = 'a';
存储的是97的二进制的补码
char ch = 97;
直接为 char 变量赋值一个整数是可以的.
char 变量 有没有正负符号?
有符号的.最高位表示符号位
printf("%d",ch);
也可以使用%d 输出 char 变量的值,本来存储的就是整数.
格式控制符的作用
- 不同类型的数据在变量中存储的形式是不一样的
- 读数据的时候,类型不同,读取的方式也不一样的.
int num = 100; printf("%f\n",num); 输出0.00000 以 float 形式读取,前面两个字节是0. 为了保证可以正确的读取存储在变量中的数据,就要使用正确的格式控制符.
%c 的读取方式,从给定的变量的地址开始,读取1个字节.然后将这个字节的整数读出来,还原成 ASCII 码对应的字符.
- %d 从给定的变量的地址开始,连读4个字节.
- 为了保证可以正确的读取存储在变量中的数据,就要使用正确的格式控制符.怎么存怎么读.
格式控制符的总结
int 整形
%d .读取 int *****
整形数据,以十进制的星海输出
%o . 以八进制形式输出
%x. 以十六进制的形式输出
%hd . short
%ld . long
%lld. long long
%u . unsigned int
%hu unsigned short
%lu unsigned long *****
%llu unsigned long long
实型:
float: %f
double: %lf
字符型
char %c
地址
%p
垃圾值的由来
声明一个变量,如果没有初始化,就会有一个垃圾值
- 大括弧结束后,变量是如何回收的
- 并不是把以前字节删除.是告诉系统,变量不再使用 ,可以分配给别的变量了.
- 里面的数据不会清空.
- 再声明一个变量,就可能把之前的字节分配给下一个字节.
- 那么这个新的变量的垃圾值就是之前的变量的值
- 全局变量声明系统会自动清零
数组
数组的概述
数组的作用
- 是存储多个数据,并且多个数据之前和谐的共处.
- 普通变量只能存1个数据
- 而数组可以存多个数据,新数据进去,旧数据还在
数组的特点.
- 可以存储多个数据
- 1个数组中只能存储类型相同的多个数据,在我们创建的时候指定.
- 存储的个数是固定的,在我们创建数组的时候指定的.
- 存储在数组中的数据太方便管理了.
如何声明1个数组
- 确定存储的这组数据的类型
- 这个数据最多可以存多少数据
声明数组的语法
数据类型 数组名称[最多存多少个数据];
int arr[5];//array
创建了一个最多可以存储5个 int 类型的数组
数组在内存中是如何创建的—>形象理解版本
int arr[3];
1. 内存中声明1个数组变量 arr
2. 平均的划分成 3 个等份
3. 每份都是 int 类型.
4. 真正存储数据的是数组里面的3个小空间.
几个专业术语
- 元素: int arr[3],就有三个元素.
- 下标(索引): 为了区分每一个元素,给其编号,从0开始依次递增.
- 长度: 指的是数组当中元素的个数.N 个元素,下标最多到 N - 1.
如何往数组当中存储数据
数组当中存储数据的是元素而不是整个数组.
数组名[元素的下标] = 数据;
arr[0] = 100;
将100赋值给 arr 数组中下标位0的那个元素
需要注意的几个问题
- 数组的元素本质就是一个普通类型的量
- 数组中的数据和谐共处是多个元素和谐共处
- 当为同一个下标变量赋值,新的值会干掉旧值
为元素赋值的时候要注意的几点
- 赋值的时候类型要相同
- 下标不要越界,其实可以赋值,只是赋值给数组屁股后面的空间了.会出现危险
- 下边的范围0~长度-1.
取出数组中的数据
- 我们要取的是数组当中元素的数据.
如果要取出元素的值,就要确定取出哪一个元素的值
printf("%d\n",arr[1]); 就可以取出数组当中下标为1的元素的值 下标越界取值就是别人的值了.
遍历数组
要将数组中的每一个元素的值打印出来,就遍历0—>数组长度-1 范围之间的整数 作为下标
for(int i = 0; i<3;i++)
{
printf("%d\n",arr[i]);
}
数组的长度
关于数组的长度:
- 在声明数组的时候,必须要声明数组的长度
- 长度可以是变量,常量,表达式,’字符’(长度就是其 ASCII 码.
- 不能是一个小数.也不能是负数
- 可以是1,也可以是0,不过侬脑子秀逗啦?
- 也可以是宏!宏值必须是整数
#define LEN 10
int main()
{
int arr[LEN];
}
关于数组元素的默认值
默认值没有初始化就是垃圾值,初始化了其中一个,其他的就是0
关于数组元素的初始化
1. 最傻的方式
int arr[3];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
2. 在声明的同时就初始化
int arr[3] = {10,20,30};
- 使用这种方式初始化数组的长度就不能使用变量了,为什么?
int arr[3] = {10,20,30};
编译器编译的时候还是会把他们还原成最根本的方式.
int arr[3];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
如果这个时候用变量?
int arr[len] = {10,20,30};
1.在编译器编译的时候不知道数组的长度.
2.变量的值要在执行起来赋值以后才知道.
3.为了保证一定不会越界,出错,就不允许使用变量来表达长度.
4.使用宏是可以的,宏是预处理指令,编译的时候就已经确定了.
数组的长度超过定义的长度
int arr[5] = {21,21,23,23,21,321,32,32,32,321,24,41,321,42}
- 这个时候取数一下这个长度显然不现实,就直接省略长度
- 省略后长度就是由大括弧中的数据来决定的,有多少数据就有多长.
- 如果指定了5个长度,5个元素后面的数据会直接丢弃!
部分初始化
int arr[3] = {10};
1.只为数组的前面的元素赋值,数组的其他的元素就初始化为0;
2.如果要将数组中所有的元素初始化为0,就把第一个元素初始化为0;
3.一定要使用大括号的方式才行指定下标的初始化
int arr[3] = {[1] = 10,[2] = 20};
其他的元素的值就自动的初始化为0;
数组在内存中的存储形式
int arr[3];
- 声明一个数组,在内存中是从高地址向低地址连续的申请(数组长度*元素字节数)长度的空间.
- 下标为0的元素在低字节地址.
- 元素的值还是按照之前那样,存储的是数据的二进制补码.
- 数组的地址是数组中下标为0的地址
- 数组明就代表数组的地址
C 语言的数组名中存储的就是数组的地址
数组的地址 == 数组名 == 数组中的低字节的地址 == 数组中下标为0的元素的地址 == 数组中下标为0的元素的的低字节的地址 printf(%p\n",arr); printf(%p\n",arr[0]);
数组的长度的计算
sizeof(arr);
- 使用 sizeof 运算符,得到数组总共占用的字节数
int len = sizeof(arr);
- 得到占用的总的字节数,除以每一个元素占用的字节,就可以得到长度
int len = sizeof(arr)/sizeof(arr[0]);
//每个系统或者编译器不同的话,同一个类型的变量占用的字节数也可能是不一样的,所以不要把元素的大小写死.
- 数组的每一个元素的类型相同,所以数组的每个元素占用的空间也相同
什么时候使用数组
多个类型相同的数据,并且数据的意义相同的时候,才使用数组来存储.
数组的算法
找出一个整形数组的最大值.
int arr[] = {32,2,32,321,42,15,36,54,765,865,65,35,427,46}
int max = INT32_MIN,min = INT32_MAX;
for(int i = 0;i < sizeof(arr)/sizeof(arr[0]);i++)
//sizeof 表达式会造成性能困扰,每次都要计算,可以换成一个变量保存在里面.
{
if(arr[i] > max)
{
max = arr[i];
}
if(arr[i] < min)
{
min = arr[i];
}
}- 求数组的平均值和累加和.(遍历每一个元素加到变量中保存.)
判断1个数组中是否包含指定的元素(遍历每一个元素进行比较.全部都不相等就是没有)旗帜变量
int flag = 0;
for(int i = 0;i
数组与函数
- 为函数传参的过程就是一个赋值的过程,将实参赋给形参
- 当参数的类型是 int,double,float,char, 类型,函数执行完毕之后对实参变量的值没有影响.
- 像这样的传递我们叫做值传递.
- 或者上面这些类型的数组的某一个元素的时候
数组作为函数的参数
声明
int arr[3] = {1,2,3};
void test(int arr[]);
//声明一个无返回值函数参数是一个int 类型数组
调用
test(arr);
//传递一个类型相同的数组
数组作为函数的参数的时候,在函数内部计算数组的长度,永远都是8个字节.
- 如果函数的参数是一个数组,在声明这个参数的时候,并不会创建一个数组.而是去声明一个储存数组地址的指针变量.
- 这个指针变量就是8个字节,所以计算永远是8.
- 所以在传递数组给函数的时候还要传递一个数组的真实长度.
- 因为数组作为函数的参数时是一个指针变量,所以才不用写长度.
当函数的参数是数组的时候传递的是实参数组的地址
- 形参数组指向的地址和实参数组的地址重合了,都是指向的同一段内存地址
- 当形参数组去更改实参数组下标范围内的值的时候,内存中存储的数据就发生了改变
- 当函数结束,实参数组再去调用内存数据的时候,就已经被改变过了.
随机数
- 随机的产生1个双色球号码
1~33 随机的产生6个不重复的数
1~16 产生1个随机数
如何产生不重复的随机数?
将之前产生的随机数存储起来,没产生一个就判断和之前的有没有重复,没有就下一个.
//使用一个数组存储之前已经产生的随机数
int arr[6] = {0};
for(int i = 0;i < 6; )
{
int nun = arc4random_uniform(33)+1;
//函数的参数1数组的地址,参数2长度,参数3要对比的数.返回值0代表没有重复,1代表重复.
int res = isContain(arr,6,num);
if(res == 0)
{
arr[i] = num;
i++;
//巧妙的将i++更换到没有重复的数的时候才执行.
}
}
选择排序
- 有 N 个数,把第i个到最后一个一个一个的比,若比第一个数大,就把其换到第i个位置.
- 第i轮结束可以找到最高的人.
- 第i+1轮开始从第i+1个人开始和后面的人比
- j 就是跟 i 去比较的人
有 N 个人就比 N - 1 轮
int arr[N];
//有 N 个数,把第i个到最后一个一个一个的比
for(int i = 0; i < N-1;i++)
//第i轮结束可以找到最高的人.有 N 个人就比 N - 1 轮
{
//j 就是跟 i 去比较的人
for(int j = i+1 ;j < N ;j++)
{
//若 j 比第i个数大,就把其换到第i个位置.
if(arr[j] > arr[i])
{
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
//第i轮结束可以找到最高的人.
//剩下的最后一个人已经是最小的了,就不用比了.
}
冒泡排序
int arr[] = {1,2,3,4,5,6,7,8,9}
int len = sizeof(arr)/sizeof(arr[0]);
for(int i = 0;i < len-1;i++)//通常外层循环都是控制轮数
{
for(int j = 0;j < len -1 -i;j++)//每轮比较数组长度-1-已经比较过几个数
{
if(arr[j] < arr[j+1]) //j 每次都和 j 的下一个数比较
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
二分查找法
找到一个数据如果挨个挨个找,效率就会低下.所以我们就折半查找.
大前提,是个有序的数组
1. 找中间那个数,判断大了还是小了
2. 如果大了说明再左边,小了就在右边,如果等于那还说个屁
3. 最左边的下标是0,右边的是 len -1;
4. mid 是 max/2
5. 如果在左边,max = mid -1,右边 mid +1;
int len =sizeof(arr)/sizeof(arr[0]);
int max = len -1;
int min = 0;
int mid = len/2;
int key = 1321;
while(key != arr[mid])
{
if(arr[mid] > key)
{
min = mid + 1;
}
else if(arr[mid] < key)
{
max = mid - 1;
}
mid = (min + max)/2;
//如果 key 不在数组内会造成死循环,可以加一个判断来结束循环
}
printf("%d\n",mid);