P199 - 格式控制符的总结
1.printf函数中的 占位符 / 格式控制符
格式控制符的作用:
1.不同类型的数据在变量中存储的形式是不一样的,所以在读取变量中的数据的时候,类型不同,读取的方式也不同。
为了保证可以正确读取出存储在变量中的数据,我们应该使用正确的格式控制符。
%c: 从给定变量的地址开始,只读一个字节,然后把这个字节读出来,以其还原成ascii字符。
%d: 从给定变量的地址开始,连读4个字节
%f: 从给定变量的地址开始,连读4个字节
变量中的数据如何存储的?那么就应该如何读取,这样才可以拿到正确的数据。
格式控制符的总结:
int 整形:
%d 读取int类型的数据,以十进制的形式输出 *****
%o 读取int类型的数据,以八进制的形式输出
%x 读取int类型的数据,以十六进制的形式输出
%hd short
%ld long
%lld long long
%u unsigned int
%hu unsigned short
%lu unsigned long *****
%llu unsigned long long
实型:
float %f
double %lf (%.2lf)
字符型:
char %c
地址型: %p
P200 - 垃圾值得由来
1.变量的回收。
在大括号执行完毕之后,定义在这个大括号中的变量就会被系统的立刻回收。
声明变量的时候其实是这样的,找系统为你从高地址向低地址分配指定字节数的连续空间。
如何回收?
当变量回收的时候,其实就是告诉系统,变量不再使用了,可以分配给别的变量了。变量所占用的字节的数据不会清空
当再声明1个变量的时候,这个新变量有可能就是刚刚被回收的那个变量占用的空间。
那么这个时候,这个新变量中是有值的。值就是上次那个变量所遗留下来的数值。这就叫做垃圾值。
所以,我们声明1个局部变量,最好先为其初始化为0;
2.全局变量
当将全局变量声明出来以后,系统会自动的将全局变量中的数据清零。
P201 - 数组的概念
1. 数组:数据的组合。
2. 特点:
a. 可以存储多个数据。
b. 一个数组中只能存储类型相同的多个数据。创建数组的时候就指定。
c. 数据中可以存储的数据的个数是固定的。创建数组的时候指定。
d. 存储在数组中的数据方便管理。
P202 - 如何声明数组
1.创建数组之前首先要确定两点
a.确定要存储的这多个数据的类型
b.这个数组最多可以存储多少个数据
2.声明数组的语法
存储的多个数据的类型 数组名称[这个数组最多可以存储多少个数据]
int arr[5] // array 数组
代表创建1个数组,这个数组的名字叫做arr,这个数组最多可以存储5个数据。每一个数据的类型必须是int类型的。
3.几个相关术语:
1.元素:数组里的每一个成员,就叫做数组的元素
2.下标/索引:为了区分每一个元素,C系统给每个元素编了一个号码
这个号码从0开始,依次递增。这个号码就是数组的下标/索引。
3.长度:数组当中元素的个数。也就是说这个数组能存储多少个数据。
P203 - 如何往数组中存储数据
1.数组中存储数据的是元素,而不是数组。
数组名代表的是整个数组,所以不能直接给数组赋值。
2.语法:
数组名[元素的下标] = 数据 ;
arr[1] = 100 ;
将100赋值给arr数组中下标位1的那个元素。
P205 - 为元素赋值注意两点
3.注意:
1.微元素赋值的时候,赋值的数据类型要和元素的类型一致。
当赋值的数据的类型跟元素的类型不一致的时候,会做自动类型转换。
2.下标不能越界。
数组的下标的范围:0->数组长度-1
P206 - 如何取出存储在数组中的数据
4.取出在数组中的元素的数据:
数组名[下标]
P207 - 数组的遍历
for(int j = 0 ; j < 数组的长度 ; j ++)
{
arr[j];
}
P208 - 关于数组的长度
1.在声明数组的时候必须要声明数组的长度
2.数组的长度可以是常量、变量、表达式(长度就是表达式的结果)、字符(ascii码)。
3.数组的长度不能是小数,也不能是负数。
4.数组的长度可以是1.也可以是0。(但是没有卵用)
5.数组的长度也可以是一个宏,宏值必须是整数。
P209 - 数组的元素和默认值和初始化
1.当我们声明了数组没为元素赋值,里面是有垃圾值的。
2.数组的初始化。
1.最傻的方式,用下标挨个赋值。
2.在声明数组的时候就初始化数组的元素。(使用这种方式初始化,长度不能用变量)
int arr[3] ={10,20,30};
3.在使用第二种方式的时候,可以省略数组的长度。(长度由大括号的数据来决定)
int arry[]={1,2,3,4,5,6,7,8,9,10};
4.第四种初始化方式,只为数组前面的元素赋值。
int arr[5]={1};
这时候数组的第0个元素的值是1,其他的元素的值自动初始化为0.
所以我们要将一个数组中所有的元素初始化为0,就int arr[5]={0};
5.第五种初始化方式,指定下标的初始化。其他的元素的值就自动初始化为0了。
P212 - 数组在内存中的存储形式
1.变量在内存中的存储方式.
a.不同类型的变量在内存中占据不同的字节数.1个变量占用的字节一定是连续的.
int 4
double 8
float 4
char 1
b.在为变量分配字节空间的时候,是从高地址向低地址分配的连续空间.
先声明的变量是在高字节,后声明的变量是在低字节。
c.任何数据在内存中都是以其二进制的补码存储的.低位存储在低字节,高位存储在高字节。
d.每1个字节在内存中都有1个地址。十六进制数。变量的地址:是组成这个变量的低字节的地址。
e.使用&取地址运算符可以直接拿到变量的地址。
f.使用%p打印地址类型的数据。
2.数组在内存中是如何存储的呢?
int arr[3];
a.声明1个数组,在内存中从高字节向低字节申请连续的(数组的长度*每1个元素的字节数)个字节的空间。
b.下标为0的元素在低字节。
c.元素的值还是按照之前那样 存储的是 数据的二进制的补码。
d.数组的元素的本质就是1个普通类型的变量。1个数组就是由多个普通类型的变量联合而成的。
P213 - 数组的地址问题
1. 数组地址是数组当中字节最低的地址
2. 数组地址是数组中下标为0的元素的地址。
3. 数组地址是数组中下标为0的元素的低字节地址。
4. 重点:
数组名就代表数组的地址。
C语言的数组不代表整个数组,代表这个数组的地址。
C语言的数组名中存储的就是数组的地址。
我们不能直接打印数组名,这样得不到数组的元素的值。因为数组中存储的是数组的地址。
所以我们应该使用%p来打印数组名。
int arr[] = { 10 , 20 , 30 };
printf("arr[0]的内存地址 :%p\n",&arr[0]);
printf(" arr 的内存地址 :%p\n",&arr);
printf(" arr 中储存的值 :%d [%p]\n",arr,arr);
输出结果:
arr[0]的内存地址 :0x7ffeefbff74c
arr 的内存地址 :0x7ffeefbff74c
arr 中储存的值 :-272631988 [0x7ffeefbff74c]
数组的地址==数组名==数组中低字节的地址==数组中下标为0的元素的地址==数组中下表为0的元素的低字节的地址
P214 - 数组长度的计算
1.数组的每一个元素的类型相同,所以数组的每一个元素占用的字节空间一样。
2.使用sizeof运算符 可以计算数组总共占用的字节数。
sizeof(数组名); //这样我们就可以得到这个数组占用总的字节数。
3.得到数组占用的总字节数,然后除以每1个元素占用的字节数就可以得到数组的长度了。
int arr[] = {545,45,45,45,4,877,8,23,8,423,18,5,12,8,7,7,65,32,8789,6,1,4,4,21,4,89,78,45,3};
int len = sizeof(arr) / sizeof(int) ;
for(int i = 0 ; i < len ; i ++)
{
printf("arr[%d]:%d\n",i,arr[i]);
}
4.不建议将字节数写死。元素占用的字节数建议也是使用sizeof计算出来。因为不同的系统不同的编译器相同的变量可能占用的字节不一样。
sizeof(arr) / sizeof(元素类型);
sizeof(arr) / sizeof(arr[ø]) ;
P215-P217 - 关于数组你必须学会的四种简单算法(找出最大,最小,求累加和) & 判断数组中是否包含指定的元素,找指定的元素在数组中第一次出现的下标)
int arr[]={186,953,109,382,349,153,236,620,935,492,396,651,774,208,685,59,867,636,572,518,378,522,602,366,729,632,869,329,845,441,540,701,272,30,187,887,726,113,832,37,554,672,762,428,758,31,447,940,903,830};
int max = INT32_MIN;
int min = INT32_MAX;
int len = sizeof(arr) / sizeof(arr[0]);
int key = 50 ;
int sum = 0 ;
int flag = 0 ;
for(int i = 0 ; i < len ; i++ )
{
sum += arr[i];
if(arr[i] > max)
{
max = arr[i];
}
if(arr[i] < min)
{
min = arr[i];
}
if(arr[i] == key)
{
printf("找到了和KEY相同的数,跳出循环\n");
printf("指定的KEY第一次出现的下标是%d\n",i);
}
flag = i + 1 ;
}
printf("最大数 : %d\n",max);
printf("最小数 : %d\n",min);
printf("累加和 : %d\n",sum);
if (flag == len)
{
printf("没有找到了和KEY相同的数\n");
}
P218 - 参数的值传递
1.函数的参数。
为函数传参的过程就是1个赋值的过程。
是将实参赋值给形参。
2.当参数的类型是int、double、float、char类型的时候。
调用者传入1个实参变量。
函数执行完毕之后,对实参变量的值没有影响。
像这样的传递我们叫做值传递。
调用者将实参变量传递到给函数,不管函数内部是如何操作形参。
对实参变量没有任何影响。
3.当1个函数的参数是1个普通变量的时候。那么这个时候可以传递数组的元素(数组的元素的类型和参数的类型一致)。
P219 - 数组作为函数的参数
4.数组是否可以作为函数的参数呢?
肯定可以。因为数组也是1个数据类型。
1.声明:
直接在函数的小括号中声明1个数组就可以了。
void test(arr[3])
2.调用:
如果被调用的函数带了参数,并且参数的类型是1个数组。那么在调用的时候就必须要为其传递1个数组,并且传递的数组的类型要和参数的数组的类型一致。
3.遇到的问题。
在函数的内部,通过sizeof去计算[参数数组】占用的字节数的时候。
我们发现1个奇怪的问题:无论参数数组的长度声明为多大,得到的永远都是8个字节。
这个时候,通过sizeof去计算参数 数组的长度的时候,是算不出来的。
为什么?
当数组作为函数的参数的时候,那么在传递的时候,会丢失数组的长度。
结论:当数组作为函数的参数的时候。在函数内部使用sizeof计算参数数组的字节数,得到的永远是8.
所以,在函数内部无法使用sizeof计算参数数组的长度。
4.为什么?
1.如果函数是1个数组,在声明这个参数的时候 并不会去真得创建这个数组。而是去申请一个存储数组地址的变量(指针变量),这个指针变量在内存中占用8个字节,所以用sizeof去计算这个参数数组的字节都是8.
2.在传值的时候,是把实参数组名传递过去,数组名代表数组的地址。所以,这个时候传值,传的是数组地址。把数组的地址传递给函数的参数,所以函数的参数也指向了实参数组。
5.如何解决。
那么就让调用者将实参数组的长度传递给我。再写1个参数,让调用者将传递进来的数组的出长度告诉我,
注意:
如果我们的函数的参数是1个数组,这个时候还必须得加1个参数。让调用者将这个数组的长度传递进来。
6.当数组作为函数的参数的时候。实际上他并不是1个数组.而是1个用来存储数组地址的个指针变量.所以,数组作为函数的参数的时候。那个长度就没有必要写了。
void test(int arr[], int len){};//传数组参数的时候将数组长度作为另外一个参数传进去。
P220 - 数组作为参数的地址传递
5.当函数的参数是1个数组的时候。那么在传递的时候,传递的是实参数组的地址。
所以,形参数组指针指向了实参数组。这个时候通过形参数组指针操作数组实际上操作的就是是实参数组。这种参数传递,我们叫做地址传递。
P221 - 重要结论
6.重要结论
1.当数组作为函数的参数的时候,会丢失数组的长度,所以这个时候还需要1个参数,让调用者传入数组的长度进来。
2.当数组作为函数的参数的时候,在函数内部去修改这个参数数组的元素,其实修改的就是实参数组的元素。
3.强调:只有数组作为函数的参数的时候,通过sizeof才算不出来长度。
P222 - 产生不重复的随机数
1.随机的产生一个双色球的号码。
1-33 随机产生6个不重复的数。
1-16 随机产生1个随机数。
2.如何产生不重复的随机数。
将之前产生的随机数储存起来,没产生1个随机数,就判断和之前的随机数有没有重复的,如果有就重新产生,如果没有就下一个。
P223 - 选择排序
/**
* 选择排序法
*
* @param arr 要排序的数组
* @param len 数组长度
* @param desc 是否降序,0为升序,非0为降序
*/
void SelectionSort(int arr[], int len ,int desc)
{
//数组中有len个数据,要比len-1轮。
for(int i = 0; i < len - 1 ; i++) //外层循环控制轮数,每循环一次,完成1轮的比较
{
//每轮做的事情,就是拿下标位i的元素和后面所有的元素进行比较
//arr[i] i+1 len-1
//将后面所有的元素遍历出来。
for (int j = i + 1 ; j < len ; j++)
{
if ( desc != 0 ) {
if(arr[i] < arr[j])
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}else
{
if(arr[i] > arr[j])
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
}
P224 - 冒泡排序
/**
* 冒泡排序法(降序排序)
*
* @param arr 要排序的数组
* @param len 数组的长度
* @param desc 是否降序,0为升序,非0为降序
*/
void bubbleSort(int arr[],int len,int desc)
{
//冒泡排序要比len-1.
for(int i = 0 ; i < len - 1 ; i++ )//外层循环控制轮数,每循环一次,要完成1轮的比较。
{
//每一轮比较多少次。 len - 1 - i
for (int j = 0 ; j < len - 1 - i ; j++ )
{
//j j+1
if(desc != 0 )
{
if(arr[j] < arr[j+1] )
{
int temp = arr[j] ;
arr[j] = arr[j+1] ;
arr[j+1] = temp ;
}
}else
{
if(arr[j] > arr[j+1] )
{
int temp = arr[j] ;
arr[j] = arr[j+1] ;
arr[j+1] = temp ;
}
}
}
}
}
P225 - 二分查找法
1.二分查找法/折半查找。
在1个数组中查找指定的元素的下标。
1.从头到尾挨个的遍历。这样的话,效率低下。
2.使用折半查找,前提是:数组是有序的。
/**
* 二分查找法/折半查找法
*
* @param arr 要查找数据的数组(必须是已升序排列过的)
* @param len 数组长度
* @param key 要查找的数字(必须是数组中存在的,否则会死循环)
*
* @return 返回找到的数组下标
*/
int search_binary(int arr[],int len,int key)
{
int min = 0 ;
int max = len - 1 ;
int mid = len / 2 ;
//先判断key 跟中间数是否相等,如果不一样就判断大小。
while(key != arr[mid])
{
//判断大小关系
if(arr[mid] > key)
{
//说明要找的下标在左边,再去找左边的中间数
max = mid - 1 ;
}else
{
min = mid + 1 ;
}
mid = ( min + max ) / 2;
}
return mid;
}