目录
(5)四种整型:int, short, long, long long
(8)三种浮点类型:float, double, long double
③char本身的值是ASCII码整数,可按ASCII码和符号两种⽅式输出
(1)数的进制和存储单位
①四种进制的对应关系
十进制 | 二进制 | 八进制 | 十六进制 |
1 | 1 | 1 | 1 |
7 | 111 | 7 | 7 |
8 | 1000 | 10 | 8 |
10 | 1010 | 12 | a |
15 | 1111 | 17 | f |
16 | 10000 | 20 | 10 |
②二进制和bit,以及其它存储单位
- 一个二进制的位就是一个bit(比特),比如10001是5个比特,111是3个比特8个bit是一个BYTE(字节),计算机的最小存储单位是字节
- 2个BYTE是一个WORD(字)
- 2个WORD
- 2个WORD是一个DWORD(双字)
- 1024个BYTE 是1K
- 1024K 是1M
- 1024M 是1G
- 1024G = 1T
不同场景下的单位:签合同时一定要注意这一点,写500M是模糊不清的,到底是500MB还是500Mb,这两者可是8倍差距
- 512GB 硬盘 (大B,单位是字节)
- 12Mb 网络带宽 (小b,单位是比特)
- 200MB 文件的大小 (大B,单位是字节)
- 100Mb网卡 (小b,单位是比特)
③进制转化技巧
2进制其实和8进制和16进制是一一对应的
2进制转化为8进制
010 101 010 111 111 010 101 011
2 5 2 7 7 2 5 3
2进制转化为16进制
0101 0101 0111 1110 1010 1011转化为16进制
5 5 7 e a b
二进制其实和8进制和16进制是一一对应的,在计算机语言中一般不直接用二进制,c语言更多的用8进制或者16进制
c语言不能直接书写二进制,用8进制和16进制来替代。因为2 8 16是一一对应的。
2进制转化为10进制
10101010 11111101
用2的n次方累加可以计算出
10进制转化为2进制
1. 先把这个数转化为8进制。以56为例,用56除以8,分别取余数和商数。然后用得到商数去除8。循环下去。直到新的商数为0时,停止循环。最后将所有余数从右到左拼起来就是最终结果。(辗转相除法)
56 8: 7(商数) 0(余数)
7 8: 0(商数) 7(余数)
所以十进制的56转化为8进制是70
2. 然后把8进制直接对应为2进制。
7 0
111 000
所以十进制的56转化为2进制是111000
(2)数的原码、反码和补码
①计算机内部用二进制编码
②数分为有符号数和无符号数
③有符号数的编码会进一步区分为原码、反码和补码
反码
- 正数的反码就是原码本身。
- 负数的反码,就是原码的基础上,除了符号位以外,其它位置上的数变为相反数,即1变为 0,1变为0。
补码
④补码推算原码
- 补码的补码就是原码。一个数补码的补码就是原码,所以只要将补码再求补码,就是它的原码了。
- 求补码的过程逆向,就是先将补码的先减1,再取反码(将除符号位以外的数取反,即1 变为0,0变为1)。
⑤计算机用补码存储数字和参与运算的原因
补码解决了+0和-0不同状态的问题
⑥-128的补码等于127的补码+1
⑦有无符号数的大小范围
- 00000000表示零
- 00000001~01111111 正数1~127
- 10000000~11111111表示负数-128~-1。注意这里10000000表示-128,11111111表 示-1。
(3)关键字sizeof
#include <stdio.h>
int main()
{
int a = 1; // 直接写一个整数,比如这里的1,默认是10进制的整数
int b;
b = sizeof(a);
printf("%d\n", b); // 4 // %d的意思是按照10进制的有符号数输出
a = 11;
int c = sizeof(a);
printf("%d\n", c); // 4
return 0;
}
c语言不规定数据类型的大小,具体某种数据类型占多大内存,和系统相关
在同一个系统下,具体的一种数据类型大小是相同的(比如上面的int类型是4个字节的大小)
int a = 100;
printf("%u\n", sizeof(a)); // 32位系统 //要注意这个小的细节
printf("%lu\n", sizeof(a)); // 64位系统
(4)unsigned关键字
定义无符号数的关键字
#include <stdio.h>
#define MAX 100u // 如何定义宏时设为无符号整数
int main()
{
// 1.定义有符号数
int a; // 定义了一个有符号的int
signed int b; // 与上面等价
// 2.用unsigned关键字定义无符号数
unsigned int c;
return 0;
}
(5)四种整型:int, short, long, long long
int类型能表示的数的范围:整数占4个字节,一个字节是8位,因此一个无符号数最大可以表示到2^32=4294967296=40多亿(0到40多亿),有符号的就是除2可以表达20多亿个数(-20多亿到20多亿)。int在64位的windows或者64位linux一般是4个字节大小,但在其它不同的系统可能会有所变化。
- short ,短整型,一般short是int的一半大小,即2个字节
- int,整型,4个字节(int比较稳定,无论在哪个系统都是4个字节大小)
- long,长整型,long在32位系统下一般是4个字节,在64位linux系统一般是8个字节,64位的windows一般是4个字节
- long long,长长整型,long long在32位系统和64位系统下都是8个字节
- 200 // 有符号整数
- 200u // 无符号整数
- 200l或者200L // 有符号长整数
- 200lu或者200Lu或者200LU // 无符号长整数
- 100ll或者100LL // 有符号长长整数
-
100LLu或者100ull // 无符号长长整型
#include <stdio.h>
int main()
{
short a;
printf("%d\n", sizeof(a)); // 2
int b;
printf("%d\n", sizeof(b)); // 4
long c;
printf("%d\n", sizeof(c)); // 8
long long d;
printf("%d\n", sizeof(d)); // 8
unsigned int e;
printf("%d\n", sizeof(e)); // 4
return 0;
}
(6)int不同进制的定义与printf输出
①如何定义不同进制的整数
#include <stdio.h>
int main()
{
int a; // 定义了一个整数,默认10进制
int b = 020; // 8进制的20(等于10进制的16)(注意开头是数字0)
int c = 0X20; // 16进制的20(等于10进制的32)(注意开头是数字0)
return 0;
}
②printf输出按不同进制输出整数
10进制还可以分为有符号数和无符号数输出
8进制和16进制只能无符号输出
#include <stdio.h>
int main()
{
int a = 1000;
printf("%d\n", a); // 按10进制的有符号数输出:1000
printf("%u\n", a); // 按10进制的无符号数输出:1000
printf("%o\n", a); // 按8进制(无符号)输出:1750
printf("%x\n", a); // 按16进制(无符号)输出, abcd这些用小写:3e8
printf("%X\n", a); // 按16进制(无符号)输出, abcd这些用大写:3E8
return 0;
}
③输出结果的过程详解
- 先求出原码的补码(因为计算机存储时按照补码存储的);
- 对求出来的那个补码,按输出方式(是否有无符号)来改变补码的首位是符号位还是数的一部分:
- 求出原码,然后输出原码。
#include <stdio.h>
int main()
{
int x = 10; // 默认是10进制的有符号数
printf("%d\n", x); //10 //按10进制输出
int y = 010; // 8进制的10(等于10进制的8)
printf("%d\n", y); // 8
int z = 0x1b;
printf("%d\n", z); // 27 // 按10进制输出
printf("%o\n", z); // 33 // 按8进制输出
printf("%x\n", z); // 1b // 按16进制的小写输出
printf("%X\n", z); // 1B // 按16进制的大写输出
int a = -11; // 默认是10进制,有符号数
printf("%u\n", a); //4294967285 //按十进制的无符号数输出
printf("%x\n", a); //fffffff5 //按16进制的无符号数输出
/*
-11按4个字节在计算机在存储时的补码为:
原码:1000 0000 0000 0000 0000 0000 0000 1011
补码:1111 1111 1111 1111 1111 1111 1111 0101
%u表示将这个数按照无符号数看待
求其原码,由于是无符号数,原码就是补码
然后进行输出,对应16进制
f f f f f f f 5
对应10进制 4294967285
*/
return 0;
}
(7)常量(宏常量和const常量)
c++用const比较多
常量是不可修改的
#include <stdio.h>
#define MAX 100 //宏常量 //宏常量是不可修改的 //宏常量习惯用大写字母
#define NAME "ABC" //字符串形式的宏常量
#define MAX 100
int main()
{
//MAX = 0; //宏常量是不可修改的
printf("%dhello world\n", MAX);
int a = 1; //整数
a = 2; //整数可以修改
printf("%d\n",a);
const int b = 10; //const生成常量,不可修改 //c++用const比较多
//b = 100; //因为不可修改,所以会报错
printf("%d\n", b);
printf("%s\n", NAME);
return 0;
}
(8)三种浮点类型:float, double, long double
①内存大小
#include <stdio.h>
int main()
{
int pai=3.14;
printf("%d\n", pai);
float a = 3;
a = a / 2;
printf("%f\n", a); //1.500000 默认小数位数6位
double b;
long double c;
printf("%lu,%lu,%lu\n", sizeof(a), sizeof(b), sizeof(c));//4,8,16
// 作业:要求四舍五入输出
double d = 4.49;
int result = d + 0.5;
printf("%d\n", result); // 5
return 0;
}
②printf函数设置浮点数输出格式
#include <stdio.h>
int main()
{
float x = 1.25678;
printf("%f\n",x); //1.256780 //默认6位小数
printf("%4.f\n",x); //" 1" //4表示宽度,.和f之间啥都没有表示0个小数位
printf("%4.0f\n",x); //" 1"
printf("%4.2f\n",x); //"1.26" //4表示宽度,.和f之间2表示2个小数位
printf("%.3f\n",x); //"1.257" //3位小数,自适应宽度
return 0;
}
(9)char类型
①char的本质是⼀个BYTE的整数
#include <stdio.h>
int main()
{
char a = 'x';
int i = sizeof(a);
printf("%d\n", i); //1
return 0;
}
1111 1111
0000 0000
signed char 127-128
unsigned char 0-255
int a = 'a'; //可以存放
char a = 500; //不可以存放 //char⼀个字节最⼤127,所以这⾥会出错,因为500太⼤了
char a = 97;
printf("%d\n",a); //结果是97
printf("%c\n",a); //结果是字符a
②char与整数,与字符串的区别
"100"100区别在哪⾥?
③char本身的值是ASCII码整数,可按ASCII码和符号两种⽅式输出
#include <stdio.h>
int main()
{
char a = 'x';
// 1.内存大小
int i = sizeof(a);
printf("%d\n",i); //1
// 2.两种打印结果
a = 65;
printf("%d\n",a); //65 //输出ASCII码
printf("%c\n",a); //A //输出ASCII码对应的字符
char b = 'A';
if (a == b) // true
{
printf("char 65 == char 'A'\n");
}
// 3.与整数运算
a += 2;
printf("%d\n",a); //67 //输出ASCII码
printf("%c\n",a); //C //输出ASCII码对应的字符
// 4.整型溢出
a = 127;
a += 1;
printf("%d\n",a); //-128 整型溢出
return 0;
}
④char转义符号
char类型只能放标准的英⽂字⺟,因为标点符号那些。不能来定义中⽂。
char a = '我'; //会出错
char a = '\''; //输出单引号'
不可打印char转义符号
\a,警报
\b,退⼀格
\n,换⾏
\r,回⻋
\t,制表符,就是⼀个tab键\\,就是\
\',单引号
\'',双引号
\?,问号
#include <stdio.h>
int main()
{
char a = 'x';
a = '\b'; //等于一个退格的ASCII码
printf("(%d%c)\n", a, a); //(8)
a = '\t'; //等于一个tab的ASCII码
printf("(%d%c)", a, a); //(9 )
return 0;
}
//回车\r 换行\n
回车就是退格到最左边,换行就是去到下一行abc(这里其实是回车+换行)
bcd
abc(只有换行,没有回车)
bcd
bcd(abc是被bcd重叠了)这叫回车
在printf里面用\n时会自动加一个\r
⑤整数类型初始化为小数时取整数部分
#include <stdio.h>
int main()
{
int x = 3.66;
printf("%d\n", x); // 3
return 0;
}
(10)字符串
- 'a' 字符,在内存里就是一个字符a
- "a" 字符串,在内存里两个连续的字符,第一个是'a',第二个是'\0'
- "hello world" → 'h' 'e' ....... 'd' '\0'
- "a"字符串常量 → 'a' '\0' 'a'字符常量
- "abc" → 'a' 'b' 'c' '\0'
- 'abc' 这样写是不对的,因为字符常量只能是一个ASCII字符
(11)printf输出格式
格式 | 对应类型 | 含义 |
%d
|
int
|
有符号十进制整数
|
%u
| unsigned int |
无符号十进制整数
|
%hd
|
short
|
短整数
|
%hu
|
unsigned short
|
无符号短整数
|
%o
|
无符号
八进制整数
| |
%x
%X
|
无符号
16进制整数
x对应着abcdef
X对 应ABCDEF
| |
%f
|
float或者double
| |
%e
%E
|
double
|
科学计数法
|
%c
|
char
| |
%s
%S
|
char * / wchar_t *
|
字符串。
特别地,当char *p = NULL时,%S将输出(null)
|
%p
|
void *
|
以16进制形式输出指针。
|
%%
|
输出一个百分号%
| |
printf附加格式
| ||
l或者ll
|
放在d,u,x,o前面表示长整数
比如输出long 用%ld
输出long long用%lld
比如
无符号长 整数%lu
| |
m(代表一个整数)
|
数据最小宽度
| |
0
|
将输出前面补上0,直到占满指定宽度为止
| |
-(减号)
|
左对齐(不可以搭配'-'使用)
| |
特殊类型打印建议 | ||
类型 | 建议 | |
size_t | %I64d |
#include <stdio.h>
int main()
{
putchar(100); //输出ASCII码为100所对应的字符 → d
putchar(50000); //ASCII码循环后的那个ASCII码锁对应的字符 → P
putchar('a'); //直接输出字符a
//putchar('ab'); //putchar最多一个字符,这里会warning
printf("hello world\n"); //用printf输出字符串
// char x = 'abcd'; // 警告
printf("%s\n", "abc");
printf("%s + %s\n","abc", "bcd");
int a = 100;
printf("%p\n", &a); //000000000061FE18
//&a表示取内存中的地址,输出地址用%p
printf("(%6d)\n",a); // ( 100)
printf("(%06d)\n",a); // (000100)
printf("(%-6d)\n",a); // (100 )
printf("(%-06d)\n",a); // (100 )
//会warning, 直接忽略掉0,因为要是加上0就是100000,与原来有变化
return 0;
}
(12)整数溢出
#include <stdio.h>
int main()
{
char x = 127;
x += 1;
printf("%d\n", x); //-128
/*
(1)先得到运算后的结果
127 有符号char
=> 原码0111 1111
=> 补码0111 1111
=> 补码参与运算 加1 => 1000 0000
=> 转化为原码 = 1111 1111 + 1 = 1 0000 0000
=> 位溢出 丢掉除符号的最左边那位 1 000 0000
=> 1 000 0000 其实是 -128的原码
=> -128的原码:1 1000 0000 位溢出 丢掉除符号的最左边那位 => 1 000 0000
(也可以从127 + 1的角度得到-128的补码和原码)
所以x现在的值就是 -128
(2)再看输出方式
%d 是将 x看做有符号整数输出,本身就是有符号数,所以输出 -128
具体拆解过程:
-128对应的 int 型原码 为 1000 0000 0000 0000 0000 0000 1000 0000
=> 补码 1111 1111 1111 1111 1111 1111 0111 1111 + 1
=> 1111 1111 1111 1111 1111 1111 1000 0000
%d 是将 x看做有符号整数输出,看做有符号数,因此要将补码的首位看做有符号数
=> 因此原码 = 1000 0000 0000 0000 0000 0000 1000 0000
=> 转化为10进制 就是 -128
*/
printf("%u\n", x); // 4294967168
/*
(1)x现在是-128
(2)按输出方式输出值
=> -128 原码 1000 0000 0000 0000 0000 0000 1000 0000
=> 补码 1111 1111 1111 1111 1111 1111 1000 0000
=> %u是当做无符号数输出,因此补码的首位要看是数的一部分
=> 无符号数补码就是原码,因此对应的整就是16进制的 ffffff80
=> 对应的10进制数就是4294967168
*/
printf("%x\n", 4294967168); // ffffff80
int y = 0x7fffffff;
printf("%d\n", y); //2147483647
y += 1;
printf("%d\n", y); //-2147483648
y += 2;
printf("%d\n", y); //-2147483646
return 0;
}
(13)大端对齐和小端对齐
如下图,一个int类型的变量a存放在内存地址中,需要占用4个Byte(注意计算机内存地址的最 小单位是Byte)。一个大于Byte的数据类型放到在内存中存放的时候要有先后顺序,现在要把 变量a拆成4部分分别放到4个字节中去,如何放呢?
- 低内存地址放整数的低位,高内存地址放整数的高位,这种方式叫倒着放,术语叫小端对齐。电脑X86和手机ARM都是小端对齐的。下图左边圆圈存地址越小放的是整数越高位的数,比如内存地址为0对应整数的12,就是小端对齐。
- 低内存地址放整数的高位,高内存地址放整数的低位,这种方式叫正着放,语叫大端对齐。很多unix的CPU都是大端对齐的。如下图右边圆圈部分,内存地址越小放的是整数越高位的数,比如内存地址为0对应整数的12,就是大端对齐。
![](https://i-blog.csdnimg.cn/blog_migrate/59705c3cf6ccf3a1efb30016549a3b80.png)
- 对于数字而言,现在无论是windows还是linux,一般是小端对齐,不过具体是哪种可能需要验证一下
- 对于字符串而言,从左到右,拆分为一个一个的char,每个char对应一个字节,然后该char对应的ASCII值存储在一个BYTE上(大端对齐)
(14)volatile关键字
#include <stdio.h>
int main()
{
//volatile int a = 2;
int a = 2;
a = a + 1;
a = a + 4;
//无volatile也就是正常情况下时C的编译器会优化前两行代码,它们是不合理的 //会编译成a = a + 5
printf("%d\n", a); // 7
//volatile int a=2;
//a=a+1;
//a=a+4;
//加volatile时C编译器不会自作聪明地去优化
//在特定场景下需要加volatile
//也就是每一步都是有用的时候,但合并起来就会出错的情况,比如电路什么
return 0;
}
(15)register关键字与数据计算过程
#include <stdio.h>
int main()
{
int a = 2;
a = a + 1;
return 0;
}
- 先把数据放到内存⾥
- 再从内存⾥取出数据放到寄存器(寄存器是CPU中的基本存储单位)中运算
- 然后再从寄存器中取出数据到内存中
mov b, 0
mov eax, b
add eax 1
mov b, eax
加上register关键字后
int main()
{
//int a=2;
//b这个变量在CPU的寄存器里面,而不在内存中
register int b=2;
b=b+1;
printf("%d\n",b);
return 0;
}
mov eax,0
add eax,1
(16)getchar函数与scanf函数
- getchar是从标准输⼊设备读取⼀个char
- char a = getchar();//从标准输⼊设备读取⼀个char,并赋值给a
- getchar();//可以看做是⼀个暂停,此时按回⻋就继续程序
- scanf("%d",&b);//输⼊⼀个整数,使得b的内存地址存放的数为这个数
- 通过scanf输⼊的时候,最后按的是⼀个什么键?回⻋键,回⻋键scanf会认为是输⼊完成,⽽不是字符串的内容
- scanf认为回⻋和空格都代表输⼊完成
#include <stdio.h>
int main()
{
int a;
printf("int a = ");
scanf("%d",&a); //第二个参数是变量的内存地址,
// 输入什么数,这个变量内存地址就放什么
int b;
printf("int b = ");
scanf("%d",&b);
printf("char x =");
getchar(); //这句话的作用是把回车吃掉,没有这句话回车会给a
char x = getchar();
printf("%d%d%c\n",a,b,x);
return 0;
}
(17)运算符与++和--
- 不要写a+=1,显得不专业,⽤a++或者++a
- a++先返回a的值,然后再⾃加1
- ++a的意思是a先⾃加1,然后再返回a的值
- 如果不能确定,或者要改变默认的优先级,⽤()
(18)强制类型转化
#include <stdio.h>
int main()
{
int a=5;
int b=2;
double f=a/b;
printf("%lf\n",f); //2.000000
/*
两个整数计算的结果也是一个整数
浮点数与整数计算的结果是浮点数,浮点数和浮点数计算的结果还是浮点数
所以上面运算 5/2=2,然后double f=2;所以f=2.000000
*/
double x = (double)a/b;
//(double)a表示强制把a转化为double类型
printf("%f\n",x); //2.500000
return 0;
}