C语法补漏

准备开始拾起语法了,规划规划

存储器用来存放数据和程序,构成存储器的材料,目前采用 的是半导体和磁性材料。存储器中最小的存储单位就是一个双稳态半导体电路或者CMOS晶体管或者是磁性材料的存储元,它可存储一个二进制代码,称为比特。8个基本单元存储一个字节,

C的数据类型

基本数据类型

记住计算机实际上使用补码的形式来存储数据,正数的补码是该数的二进制,负数的补码

数据类型字长(字节)表示十进制的范围
unsigned  char10-255
char1-128 - 127或0-255
unsigned int20-65535
int2 & 4

-32768 ~32767

unsigned long40 ~ 2^32 -1
short2-32768~32767
unsigned short20~65535
long4-2^31 ~  2^31 -1
float41.2E-38 ~ 3.4E+38
double8

为了得到某个变量在特定平台的准确大小,可以使用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 任意一个`二进制浮点数`可以表示成下面的形式(又学习了一招插入数学公式)

                                                               $(-1)^{_{S}} * M * 2^{_{E}}$

浮点数共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的方式在别的文件中使用

初始化变量的方式

  1. 在声明变量的同时初始化

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 ,[]的优先级大于`*`所以需要使用圆括号,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值