1.0:Hello World
2.0:整数
//C语言:
//16进制 0x71ed
//8进制:0125
//二进制:1010101(C语言并没有单独的二进制表示方法。前面这个数表示十进制)
//十进制:100
//汇编语言:
//16进制:1aH
//8进制:17O
//十进制:12D
//二进制:101B
//十六进制好处,可以直接观察这个数在内存中每个字节的内容
//原码,反码,补码
//负数的补码:负数的绝对值的原码取反加一
//负数的原码:负数的补码取反加一得到负数绝对值的原码,高位变为1
//低位优先:低位首先存在低地址
//高位优先:高位首先存在高地址
都知道第一位表示的是正负符号 则:0000 0000表示的是0; 0111 1111表示的是127,这样正数0-127就表示完了。轮到表示负数了,负数第一位为1(规定的) 则:1000 0001表示的是-11111 1111表示的是-127,这样负数就有-1到-127发现负数好像还有一个1000 0000,表示0?正数已经有了,那就表示-128吧,不浪费。你把 1000 0000补码一下就是 1000 0000
原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
[+1]原 = 0000 0001
[-1]原 = 1000 0001
反码的表示方法是:
正数的反码是其本身
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
补码的表示方法是:
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
[+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补
----------------------------------
为了解决原码做减法的问题, 出现了反码:
计算十进制的表达式: 1-1=0
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在"0"这个特殊的数值上. 虽然人们理解上+0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0.
于是补码的出现, 解决了0的符号以及两个编码的问题:
1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原
这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:
(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补
-1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)
使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].
因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-2^31, 23^1-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.
3.0:浮点数
3.1:浮点数定义和存储
C语言中浮点数的定义:
1. 单精度浮点数:float,4个字节
2. 双精度浮点数:double,8个字节
float x = 1.723f;//加入f代表单精度浮点数
浮点数是将特定长度的连续字节(4个字节和8个字节)的所有二进制位(32位和64位)分割为特定宽度的三个域:
- 符号域:S,占1位,正数为0,负数为1
- 阶码域:E,占8位或者11位,E=e+127(float) or e+1023(double),指数e 有正负既有符号数,但阶码为正即无符号数,所以将e加上127或者1023作为偏移,方便指数的比较。
- 尾数域:M,占23位或者52位
- 计算浮点数的存储方式,只需要依次求出1位符号位,8位或11位的阶码,以及23位或者52位的尾数即可。
- 公式:V=(-1)^s *M *2e=(-1)s *M *2^(E-127)/(E-1023)
浮点数在X86系统以低位优先方式处处
image.png4.0:数据类型
C语言是一个强类型语言,数据必须明确指定类型。
4.1:内建型别
- 字符类型:char(ASCII编码,1)/wchar_t(Unicode编码,与平台有关,windows平台2个字节,unix平台4个字节)
- 整型:short(2)/int(4)/long(与平台有关,windows平台4个字节,unix平台32位4个字节,64位8个)/long long(Linux中用的多,C语言标准没有此类型,是linux中的一个扩展)/_int64(C语言标准没有此类型,Windows中的一个扩展)
- 实数单精度:float(4)
- 实数双精度:double(8)
- 有符号和无符号:signed/unsigned,(signed)int/unsigned int
- 布尔类型:bool,0/1(true/false)C99<stdbool.h>;在之前的C99中没有bool类型需要自己定义
typedef int bool;
#define true 1
#define false 0
char gender = 'M';//ASCII字符
wchar_t sex = L'F'//加L表示UNICODE字符,Windows平台使用的Unicode32位编码,占用2个字节;Linux平台使用的Unicode64编码,占用4个字节
char name[16]="tom";
wchar_t nick[16]=L"Jack";//加L表示UNICODE字符,Windows平台使用的Unicode32位编码,每个字符占用2个字节;Linux平台使用的Unicode64编码,每个字符占用4个字节
sizeof(char) = 1;
sizeof(wchar_t) = 2或者4;//认清字符数与字节数的区别
char c = 'a';//'a'是字符常量,c是字符常量。常量是用单引号包围。
wchar_t wc = L'a';
short a1 = 100;
int a2 = 0x64;
long a3 = 100L;
unsigned short a4 = 100;
unsigned int a5 = 100;
unsigned long a6 = 100UL;
long long a7 = 100LL;//Linux
_int64 a8 = 100i64;//Windows
unsigned char/signed char,只写char,由编译器决定是有符号无符号
内建型别:数据以字节为单位存储在内存中
大写A的ASCII值为65,小写a的ASCII值为97,相差32。在程序中是个硬编码,不要使用,用'a'-'A'替代
image
Unicode4编码具体实现有Utf-8(字符占用的字节可以从1到4个),Utf-16(2),Uft-32(4);
网页通常采用Utf-8编码,可以加快浏览速度。
C语言中的类型,可以通过typedef定义成新的类型。比如下面的例子:
typedef int INT
typedef unsigned int UINT
typedef unsigned short USHORT
typedef char CHAR
typedef unsigned long ULONG
typedef usigned short WORD
typedef unsigned int DWORD
typedef char CHAR
4.2:类型长度与取值范围
sizeof计算类型或者变量的长度,字节为单位,是操作符,不是函数。在编译阶段确定,而不是运行阶段。
注意
sizeof(char)的长度为:1
sizeof(wchar_t):2或4
sizeof(short)的长度为:2
sizeof(int)的长度为:4
sizeof(long)的长度为:4(Win X86和X64都为4,Linux X86为4,X64为8)
sizeof(float)的长度为:4
sizeof(double)的长度为:8
sizeof(bool)的长度为:1(C++里)
sizeof(BOOL)的长度为:4(windows平台)
类型 | 有符号 | 无符号 |
---|---|---|
char | [-128, 127] | [0, 255] |
short | [-32768, 32767] | [0, 65535] |
int/long | [-2147483648, 2147483647] | [0, 4294967295] |
float | [-3.4*10^38, 3.4*10^38] | |
double | [-1.7*10^308, 1.7*10^308] |
有符号最小值:除了1之外,其他位都为0
整数溢出之后发生什么呢? (char)(-128*-1)
整数上溢出1:最小值;下溢出1:最大值
以一个字节为例子:
有符号:[-128, 127]; 127+1->-128; -128-1->127
无符号:[0, 255]; 255+1->0; 0-1->255
4.3:类型转换
- 类型转换:将一种类型转换为另一种类型,一种类型指针转为另一种类型指针。
- 强制转换:(新类型)变量
char c =‘a’;
short i = (short)c;
int a = (int)i;
long b = (long)a;
float c = (float)b;
double d = (double)c;
上面的转换都是从宽度短的转换为宽度大的。当把一个宽度短的数据类型到宽度大的数据类型转换的时候,是用符号位填充多出的位数。
类型 | 原1字节数 | 转换为2字节数 |
---|---|---|
正数 | 00110101 | 00000000 00110101 |
负数 | 10110100 | 11111111 10110100 |
下面的例子是把宽度长的转换为宽度短的,这个过程可能会造成数据的丢失:
int x = 0x12345678;
short y = (short)x;
当把占4个字节的x强转为占2个字节的y的时候,整数的高位2个字节将会被截断因此,y只保留了0x12345678中的低位2个字节0x5678。
浮点数到整数
int i = (int)3.15;//不是四舍五入,取整
如果一个运算符两边的运算数类型不同,先要将其转换为相同的类型,即较低类型转换为较高类型,然后再参加运算,转换规则如图所示。
5:变量
常量,顾名思义,就是其值不会改变的数据,就是不同数据类型中不变的值。
变量,内存或寄存器中用一个标识符命名的存储单元,可以用来存储一个特定类型的数据,并且数据的值在程序运行过程中可以进行修改。
5.1:变量定义与命名
程序员一旦定义了变量,那么,变量就至少可为我们提供两个信息:一是变量的地址,即就是,操作系统为变量在内存中分配的若干内存的首地址;二是变量的值,也就是,变量在内存中所分配的那些内存单元中所存放的数据。
int a = 10;
a=100;
printf(“addr:%p,value:%d\n”, &a,a);
由于程序的多样需要,我们对变量也有各种各样的要求,比如:
- 变量的生命期
- 变量的初始状态
- 变量的有效区域
- 变量的开辟地
- 变量的开辟区域的大小
C语言设置了以下变量:
- 不同数据类型的变量;如:char cHar, int iTimes, float faverage;
- 全局变量;
- 局部变量;
- 静态变量:静态全局变量和静态局部变量;关键词:static
- 寄存器变量:关键词:register;
- 外部变量:关键词:extern;
在函数内部说明的变量为局部变量,只有在函数执行时,局部变量才存在,当函数执行完退出后,局部变量随之消失。也就是,当函数执行完退出后,原先在函数内定义的变量现在不能用。
与局部变量不同,全局变量在整个程序都是可见的,可在整个程序运行过程中,对于任何一个程序都是可用的。全局变量的说明的位置在所有函数之外,但可被任何一个函数使用,读取或者写入。
静态变量是分配在存储器中C程序所占据的数据段内,C程序运行的整个过程中一直保留,不会被别的变量占用。静态变量可以定义成全局变量或局部变量,当定义为全局变量时,在它定义的程序的整个运行期间均存在并且保持原来的存储单元位置不会改变。
同静态全局变量定义一样,当在局部变量名前加静态变量说明符static,该变量就定义为静态局部变量。编译器为该变量建立永久存储单元。永久是指C程序运行的整个过程中一直保留,不会被别的变量占用。静态局部变量和静态全局变量的根本区别在作用区域上,静态局部变量只在它被说明的函数或复合语句中有效,并且在两次函数调用期间仍然保存其值不变,即就是有记忆功能;它们的生命期是相同的,和C程序整个运行期同在。
5.2:变量作用域存储空间生命周期
变量的作用域:变量能够在什么地方被访问。
变量的存储空间:存放变量的内存位置。要明白变量在内存中存放的位置,首先就先了解下系统的内存布局。
PAE:physical address extension物理地址扩展。
以x86为例,x86支持32位寻址,因此可以支持最大232=4GB的虚拟内存空间(也可以通过PAE技术增加到36位寻址,因此可以将寻址空间扩大到64GB)。如图所示,在4G的虚拟地址空间中,Windows系统的内存主要分为内核空间和应用层空间上下两部分,每部分各占约2GB,其中还包括了一个64KB的NULL空间以及非法区域。Windows内存的逻辑地址包含2部分,即段选择符和偏移地址,CPU在做地址翻译的时候,通过分段机制计算出一个线性地址,再通过页表机制映射到物理地址以便存取物理内存中的数据和指令。
X64(AMD64)的内存布局与X86的内存布局类似,不同的地方在于各自空间的范围和大小不同,同时X64下还存在着一些空洞(hole),如图所示。在X64内存理论上支持最大264的寻址空间,但实际上这个空间太大了,目前根本用不完,因此实际上的X64系统一般都只支持到40多位(比如Windows支持44位最大寻址空间为16TB,Linux 支持48位最大寻址空间256TB等),支持的空间达到了TB级别。但是,无论是在内核空间还是在应用层空间,这些上TB的空间并不都是可用的,存在着所谓的空洞。
系统内存布局
image
一个进程的内存空间布局
栈的大小?
Windows应用栈默认1M,可以用编译指令/stack可以改设其他值;
Windows内核栈,系统根据CPU架构而定,X86系统上为12KB,X64系统为24KB,安腾系统为32KB
Linux内核栈为4K或8K;应用层10M
当程序加载入内存后,程序在内存中可以分为若干个区,这些区包括:静态区(.data,.rdata,.bss段等),代码区(.text),堆区,栈区等。
内存区 | 名字 | 用途 |
---|---|---|
.text | 代码段 | 这个存放代码的,用汇编角度来看就是指令。 |
.rdata | 只读数据段 | 存放常量,字符常量,const常量。(read不可修改) |
.data | 数据段 | 存放已经初始化好的全局变量和静态变量。 |
.bss | 存放未初始化的全局变量和静态变量。 | |
.stack | 栈区 | 存放局部变量等。栈区存放着函数调用过程中的形参,返回地址以及局部变量等。 |
.rdata,.data,.bss都是存放的数据。除了.bss段,.rdata,.data段的值都是在编译的时候就确定了,并且将其编译进了可执行文件,经过反汇编都能找得到。bss段是在代码运行的时候手动编写汇编代码将其初始化为0的(这就是未初始化的全局和静态变量默认值为0的根源)
变量的生命周期变量有效的时间段,只有在变量的生命周期内,才能访问该变量。有的变量的生命周期是函数运行期间,函数结束,变量就消失了。有的变量的生命周期是程序运行期间,只要程序还未结束,变量就不会消失或销毁。
变量分类:全局变量,局部变量,静态变量(静态全局变量和静态局部变量),寄存器变量,外部变量。
变量 | 作用域 | 存储空间 | 生命周期 |
---|---|---|---|
全局变量 | 静态区(.data存放初始化全局变量,.bss存放未初始化全局变量) | 整个程序 | 程序运行期间 |
全局静态变量 | 静态区(.data存放初始化全局变量,.bss存放未初始化全局变量) | 当前源文件 | 运行期间 |
局部变量 | 栈 | 当前函数内部 | 函数执行期间 |
局部静态变量 | 静态区 | 当前函数内部,只初始化一次 | 程序运行期间 |
寄存器变量 | 寄存器 | 当前函数 | 函数运行期间 |
寄存器变量:不像其他变量那样在内存中存放数据,而是在CPU的寄存器中暂存数据,使用寄存器变量比使用内存变量的操作速度快得多。只有整型和字符型变量可定义为寄存器变量。由于CPU中寄存器有限,尽量减少使用数量和和占用时间,用完马上释放;不能定义为全局变量,也不能定义在结构或者类中。
extern关键字:为了解决全局变量和函数的共用问题,就引入了 extern关键字。这样只需在一个文件中定义全局变量和函数,在另一个文件中要用到这些变量和函数时,只需将那个文件中的变量和函数说明表复制过来,在前面加上extern,告诉编译器,这些变量和函数已经在别的文件中定义说明。
//main.cpp
int a = 0; //全局变量; .data
char *p1; //全局变量; .bss
static int x=10; //全局静态变量,10为常量; .data
void func(void) {
int b; //局部变量; .stack
char s[] = “123”; //s为局部变量, .stack ; ”123”为常量, .rdata
char *p2; //p2为局部变量; .stack
char *p3 = “hello, world”; //p3为局部变量,.stack ; ”hello, world”为常量, .rdata
static int c = 0;//局部静态变量; .data
register int i =0;//寄存器变量;
p1 = (char *)malloc(128); //p1指向堆上的内存,直到free
p2 = (char *)malloc(256);//p2指向堆上的内存,直到free
free(p1);
free(p2);
}
int main(void) {
func();
printf(“hello world\n”);
return 0;
}
6:输入输出-stdio
scanf/printf
image
网友评论