C continue
复习:
进制转换:
十进制转二进制:对2求余,直到商为0,过程中余数的逆序
二进制转十进制:2^(n-1)每位结果相加
二进制转八进制:三位二进制对应一位八进制
二进制转十六进制:四位二进制对应一位十六进制
代码中0开头的是八进制,0x开头的是十六进制
%o 八进制 %x十六进制
原反补码:
正数/无符号数的原反补一致
负数先转换为二进制,除符号位外各位取反得到反码,反码+1得到补码(反之亦然)
补码转数据:无符号直接转,有符号看最高位,0直接转,1为负数
位运算符:
~取反在位运算符中优先级最高
单目、算术、位、关系、逻辑、三目、赋值 (大部分时候优先级)
int num =40;num =num>>2+1;printf(“%d”,num);
^按位异或 >>左边补符号位 <<右边补0
注意:只要表达式中出现了位运算符,一定要先转换成二进制进行计算
自定义函数:
函数声明、函数定义、函数调用 三步GO
函数定义在调用之前,声明可以省略;声明的内容要与定义完全一致;如果不需要返回值或者参数则一定要写void;在函数调用前,没有声明、定义则会产生隐式声明的警告
一、函数传参:
- 形参变量、函数内定义的变量,都只属于它所在函数,出了该函数就不能再用
- 普通实参与形参之间是通过赋值的方式传递数据的(单向值传递)
- return其实是把数据存放到一个公共区域(函数之间都可以访问的区域),如果不写return语句,就会去公共区域读取原本已经存在的数据,就得到一个垃圾数据
- 当数组作为函数的参数时,[]中的长度就会丢失,传数组时需要额外增加一个变量传递数组的长度
- 数组作为函数参数传递时,传递的是数组的首地址,称“址传递”(传递地址),函数和函数调用者可以共享同一个数组
练习1:实现函数,找出数组中的最大值(调用者提供数组通过函数返回最大值)
#include <stdio.h>
int findmax(int arr[],int len);
int findmax(int arr[],int len)
{
int temp=arr[0];
for(int i=1;i<len;i++)
{
if(arr[i]>temp)
temp=arr[i];
}
return temp;
}
int main(int argc,const char* argv[])
{
int arr[10]={14,13,25,85,4,54,69,91,20,10};
printf("%d",findmax(arr,10));
}
练习2:实现函数,对数组进行升序排序(同上)
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
void uparr(int arr[],int len);
void uparr(int arr[],int len)
{
int temp=0,min=0;
for(int i=0;i<len-1;i++)
{
min=i;
for(int j=i;j<len;j++)
{
if(arr[j]<arr[min])
{
temp=arr[j];
arr[j]=arr[min];
arr[min]=temp;
}
}
}
}
int main(int argc,const char* argv[])
{
srand(time(NULL));
int arr[10]={};
for(int i=0;i<10;i++)
{
arr[i]=rand()%100;
}
uparr(arr,10); //万能公式在心中 sizeof(arr)/sizeof(arr[0])
for(int i=0;i<10;i++)
{
printf("%d ",arr[i]);
}
}
练习3:实现函数,查找数组中是否存在某个值(由调用者提供),如果存在则返回该值的下标
#include <stdio.h>
int find(int arr[],int len,int key);
int find(int arr[],int len,int key)
{
/*
// 对分查找(要求有序)
int left=0,right=len-1;
while(l<=r)
{
int p=(l+r)/2;
if(key==arr[p]) return p;
if(key<arr[p]) r=p-1;
else l=p+1;
}
return -1;
*/
int i=0;
for(i=0;i<len;i++)
{
if(arr[i]==key)
{
return i;
}
}
return -1;
}
int main(int argc,const char* argv[])
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
if(find(arr,10,5)==-1)
printf("no\n");
else
printf("%d",find(arr,10,5));
}
设计函数的建议:
- 一个函数最好就解决一个问题,降低错误率,提高可读性
- 尽量减少函数之间的依赖层数(降低耦合度)
- 数据由调用者提供,结果返回给调用者(提高函数的通用性)
- 考虑函数的非法参数,可以通过返回值的方式告诉调用者你的参数有误,也可以通过注释的方式写明情况(提高函数的健壮性)。
二、进程映像:
程序:存储在磁盘上的可执行文件(二进制文件、脚本文件)
进程:在系统中运行中的程序
进程映像:进程内存的分布情况(要背)☆★
text 代码段(权限只读,强制修改就会产生段错误--非法访问内存) 存储二进制指令(每条代码换成二进制后)、常量数据、初始化过的全局变量、初始化过的静态局部变量
data 数据段 初始化过的全局变量、初始化过的静态局部变量
bss 静态数据段 未初始化过的全局变量,进程运行前,该段内存会自动清理为零(不初始化会变0)、未初始化过的静态局部变量(不初始化同样变0)
stack 栈 局部变量、块变量,由操作系统管理的,会自动申请、释放,在进程运行过程中自动申请、释放内存。但是所占内存小
heap 堆 由程序员手动管理,内存足够大
三、局部变量和全局变量:
全局变量:定义在函数外的变量。1.存储位置:data(初始化)或者bss(未初始化)2.生命周期:程序开始到程序结束3.作用范围:在程序的任意位置
局部变量:定义在函数内的变量。1.存储位置:stack栈内存2.生命周期:从函数调用定义开始到函数结束3.作用范围:只能在函数内使用
块变量:定义在语句块内的变量 if/for/while1.存储位置:stack栈内存2.生命周期:从函数调用定义开始到函数结束3.作用范围:只能在语句块内使用(与局部差别)
当全局变量和局部变量同名时,在函数内会屏蔽掉全局变量,同名变量在块变量中也会屏蔽全局变量和局部变量。因此建议全局变量首字母大写!
四、类型限定符:
auto :用于定义自动分配内存、释放内存的变量(局部变量),不加auto就代表了加。注意:全局变量是不能用auto修饰的!C11中auto变成了自动识别类型:auto num=10;自动定义类型
extern :声明变量,告诉编译器此变量已经在别处定义过了请放心使用,extern+变量声明(变量不能+等于 extern 类型名 变量名),但是只能临时让编译通过,在链接时如果找不到该变量的定义依然会报错,不能在声明变量时赋值。
在多文件编程中,假设a.c中定义了全局变量N,想要在b.c中使用N时就需要在使用前声明该变量;
static(很关键) :1.会改变变量存储位置:改变局部变量的存储位置(静态局部变量),从stack改为data或者bss(根据是否初始化);2.延长生命周期:延长了局部变量的生命周期直到进程结束为止;3.限制作用范围:限制全局变量、函数的作用范围,只能在本文件内使用----可以防止全局变量或函数的命名冲突、也可以防止被别的文件使用(必答三句话)
const (常数):“保护”变量值不能被显示地修改,但是可以通过访问内存的方式来修改值;但是如果修饰的是初始化过的全局变量、初始化过的静态局部变量,则该变量会从data转到text变成了“常量”,不能够强制修改
volatile(易变的不稳定的) :如果变量的值没有被显示修改,那么在使用该变量时不会从内存中读取,而是继续使用上次读取的结果,这个过程叫做编译器的取值优化,一般变量都会进行。如果变量被volatile修饰之后,编译器不会对该变量进行取值优化,每次都会从内存中重新读取,读取之间值就可能发生改变volatile num;(num==num)可能为假。一般硬件编程时、多线程编程时会使用到
register :(存储介质:硬盘-->内存-->高级缓存-->寄存器)申请把变量的存储介质由内存改为寄存器,由于寄存器数量有限,不一定能够申请成功。注意:寄存器变量不能取地址
typedef :类型重定义,定义变量时,如果在类型前面加typedef,那么变量名就变成了这个类型的新名字:int num;-->typedef int num;int a;num b 注意:typedef不是替换关系