准备开始拾起语法了,规划规划
存储器用来存放数据和程序,构成存储器的材料,目前采用 的是半导体和磁性材料。存储器中最小的存储单位就是一个双稳态半导体电路或者CMOS晶体管或者是磁性材料的存储元,它可存储一个二进制代码,称为比特。8个基本单元存储一个字节,
C的数据类型
基本数据类型
记住计算机实际上使用补码的形式来存储数据,正数的补码是该数的二进制,负数的补码
数据类型 | 字长(字节) | 表示十进制的范围 |
unsigned char | 1 | 0-255 |
char | 1 | -128 - 127或0-255 |
unsigned int | 2 | 0-65535 |
int | 2 & 4 | -32768 ~32767 |
unsigned long | 4 | 0 ~ 2^32 -1 |
short | 2 | -32768~32767 |
unsigned short | 2 | 0~65535 |
long | 4 | -2^31 ~ 2^31 -1 |
float | 4 | 1.2E-38 ~ 3.4E+38 |
double | 8 |
为了得到某个变量在特定平台的准确大小,可以使用sizeof(type)来测量
对于补码重新理解了一遍
模为256,共有256个数,依据8-3=8+(-3)=8 + ( (256-| -3 |)) = 8 + 253=256 + 5 =5 这个原理
但是取值范围为什么是-128 ~127,这个我倒是没明白
1000 0000 ... -128
1000 0001 ... -127
... ... ...
1111 1110 ... -2
1111 1111 ... -1
0000 0000 ... 0
0000 0001 ... 1
0000 0010 ... 2
... ... ...
0111 1110 ... 126
0111 1111 ... 127
对此我总结出可以快速求负数补码的方法,总归就是求这个负数的补数,依据补数求解二进制编码
-1--------->255 1111 1111
-2--------->254 1111 1110
-3--------->253 1111 1101
-4--------->252 1111 1100
... .... ....
字符串
C中的字符串都存储在char类型的数组中,没有字符串这种数据类型
定义中使用双引号引起来的一串字符称作字符串,没有这种数据类型,那么字符串应该怎样存储
说明字符串的结束标志是'/0'6
char arr[6]="abcdef";
char arr[]="abcde";可以自动根据字符串确定字符数组的大小
浮点数
拖延好久的浮点数现在解决它
浮点型数据在内存中如何存放
根据国际标准IEEE 任意一个`二进制浮点数`可以表示成下面的形式(又学习了一招插入数学公式)
浮点数共4个字节,E占8位,M占23位
S表示正负:S为0表示一个正数;S为负表示一个负数
M是有效数字 1 <= M < 2;
E指数位:8位,偏移量为127,我在想为什么是127,浮点数最小值为1.2e -38,转换为二进制,指数值如果为-127,E存储0,迷糊了,歇着
浮点数如何转为二进制
整数转为二进制,采用除2余数。
小数部分:乘2取整数,直至整数部分为0,如果一直循环,取到23位即可
变量
变量的声明有两种意义:
- 一种是需要建立存储空间。列如 int a
- 另一种是不需要建立存储空间,目的是向编译器证明变量的存在,extern a,表示已经定义过的变量a,可以使用extern a的方式在别的文件中使用
初始化变量的方式
- 在声明变量的同时初始化
int x = 10;
2.先声明,后赋值
int x;
x=10;
需要注意的是,变量在使用之前应该被初始化,未初始化的变量的值是未被定义的,那么变量没被初始化的结果应该是什么,下面分析
如果变量没被显式初始化,那么变量的值取决于该变量的类型的其所在的区域。
全局变量和静态变量,默认值为0
局部变量不会自动初始化默认值,变量存储在栈中,可能为任何值。
C的运算符
运算符 | 运算符号 |
算数运算符 | +, -, *, /, %, ++, -- |
关系运算符 | > ,<, >=, <=, !=, =, == |
逻辑运算符 | && ,!, || |
位运算符 | &(按位与), |(按位或), >>, <<, ^(按位异或,相同为0,相异为1) ,~(取反) |
赋值运算符 | +=, -=, *=, /=, %=, >>=, <<=, &=, |= ,^= |
三目运算符 | exp1? exp2:exp3 |
逗号运算符 | exp1,exp2,exp3……expn 表达式的结果为最后一个表达式的值 |
自增自减
i = 3;
j = i++ 先使用i的值在进行 i +1 j = 3 i = 4
j = ++i i 的值先增加再使用 i 的值 j = 4 i = 4
移位运算符
左移运算符(<<):是将一个二进 a 制的操作数按照指定的移动位数向左移动,移出位被丢弃,右边移出的空位一律补0
右移运算符也是如此,注意的是:操作的都是补码
对于负数右移操作时,看清是逻辑右移还是算术右移,算术右移左边补符号位,右边舍弃
下面分别以正数和负数为例,讲解如何移位,
求 5 << 2 之后的值
补码:0000 0101
移位:0001 0100 原码:0001 0100
转为十进制:20
求 -5 << 2之后的值
补码:1111 1011
移位:1110 1100 原码:1001 0100
结果:-20
-5 >> 2
补码:1111 1011
移位:1011 1011
结果:-3
循环语句
while(表达式){ 语句1;} 表达式为真执行语句
do
{
语句1;
}while(表达式)
switch语句
语法结构:
switch(变量表达式)
{
case 常量1:
{
语句1;
}
break;
case 常量2:
{
语句1;
}
break;
case 常量1:
{
语句3;
}
break;
default :语句4;break;
}
case之后的常量表达式不能相同
break和contiune语句
break:语句用于do-while ,for ,while循环中,可使程序跳出循环,永久终止
contiune:停止本次循环,开始新一轮的循环
条件编译
总归就是根据不同的条件编译不同的代码,产生不同目标文件的方法
#if语法结构
①
#if 表达式1
代码1;
#endif
②
#if 表达式1
代码1;
#elif 表达式2
代码2;
#endif
③
#if 表达式1
代码1;
#else
代码2;
#endif
④
#if 表达式1
代码1;
#elif 表达式2
代码2;
#elif 表达式3
代码3;
#else
代码4;
#endif
#if 和#endif是配套使用的,不能省略,并且#else是没有条件的
输入输出函数
后续整理,我发现学起语法是真的枯燥,用到在整理,不行,还是尽快解决
输入函数printf()
代码格式:printf(格式字符串,待打印项1,待打印项1,……)
格式字符串是双引号括起来的内容,包含每个待打印项对应的转换说明
待打印项可以是变量,常量,表达式
注意的是转换说明一定要与待打印项匹配,不匹配的情况后续说明
转换说明
%d —— 以带符号的十进制形式输出整数
%o —— 以无符号的八进制形式输出整数
%x —— 以无符号的十六进制形式输出整数
%u —— 以无符号的十进制形式输出整数
%c —— 以字符形式输出单个字符
%s —— 输出字符串
%f —— 以小数点形式输出单、双精度实数
%e —— 以标准指数形式输出单、双精度实数
%g —— 选用输出宽度较小的格式输出实数
%d int 有符号10进制整数
%u unsigned int 无符号10进制整数
%h 短型前缀,后接d(10进制整形)、x(16进制整形)、o(8进制整形)
%hd short 有符号10进制短整形
%hu unsigned short 无符号10进制短整形
%ld long
%lu unsigned long
%lld long long
%llu unsigned long long
#include <stdio.h>
int main()
{
char ch = 'A';
char str[20] = "www.runoob.com";
float flt = 10.234;
int no = 150;
double dbl = 20.123456;
printf("字符为 %c \n", ch); //A
printf("'A'转为整数为 %d \n", ch); //65
printf("字符为 %f \n", ch); //这种为啥编译器不报错 0.00000
printf("字符串为 %s \n" , str); // www.runoob.com
printf("浮点数为 %f \n", flt); // 10.234000
printf("10.234转为整数 %d \n", flt); // -536870912
printf("整数为 %d\n" , no); //150
printf("150转为浮点数 %f\n" , no); // 10.233994
printf("双精度值为 %lf \n", dbl); //20.123456
printf("八进制值为 %o \n", no); //226
printf("十六进制值为 %x \n", no); //96
return 0;
}
令我好奇的是,对应的转换格式不对,编译器为什么不报错,而且那些错误的数据是从哪来的。下面说明转换不匹配的情况
转换不匹配
#include <stdio.h>
#define PAGES 336
#define words 65618
int main(void)
{
short num=PAGES;
short mnum=-PAGES;
printf("%hd %hu\n",num,num); //336 336
printf("%hd %hu\n",mnum,mnum); //-336 65200
printf("%d %c\n",num,num); //336 P
printf("%d %hd %c\n",words,words,words); //65618 82 R
return 0;
}
%hd----->short
%hu----->unsigned short
为什么-336转为%hu变成了65200
-336在计算机存储的形式是-336的补码65200,对于%hd的格式自然是转换成原码-336输出,鉴于负数的存储形式,0~32767代表他们本身,32678~65536表示负数,被解释成有符号是65200代表-336,被解释成无符号时,65200代表65200
336转成%c为什么变成了P?
首先明白336属于short类型,两个字节,存储形式:0000 0001 0101 0000
char是1个字节,当使用printf()打印336时,它只会查看后面那个字节0101 0000
相当于一个整数余256,余数是80,对应字符P
65618转成short类型变成了82
转成更小的存储单位,使用printf()打印的时候只查看后两个字节,相当于65618余模65536
余82,如果余数范围在32678~65536,考虑是unsigned short 还是short ,short在这个范围一定是负数,unsigned short是数据本身
输入函数scanf()
输入数字可以使用空白(换行符,制表符,空格)将需要输入的部分分成多个字段,
1. 使用%s的转换说明,scanf()会读取除空白以外的所有字符。scanf()跳过空白读取第一个非空白字符,保存非空白字符直到再次遇到空白。
#include <stdio.h>
int main() {
char name[40];
scanf("%s",name);
printf("%s \n",name);
}
2.scanf()允许吧普通字符放在格式字符串中,除空格外的普通字符必须与输入字符严格匹配,
getchar()和putchar()
每次只处理一个字符,可以使用这个函数输入字符串,只有遇到空格才会停止输入
条件判断语句
if-else必有一个执行
if-else if -else
注意:
if条件满足,不再执行后面条件。if语句不满足时执行else if 以及else语句
一旦某个 else if 匹配成功,其他的 else if 或 else 将不会被测试
if 以及else if条件都不满足时,执行else语句
#include <stdio.h>
int main()
{
int a = 9;
if (a<10)
{
printf("a 的值小于10\n");
}
else if (a<20)
{
printf("a 的值小于20\n");
}
else if (a<30)
{
printf("a 的值小于30\n");
}
else
{
printf("没有匹配的值\n");
}
return 0;
}
枚举类型
我理解的枚举是使用enum把一系列的整型常量变成一组新的数据类型。枚举类型的目的旨在于提高程序的可读性和可维护性
//语法结构
enum spectrum{red,orange,yellow,green,blue,violet};
enum spectrum color=red;
可以看出,声明枚举类型变量的方法:enum 标记名 变量名 ;
可以将enum spectrum当做一个数据类型来使用,像red,orange这些常量被称做为枚举符。
注意的是,虽然枚举符是int类型,但是枚举变量color可以使任意的整数类型。spectrum的枚举范围是0~5,那么枚举变量也可以使用unsigned char ,我的理解是只要能够存储0~5的整数类型的数据都可以存储枚举常量
枚举常量赋值
1.在声明枚举中,可以为枚举常量指定整数值,
enum levels{
low=100,
medium=500,
high=200
} ;
int main()
{
// enum spectrum color=red;
int class = low; //这条为什么在编译器通不过
printf("%d \n",low);
return 0;
}
2.如果只给一个枚举常量赋值,没有对后面的进行赋值,那么后面的常量会依次增加
enum levels{
low,
medium=500,
high,
higher
} ;
int main()
{
// enum spectrum color=red;
// int class = low;
printf("%d \n",low); //0
printf("%d \n",medium); //500
printf("%d \n",high); //501
printf("%d \n",higher); //502
return 0;
}
3.在默认情况下,枚举列表中的值从0开始被赋值
enum levels{
low,
medium,
high,
higher
} ;
int main()
{
// enum spectrum color=red;
// int class = low;
printf("%d \n",low);
printf("%d \n",medium);
printf("%d \n",high);
printf("%d \n",higher);
return 0;
}
但是实际的应用场景是什么? 将枚举和数组结合
1.方便调取数组数据。当做数组的下标,数组很大时,可以根据枚举常量寻找需要的数值,
并且可以根据最后一个枚举常量定义数组的大小
2.主要功能是增强代码的可读性。建议读一读STM32的标准库的代码,
指针理解
首先明白数据在内存中是怎样存储的
- 栈区(stack)由编译器自动分配释放,利用栈存储一些临时变量,包括函数形参,函数内部局部变量,返回值等。栈的操作遵循"后进先出"
- 堆区(Heap)由程序员手动分配、释放的存储区,堆在内存中位于bss区和栈区之间,通过调用函数malloc()、calloc()、或者realloc()来从堆中分配内存。之后必须手动调用free()函数来释放堆内存,否则导致内存泄漏
- 静态存储区:内存分配在程序编译之前完成,在程序的整个运行期间都存在。存储全局变量和静态变量。还可以细分为.data 区和.bss区 。
- 常量存储区:通常位于静态存储区内,通常用来存储常量数据,字符串常量,cons修饰的变量
- 程序存储区:存放程序代码,该区域的大小在程序运行前就已经确定,该区域只读
栈溢出是怎么一回事
ebp寄存器指向栈底,esp寄存器指向栈顶,
栈内存的大小与编译器有关,在VC、VS下,默认1M,当然也可通过修改编译器参数修改栈内存大小。
地址-------指针
通过地址能访问内存中的数据,这个地址就是指针。
指针类型难道和指针所指向的类型不是一个道理吗?
难道不是内容的类型决定了指针的类型?
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
(1)int*ptr; //指针所指向的类型是int
(2)char*ptr; //指针所指向的的类型是char
(3)int**ptr; //指针所指向的的类型是int*
(4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
(5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
指针的值指针存储的值被编译器当做地址,在32位程序中,所有类型的指针的值都是一个32位整数,所以指针本身的大小为4个字节,
数组
声明数组
通过声明数组告诉编译器数组中含多少元素和这些元素的类型。声明数组时只能在方括号使用整型常量表达式,就是由整型常量构成的表达式。
★★★错误声明法
int a1[-4]; 数组大小必须大于0
int a2[0]; 数组大小必须大于0
//宏定义变量才能使用这种方法
int n=6;
int a3[n]={0}; //error: variable-size type declared outside of any function
初始化数组
如果不初始化数组,数组元素和未初始化的变量一样,其中存储的都是垃圾值,所以在使用数组元素之前,必须给他们赋初值。
♪♫★★★声明数组的同时初始化数组
int a[4]={1,2,3,4};
初始化列表的项数应与数组大小一致,如果不一致会出现什么情况,一 一列举
初始化列表的值小于数组大小时,编译器会把剩余部分的元素初始化为0;如果初始化列表的项数多于数组的大小,编译器会报错
★★★可以忽略数组大小,编译器会根据初始化列表的项数来确定数组大小。前提是,数组必须初始化
int a[]={1,2,3,4};
有个问题,能改变变量的地址吗,我觉得是编译器分配好的,应该不能吧
数组的边界
为什么数组越界编译器不报错。编译器在运行时添加额外代码检查数组下标会降低程序的运行速度,没给编译器添加这个功能。越界的数组下标会改变其他变量的值。
指针和多维数组
int zippo[4][2] ;多维数组声明,内含int数组的数组
zippo是这个内含两个int值的数组的地址,zippo[0]是占用一个int大小的地址,解引用指针,*zippo[0]=zippo[0][0] *zippo=zippo[0]
声明一个指针变量指向二维数组
int (*pz)[2];
以上代码声明一个指向数组的指针,该数组内含两个int ,[]的优先级大于`*`所以需要使用圆括号,