C语言的归总学习1

1 计算机的组成


其中总线按功能和规范可分为五大类型:
1.1 数据总线:在CPU与RAM之间来回传送需要处理或是需要储存的数据。
1.2 地址总线:用来指定在RAM之中储存的数据的地址
PS:地址总线的位数不代表CPU位数
1.3 控制总线:将微处理器控制单元的信号,传送到周边设备。
1.4 扩展总线:外部设备和计算机主机进行数据通信的总线,例如ISA总线,PCI总线。
1.5 局部总线:取代更高速数据传输的扩展总线。
PS:其中的数据总线DB、地址总线AB和控制总线CB也统称为系统总线。

2 软件

2.1 软件是一系列按照特定顺序组织的计算机数据和指令的集合!(简单的说软件是程序加文档的集合体。)
2.2 一般软件分为系统软件、应用软件以及介于两者之间的中间件
(中间件?)流、系统调用(API)、标准函数库、ABI

3 文件

3.1 文件是一个外存(硬盘,U盘,网盘)的概念。
3.2 文件由文件名和文件主体两部分构成。
3.3 文件分为可执行文件(指令和数据构成)和不可执行文件(只由数据构成)
举例:在C/C++语言中 .c/cpp 源文件(文本),.h头文件(文本),.i 预编译文件(文本),.s 汇编文件 .o 或.obj 二进制可重定位目标文件都是不可执行文件。而*.exe 是可执行文件。

4 C语言的编译链接过程

首先进行预处理,转换为预编译文件,.i文件,然后编译器进行语法分析,也就是要把那些字符串分离出来。
然后进行语义分析,就是把各个由语法分析分析出的语法单元的意义搞清楚。
最后生成的是目标文件,也称为obj文件。
再经过链接器的链接就可以生成最后的EXE文件了。
有些时候需要把多个文件产生的目标文件进行链接,产生最后的代码。这一过程称为交叉链接。
在这里插入图片描述
程序是文件,而文件又是一种存储概念。

5 进制及其转换

5.1 二进制数、八进制数、十六进制数转换为十进制数的规律是相同的。
把二进制数(八进制或十六进制数))按位权形式展开多项式和的形式,求其最后的和,就是其对应的十进制数——简称“按权求和”。(当二进制.后面的数字 2^-1 …,这也是float与double是近似值的原因)
5.2 十进制转其他进制(可利用贪心算法思想)
PS (103)10 -> ( )2
128 64 32 16 8 4 2 1
103 < 128 0
103 > 64 1
103-64=39 > 32 1
39-32=7 < 16 0
7 < 8 0
7 > 4 1
7-4=3 > 2 1
3-2=1 1
即为 01100111

m位二进制的逻辑左右移 << n >> n
左移:将低位m-n位数左移n位,高位丢弃(存在了进位 标记位),然后低位填零
右移():将高位移动到相应位置,然后用零填充
算术右移(需要保存符号位):(区分符号位)如果符号位为0则与逻辑右移一样,如果符号位为1,则高位移动后用1填充

补码与原码

0XFF,计算机存储方式是以二进制的补码存储的,所以0XFF存到计算机里面为(16进制没有符号位,即就是补码):1111 1111
而我们输出的时候是以十进制输出,那么最高位就代表符号位:
那么我们就需要求出它的原码(即补码的补码):1 000 0001
输出结果为:-1。
补码:正数的补码与原码相同。负数的补码,将其原码除了符号位外所有位取反后加1;
此外补码求源码可以将第1位当做-,按位权形式展开多项式和的形式,求其最后的和
1 0 1 1 0
-16 4 2 =-10

补码又可以表示为有符号、无符号

补码转为无符号 8位
-1->127
当补码溢出时如127+1->-128
在这里插入图片描述
T_MIN=-T_MIN
有符号扩展
1 0 1 1 0 -16+4+2= -10 当其位数扩大时
1 1 0 1 1 0 -32+16+4+2=-10
但当缩小时,其值可能不变,但可能从负数变成了正数,从正数变成了负数
1 0 0 1 1 0 -32 +4+2=-26
0 0 1 1 0 4+2= 6 当其位数缩小时

无符号溢出 (u+v)%2^w (求模->保留位数)

w位 0-15
13+5=2;
u   1 1 0 1    	13
v   0 1 0 1      5
————
1 0  0 1 1      2

有符号位,可能出现正溢出–>负数和负溢出–>正数(截断)

6 C语言

6.1 C语言是结构化语言
6.2 C语言的两大特性
1、强类型(编译时刻任何对象所属的类型必须确定)
数据会根据所声明的类型进行转换,sizeof是编译时就求算变量类型大小,++是在运行时才进行
2、面向过程,模块化思想

7 C源程序的结构特点

1.一个C语言源程序可以由一个或多个源文件组成。
2.每个源文件可由一个或多个函数组成。
3.一个源程序不论由多少个文件组成,都有一个且只能有一个main 函数,即主函数。
4.源程序中可以有预处理命令(include 命令仅为其中的一种),预处理命令通常应放在源文件或源程序的最前面。
5.每一个说明,每一个语句都必须以分号结尾。
6.标识符,关键字之间必须至少加一个空格以示间隔。若已有明显的间隔符,也可不再加空格来间隔。

8 C语言数据类型大小

sizeof 计算变量或数据类型所占的字节个数
在32 位的系统上
short 占据的内存大小是2 个byte;
int 占据的内存大小是4 个byte;
long 占据的内存大小是4 个byte;
float 占据的内存大小是4 个byte;
double 占据的内存大小是8 个byte;
char 占据的内存大小是1 个byte。
bool 占据的内存大小是1 个byte。

void 不能定义变量,但可以定义指针

#include<stdio.h>
// sizeof关键字计算变量或数据类型所占的字节个数
int main()
{
	printf("char size:%d \n", sizeof(char));
	printf("short size:%d \n", sizeof(short));
	printf("int size:%d \n", sizeof(int));
	printf("long int size:%d \n", sizeof(long));
	printf("long long size:%d \n", sizeof(long long));
	printf("float size:%d \n", sizeof(float));
	printf("double size:%d \n", sizeof(double));
	printf("long double size: %d \n", sizeof(long double));
	printf("bool size:%d \n", sizeof(bool));
	return 0;
}

在这里插入图片描述

9 变量 常量 标识符

  1. 变量是以某标识符为名字,其数值可以改变(可读,可写)。
    PS {何读(获取,可取值),可写(赋值)}
    变量中分为局部变量和全局变量,若发生局部变量和全局变量同名时,局部优先原则。
  2. 常量其值不可改变(只可读,不可写)。
  3. 常量值编译时即确定,所以能进常量的集合进行排序,和跳转表(switch)等

10 定义 声明

10.1 定义:为这个变量分配一块内存并为给其取一个名字(即变量名)
PS 这个变量名和所分配的内存“同生共死”,且该内存的位置不能被改变。
另外,一个变量在一定区域内(函数,全局等)只能被定义一次
变量定义形式:
存储类别 数据类型 变量表列;
PS:外部数据(函数之外)定义只能用extern 或static,而不能用auto或register。
函数定义形式:
存储类别 数据类型 函数名(形参表列);
函数体

PS:函数的存储类别只能用extern或static。函数体是用花括号括起来的,可包括数据定义和语句。

PS:存储类别可用((如不指定存储类别,作auto处理)
)
auto
static
register
为提高程序运行效率,可以将某些变量保存在寄存器中,即说明为寄存器变量,但不提倡使用。写入寄存器的值可能成为脏值(变量值改变,但保存在寄存器的值没有改变)
extern

10.2 声明:有两重含义,如下:
1、告诉编译器,该变量名已经匹配到一块内存上了。 extern
2、告诉编译器,这个名字我先预定了,别的地方再也不能用它来作为变量名。?(比如函数声明吗?)
PS 声明与定义最重要的区别:定义创建了对象并为这个对象分配一块内存,而声明的时候没有分配内存空间

11 变量的初始化

可以在定义时对变量或数组指定初始值。
静态变量或外部变量如未初始化,系统自动使其初值为零(对数值型变量,其存在.bss区)或空(对字符型数据)。对自动变量或寄存器变量,若未初始化,则其初值为一不可预测的数据。

12 常量

C语言的常量分为
1、字面常量
分为 int a=10 (十进制)
int b=010 (八进制)
int c=0x10 (十六进制)
int d=0b10100101 (二进制)VS 2012不支持
2、 用#define 定于的宏变量 预编译时替换
3、用const关键字修饰的变量,称为常变量 不能改变变量的内容
4、枚举常量
ps:寄存器的值是不可写的
5、字符常量和字符串常量
单引号是字符的定界符。
用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译
器采用的字符集中的序列值
。因此,对于采用ASCII字符集的编译器而言,‘a’的含义与0141 (八进制)或者97 (十进制)严格一致。
双引号是字符串的定界符。
用双引号引起的字符串,代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为零的字符’\0’初始化。
cout << sizeof(“12323dsds23”); //字符串长度+1
在这里插入图片描述
ps:
switch后面的“表达式”,可以是int、char和枚举型中的一种
case后面必须是“常量表达式”,表达式中不能包含变量
数组的声明大小也是“常量表达式”(执行期不需要计算的表达式)

常见的ASICII值

'a'		// 97
'A'		// 65
'\0'	// 0
'0'		// 48

&a+1 ---指针  ------变量
0x00001010 ------常量

12 转义字符

转义字符以反斜线""开头,其具有特定的含义,不同于字符原有的意义,故称为“转义”字符
使用转义字符时需要注意下问题:

  1. 转义字符只能使用小写字母,否则转义字符被忽略;如’\N’‘N’。
  2. \v垂直制表和\f 换页符对屏幕没有任何影响,只会影响打印机执行响应操作。
  3. \t光标向前移动四格或八格编译器里设置
  4. '字符里(即单引号里)使用字符串里(即双引号里)需要只要用′即可
  5. ?其实就是?(没有转义字符也一样,在vs2012测试过)
    在这里插入图片描述
    在这里插入图片描述
    示例
#include<stdio.h>
#include<string.h>
int main()
{
	char str[20] = { "tulun\103hello" };//"\103"---"C"
	int slen = strlen(str);
	printf("slen = %d \n",slen); printf("%s \n",str);

	return 0;
}

在这里插入图片描述
即转义字符既可以用于单个字符,也可以用于字符串,并且一个字符串中可以同时使用八进制形式和十六进制形式.
转义字符的初衷是用于ASCII 编码,所以它的取值范围有限:
1、八进制形式的转义字符最多后跟三个数字,也即\ddd最大取值是\1772.
2、十六进制形式的转义字符最多后跟两个数字,也即\xdd最大取值是\x7f
单引号、双引号、反斜杠是特殊的字符,不能直接表示:
1.单引号是字符类型的开头和结尾,要使用\’也即’’’
2.双引号是字符串的开头和结尾,要使用\”表示,也即“abc\”123\” tulun"
3.反斜杠是转义字符的开头,要使用\表示,也即’\I’或者“tulun\1024"

13 C结构

  1. 循环结构:当条件成立时,重复执行某些语句。
    但是凡是次数确定、范围确定的情况,使用for循环,for循环当中定义的初始化变量,只有自己才能用
    while循环一般次数不确定,范围不确定时使用
    do … while循环类似于while循环,不同的是一个do … while循环是保证至少执行一次。
  2. 顺序结构:按照语句出现的先后顺序依次执行;
  3. 选择结构:根据条件判断是否执行相关语句;
	if()
	else if()
	else() //只会执行一条条件

14 函数

函数由返回类型,函数名(函数名可以看成是函数指针),函数体构成。
函数的参数一般情况下是形参与实参的结合,即栈的调动形式,一般由从右向左压入。
通过函数,可以把一个复杂任务分解成为若干个易于解决的小任务。充分体现结构化程序设计由粗到精,逐步田化的设计思想。(分模块)

调用函数之前需要查看函数是否函数声明和定义,然后进行函数实参解参(实参与形参是否匹配,然后查看类型是否匹配(不匹配是否可以类型转换)),然后查看返回类型(知道返回多大,是否产生临时量)
函数调用(分配栈帧(栈的划分),每个函数有自己的一个栈帧,且栈反复利用(当被调函数结束回收,新的函数被调用又给其分配))
与其他程序设计语言不同,C语言要求:在函数调用时即使函数不带参数,也应该包括参数列表。因此,如果f是一个函数,
f ();
是一个函数调用语句,而
f;
却是一个什么也不做的语句。更精确地说,这个语句计算函数f的地址,却并不调用该函数。

函数调用过程
1.实参压栈 C/C++压入顺序从右向左(由于有可能存在可变参数) 形参从栈顶开辟内存(由主调函数开辟,回退时还是由主调方回退)并初始化 如Data sum(Data a,Data b );//相当于 Data sum(void *,Data,Data); void * 是压入的临时量的地址,是在返回值类型大于8字节时,编译器改写的函数
2.将PC所指向的下一行地址压入
3.Call 将跳转到函数位置(偏移量)

函数回退过程
1.寄存器(程序不能去写寄存器的值,所以该值具有了常性)保存函数的返回值(不产生临时变量,而当返回类型特别大的时候,会将返回值内容赋值到调用时分配的临时量内存中)
2.push esp栈底 使栈底指针指向主调动函数的栈底
3.ret push 地址交给PC寄存器,然后根据PC跳转到原函数
4.将返回值(寄存器or临时量的值赋值),且该临时量生存期结束
5.回退形参的内存。

对于函数名func来说,不管是*func还是func还是&func,编译器都认为他是函数指针,一般情况下你无法得知函数指针的地址。

15 可见性(作用域)和生存期

**作用域(可见性)**指标识符能够被使用的范围;只有在作用域内标识符才有效,任何标识符作用域的起始点均为标识符说明处。
此阶段针对编译和链接过程。
1)局部域,局部域包括块域和函数原型域。
当函数域内定义的变量名称与块内定义的变量名称同名时,编译器的原则是局部优先原则。
函数中定义的标识符,包括形参和函数体中定义的局部变量,作用域都在该函数内,也称作函数域.
2)文件作用域也称全局作用域。定义在所有函数之外的标识符,也称为外部对象,具有文件作用域,作用域为从定义处至整个源文件结束。文件中定义的全局变量和函数都具有文件作用域。
如果块内定义的局部变量与全局变量同名,块内仍然局部变量优先,但与块作用域不同的是,在块内可以通过域运算符“::”访问同名的全局变量。
可见性从另一个角度说明标识符的有效性。标识符的作用域包含可见范围,可见范围不会超过作用域。可见性在理解同名标识符的作用域嵌套时十分直观。对于外层块与内层块定义了同名标识符的,在外层作用域中,内层所定义的标识符是不可见的,即外层引用的是外层所定义的标识符;同样,在内层作用域中,外层的标识符将被内层的同名标识符屏蔽,变得不可见,即外层中同名标识符的可见范围为作用域中挖去内层块的范围。

生存期(生命期)此阶段针对程序的执行过程–进程(分配了CPU资源,内存资源)。
生命期指的是变量生命期指从获得空间到空间释放之间的时期(程序的执行过程)。
1)局部变量的生存期是:函数被调用,分配存储空间,到函数执行结束,存储空间释放。存储.stack区
2)全局变量的生存期是:从程序行前开始,到执行后结束。存储在.data区(在程序运行前就存在obj文件的.data区,只是还没有映射)
ps:全局和局部很重要。
3)动态生命期是:标识符由特定的函数调用或运算来创建和释放,如调用malloc()为变量分配存储空间,变量的生命期开始,而调用free()释放空间或程序结束时,变量生命期结束。具有动态生命期的变量存储在堆区.heap。
ps:修饰函数里的局部变量时其定义是在函数内定义的。它的作用域在是函数内的,其生命期在整个文件。

下面程序能编译通过吗?

void fun()
{
	int x = 10;
	g_max = x + g_max;//编译提醒未声明
}
int g_max = 10;
int main()
{
	int a = 20;
	a = g_max;
	
	return 0;

}

在这里插入图片描述
不能编译通过,假如编译通过,执行时在进入main()之前先将全局变量放在数据区,但都不能通过,因为g_max的作用域是从定义到结束

ps:sizeof() 编译时计算出来 ++运行时才进行

16 数组

数组是包含给定类型的一组数据,即一组相同类型元素的集合。
1.C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来,因为要为他确定内存大小。然而,C语言中数组的元素可以是任何类型的对象,当然也可以是另外一个数组。这样,要“仿真”出一个多维数组就不是一件难事。
2.对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针。其他有关数组的操作,哪怕它们乍看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为(数组的退化(如果不退化,作为形参等需要产生一个极大的空间,而且数据传输也需要时间),节省了时间和空间,但丢失了元素的个数,且不对数组的边界进行检测)。
3.数组的声明 类型 数组名 [大小] 类型指定了元素的类型,大小指定了又多少个元素。

int a[3]={0};//从右向左解释 数组有3个元素,元素类型为整形
int b[3];
b=a;//错误写法,因为b是数组名,是const的
a++;//错误
a+1;//正确,+操作不会回写
char stra[8] = { "tulun" };//将"tulun"被拷贝放在了栈区 通过str下标可以修改字符串的值,且后面的空间用'\0'填充
char strb[8]={'t','l'};//可以用strlen(),该数组后用‘\0’ 填充
char strc[]={'t','l'};//不可用strlen()
char n = "tulun"[2]; //n=l;

这个声明的含义是:a是一个拥有三个整型元素的数组

const int a[3];//从右向左解释 数组有3个元素,元素类型为const整形,即数组里的元素是常变量


int (*a) [3]; //即int (*a)[3] *a是具有三个整形元素的数组,即a是指向有三个整形元素的数组指针 //只分配了指针a的空间   指针数组  对a解引用可能有NULL的可能    
int * (a[3]); // a是有着三个元素的数组,其元素类型是int *  //分配了数组空间  数组指针  对a解引用不会出现问题,而对**a在可能出现NULL
struct {
			int p[4];
			double x;
			}b[17];
			

这个声明的含义是:b是一个拥有17个元素的数组,其中每一个元素都是一个结构,该结构中包括了一个拥有4个整型元素的数组和一个双精度类型的变量

int calendar[12][31];//[]从左向右  
//calendar[12] 是具有31个元素的数组,其元素类型为int。calendar是具有12个元素的数组,其元素类型是int [31],即数组类型
//所以可以进过typedef进行处理 typedef int zip_dig [31]; //这也是不能舍弃j列元素的原因,因为其是类型的一部分。
//zip_dig cal[12];

这个语句声明了calendar是一个数组,该数组拥有12个数组类型的元素,其中每个元素都是一个拥有31个整型元素的数组
所以sizeof(calendar)=3112sizeof(int)
如果calendar 不是用于sizeof的操作数,而是用于其他的场合,那么calendar总是被转换成一个指向calendar 数组的起始元素的指针。
如果一个指针指向的是数组中的一个元素,那么我们只要给这个指针加1,就能够得到指向该数组中下一个元素的指针。同样地,如果我们给这个指针减1,得到就是指向该数组中前一个元素的指针。对于除1之外其他整数的情形,以此类推
如果两个指针指向的是同一个数组中的元素,我们可以把这两个指针相减。这样做是有意义的,例如:
int *q - p + i;(指针相减得到的是元素个数)
那么我们可以通过q-p而得到i的值。值得注意的是,如果p与q指向的不是同一个数组中的元素,即使它们所指向的地址在内存中的位置正好间隔一个数组元素的整数倍,所得的结果仍然是无法保证其正确性的。
尽管我们也可以完全依据指针编写操纵一维数组的程序,这样做在一维情形下并不困难,但是对于二维数组从记法上的便利性来说采用下标形式就几乎是不可替代的了。还有,如果我们仅仅使用指针来操纵二维数组,我们将不得不与C语言中最为“晦暗不明”的部分打交道,并常常遭遇潜伏着的编译器bug。
ps:

  1. 数组一般被用作查表(哈希表),对于固定的数,比如月份里的天数等可以利用数组进行处理
  2. 数组的相互赋值可通过memcpy函数,也可通过结构体来赋值。

二维数组

int calendar[12][31];

calendar[2]的含义是什么?是数组的第三个数组元素,它是拥有31个整型元素的数组,因此,calendar[2]的行为也就表现为一个有着31个整型元素的数组的行为。例如, sizeof(calendar[4])的结果是31与sizeof(int)的乘积。又如,

int *p=calendar[2];//calendar[2]是一个拥有31个整型元素的数组

p指向了calendar数组的第二个元素?错的
p是指向了calendar[2]中下标为0的元素
因为calendar[2]是一个数组,所以我们可以通过下标的形式来指定这个数组中的元素,就像下面这样,

i = calendar [2] [7];

用指针表示

i = **(calendar+2+7
int *p=calendar;//错误
int **p=calendar[12][31];//错误
//无法从int(*)[31] 转换为 int **  (类型不同,其存储方式不同) 二维数组需要数组指针来指向,它还保存着指向数组的元素个数,而二级指针指向数组将元素全丢了,就像声明时为什么二维数组不能省略列一样,行代表了元素个数(可以计算出来),列代表了元素里拥有的个数.

因为calendar是一个二维数组,即数组的数组,在此处上下文将其转换为一个指向数组的指针(calendar[0]是一个一维数组,即calendar是指向数组的指针),而p 是一个指向整型的指针,这个语句试图将一种类型的指针赋值给另一种类型的指针,所以是非法的。
类型不同,其存储方式不同

int (*ap)[31]=calendar;//*ap是一个具有31个整型元素的数组,二维数组的列不退化,即数组里包含数组,数组元素不进行退化
//之所以用指针数组而不是用二维数组的原因是使用指针数组更加节省空间,而使用二维数组,则无论程序里传递几个数组都必须消耗31*12个int的空间

声明了*ap是一个具有31个整型元素的数组,因此ap是指向具有31个元素的数组的指针,因此我们可以这样写:

int calendar[12][31];
int*monthp)[31];
monthp=calendar;

这样monthp将指向数组calendar的第一个元素,也就是数组calendar的12个有着31个元素的数组类型元素之一。
讨论这么多,只是为了揭示C语言中数组与指针之间的独特关系,使用时二维数组利用下标形式是及其便利的。

对于数组名作为参数传入函数,数组名被转换为了指针,对其数组进行操作a[i] 等同于*(a+i),即若是相互替换,函数结束时是可以的。

void month(int year)
{
	static const int calendar[13]={28,31,29,31,30}//static被放入了.data区延长了该数组的生命期,即该函数被多次调用时,该数组不需要被重新分配空间
}
int ar[5];//&ar; 数组的数组的地址,即&ar是类型是数组类型,+1即解析的类型大小是ar[5]的空间
//int ar[1][5] 
//int (*p)[31] *p是一个整形数组,p是指向数组的指针
int main()
{
	int ar[5][2]={1,2,3,4,5,6,7,8,9,10};
	int (*s)[2]=&ar[1];// &*(ar+1)  ar+1
	int *p=ar[1];//p指向ar+1,即指向3
	cout<<p[3]<<endl;// *(p+3)  6
	cout<<s[1][3]<<endl;//由于数组空间是连续的 (*(s+1)+3)   (*(ar+2)+3)  8
} 

静态数组结构:
在这里插入图片描述
ps:ar是数组首元素的地址,&ar代表数组的地址,两者指向一样,但二者特点不同

动态开辟的二维数组:

ps:动态开辟的数据结构与静态开辟的数据结构不同。

#include<iostream>
#include<stdlib.h>
using namespace std;
#define row 3
#define col 4
int main()
{

	int **p = (int **)malloc(sizeof(int *)*row);//先构造一个数组空间,长度为row,元素类型为int *
	memset(p,0,sizeof(int *)*row);//虽然p是二级指针,但memset参数类型为void * 可以接受,且在函数内部void *p转换为(char *)p刚好能处理一级空间
	for (int i = 0;i < row;i++)
	{
		p[i] = (int *)malloc(sizeof(int)*col);//再在p指向的数组空间内的每一个元素开辟空间指向一个数组,该数组长度为col
			memset(p[i],0,sizeof(int *)*row);
	}
	return 0;
}

动态数组结构:在这里插入图片描述

17 C语言中的左值和右值

左值(Ivalue)和右值(rvalue)按字面意思通俗地说。以赋值符号=为界,=左边的就是左值,=右边就是右值。
更深一层,可以将L-value 的L,理解成Location,表示可寻址。
Avalue (computerscience)that has an address 。
R-value中的R指的是Read,表示可读。
in computer science, a value that does not have an addressin a computer language.

18 取模运算%

余数的符号位取决于取模运算前面的数的符号位
在C语言中的应用
1、判断是否能够整除某个数;
2、判别奇偶数,判别质数(或素数); 将n从2开始求余直到n-1
3、计算范围。形成循环。 x%10 取一个数的个位 x%100 取一个数的十位和个位 x/10 剔除个位
4、求最大公约数;求最大公约数最常见的方法是欧几里德算法(又称辗转相除法),
其计算原理依赖于定理: gcd(a,b) = gcd(b,a mod b) 余数为0时 其除数为最大公倍数

19 指针

任何指针都是指向某种类型的变量,void 不能定义变量,但可以定义指针变量。特别之处在于void 指针可以指向任意类型变量的地址(泛型指针),如果要将void指针vp赋给其他类型的指针,则需要强制类型转换。
计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样。为了正确地访问这些数据,必须为每个字节都编上号码,就像门牌号一样,每个字节的编号是唯一的,根据编号可以准确地找到某个字节。我们将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从0开始依次增加,对于32位环境,程序能够使用的内存为4GB。
最小的地址为0x0000 0000,最大的地址为0XFFFF FFFF。
内存(在程序中称为主存DRAM)是计算机中重要的部件之一,它是外存(硬盘)与CPU进行沟通的桥梁。
计算机中所有程序的运行都是在内存中进行,为了有效的使用内存,就把内存以8位二进制(bit)划分为存储单元(也就是1字节)。为了有效的访问到内存的每个存储单元,就给内存存储单元进行了编号,这些编号被称为该内存存储单元的地址
printf 打印地址

#include <stdio.h>
int main()
{
	int a = 10;
	printf("1 ==> Ox%08x \n ",&a);
	printf( "2 ==> 0X%08X \n ",&a);
	printf("3 ==> %p \n ",&a);
	printf( "4 ==> %#p \n ",&a);
	return 0;
}

在这里插入图片描述
存储地址的变量称为指针变量。在c语言中指针就是地址。

int main(){
	int *ip = NULL;//指针变量,用来存储地址
	int const *p=NULL;//正确
	int *const p=NULL;
	int a = 10;
	ip = &a;
	*ip = 100;
	return 0}

```![在这里插入图片描述](https://img-blog.csdnimg.cn/20201018115121595.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d1Z3Vpbmlhbmppbmc=,size_16,color_FFFFFF,t_70#pic_center)
PS :当直接进行修改a值时,CPU只对内存进行了一次操作,而通过*pi进行修改,CPU需要对内存进行两次操作。首先根据ip地址取ip的内容(即a的地址),再由a的地址找到a的内容进行修改

&p和p,p是存的是指向操作空间地址  &p是指向保存自身空间地址
**使用指针变量是首先要明确指针变量自身的值(存储的是地址),再明确指针变量所指的实体(解引用)**

```c
int a=10int *p=&a;

*p=a;//*p操作的是变量a,而不是10
&*p=&a;//&(*p)=&a   

指针的运用
1.作为参数传递修改参数值(指针一般作为参数值都需要进行判空处理,而对于传入的是两个指针,对其比较,传入空指针可以不进行判空
由于值传递,修改的值是主调方为被调方开辟的形参值(与实参内存无关),而当函数结束回到主调方会被释放,所以无法改变传入实参的值(函数与函数之间没有耦合性,不会相互干扰),而通过指针作为参数,虽然主调方为被调方开辟形参内存,但形参里存放的是指针(实参的地址),其指向了实参,对其操作相当于对实参内存进行操作。
ps:指针也可能相当于值传递

void fun(int *P)
{
	int a=200;
	*p=100;
	p=&a;
}

int main()
{
	int  x=0;
	int * s=x;
	fun(s);
	printf("%d %d \n",x,*s);//输出100,100
}

两种理解方式:
1.当调动fun()函数时,mian()为p开辟内存后将s赋给p,即p此时也指向x,即*p是对x的修改,而当p指向了a时,与s无关,且当fun()函数结束时,p的内存被释放,与s的内存无关 (相当于值传递)
2.虽然p=&a是有点想改变指针s的值的意思(即s=&a),但传入的是s不是&s,即值传递不修改实参内容
指针大小 一般情况都为4字节,指针大小也能判断CPU位数
指针的特性
1.类型不同,指针+1的特性不同
2.类型不同,指针解析内存的空间不同 指针变量类型不同,就不能相互赋值,必须强制类型转换
字节序(字节储存机制):
小端:低地址放地位,高地址放高位,传输时低地址放在流的开始。(内存上到下是低位到高高)
大端:高地址放低位,低地址放高位
由于指针指向的是存储开始的首地址(低位),然后根据其类型进行解析内存(低位解析到高位)

int main()
{
	int a=0x12345678;//a的存储 78  56  34 12 (小端)
	int *p=&a;
	short *sp=(short *)&a;//类型不同,需要强制转换
	char * cp=(char *)&a;//p、sp、cp都是指向a首地址(78)
	int ix=*ip;//12345678
	short sx=*sp;//5678
	char cx=*cp;//78
	return 0;
}

指针的类型决定了指针解析内存的大小
在这里插入图片描述

int main()
{
	int a = 0x12345678;
	int *ip = &a;
	char *cp = (char*)&a;//类型不同,需要强制转换
	char *p = (char*)&a;
	*p= 'a';// char类型指针解引用
	p= p +1;// char类型指针加1.
	*p= 'b';
	p = p+1;
	*p= 'c';
	p=p +1;
	*p= 'd';
	return 0;//a=64636261 即p开始指向a的首地址(低位)
}
int main()
{
	int ar[5]={12,2334,45,56 };
	int *p = ar;//int *p = &ar[O];
	int x = 0;
	int y = 0;
	×=*p++; //*p++ 将值给x后p指向下一个元素
	y =*p;
	printf("%d %d \n",x,y); 12 23
	x= ++*p;//指向元素的值+1
	y =*p;
	printf("%d %d \n",x,y);24,24
	x=*++p;//取出p指向的下一个元素的值
	y =*p;
	printf("%d %d \n",x,y);34,34
	return 0;
}

程序在运行过程中需要的是数据和指令的地址,变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符:在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址;程序被编译和链接后,这些名字都会消失,取而代之的是它们对应的地址。

三种指针(无类型指针,野指针,失能指针)

无类型指针 void (泛型指针,通用性)*
void 限制函数参数,表示该函数无参
指针都可以给void指针变量赋值,具有很多的通用性,被运用在缓冲区等交换数据,但其解析能力丢失了

char ch='a';
void *vp=&ch;
vp=&vd;
//*vp p++ p-- sizeof(void)是错误的 

野指针:(没有初始化的指针)
程序里不要出现野指针,初始化需要将其指向NULL或一个有效空间!否则指针指向了随机地址,项目中会导致及其可怕的后果。

空指针
指针指向NULL的指针

空悬指针
空悬指针都需要变为空指针。

失效指针:
指针指向的空间被系统回收,其指向的空间是随机值(生命期结束)。

多级指针

	int a = 10;
	int b = 20;
	int *p = &a;
	int **s = &p;

	**s=100;//a=100;
	*s=&b;//p指向b
	**s=200;//b=200;
	

在这里插入图片描述

20 结构体 (不同类型数据的集合,数组是相同类型数据的集合)

程序开发人员可以使用结构体来封装一些属性,设计出新的类型,在c语言中称为结构体类型。在c语言中,结构体是一种数据类型。(由程序开发者自己设计的类型)

  1. 结构体可以嵌套使用。函数不能嵌套使用(函数是C语言的最小执行单位)
  2. 结构体可作为返回值(返回多个需要的变量)
  3. 设计一种数据的集合
  4. 结构体可以相互赋值(将数组当做结构体的属性可以实现两个数组的赋值)
  5. 结构体不能自己嵌套自己
  6. 结构体之间比较不能使用strcmp,由于存在对齐(空出来的空间0xcccc)
  7. 结构体之间不能直接比较,需要进行重载,但可以相互赋值
  8. .与->就是结构体和类对象方案其自己成员的操作符。

ps: 链表 也是一种集合( 链表的节点一般是结构体,使得其可以连接),但它可以连续 可以表示 5x^2 + 7x^3的多项式

	struct Student //编译时编译器检测struct关键字,表示结构体的开始
	{
	char s_id[8];
	char s_name[8];
	char s_sex[4];
	int s_age;
	//stuct Student st//错误,不能自己嵌套自己,类似死递归。
	stuct Student * pst;//正确
	};//编译器检测;表示结构体的结束(而函数后面;表示的只是空语句)

结构体初始化(初始化的顺序根据结构体定义元素的顺序从上到下初始化)

既然结构体是一种数据类型,那么就可以用它来定义变量。结构体就像一个“模板”,定义出来的变量都具有相同的性质。也可以将结构体比作“图纸",将结构体变量比作“零件"”,根据同一张图纸生产出来的零件的特性都是一样的。
结构体是一种数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要存储空间。

Student tx;//结构体内的数据是随机值
Student tx={};//数值全为0,类似数组的初始化; 但只能在Cpp文件下才能编译通过 c文件没有typedef需要struct Student
int a[10]={};//初始化10个空间,每个值为0;

ps:

  1. 结构体可以相互赋值
  2. 结构体里的字符串数组和char*指针的区别,数组是字符串从.data区拷贝到数组,放在栈中,可以修改,而指针指向的是.data区里的字符串,且被优化,多个指向指向同一个字符串(只有一个字符串),不能被修改。
  3. 不能对结构体变量进行强转
  4. 对于结构体变量实参需要地址传递,节省空间
struct Inventory
{
	char description[20];
	int quantity;
};
struct Student
{
	char name[20];
	int age;
}
int main()
{
	struct Inventory sta={"iphone",20};
	struct Student stud;
	struct Student *ps=NULL;
	//stud=(struct Student)sta;//错误
	sp=&stud;
	sp=(stuct Student *)&sta;//可以,实体不能强转,指针可以强转,而指针强转说明其开始耍流氓了。

}

ps:结构体实体不能强转,指针可以强转,而指针强转说明其开始耍流氓了。
(类型强转的本质是什么?)为了使运算符两边的操作数类型一致,而类型又对应到了其存储空间,和其指针寻址解析的能力,才能进行赋值。

//错误代码
struct School
{
	char *s_name;
	int s_age;
};
void Init_school(struct School & sx)
{
	scanf("%s",sx.name);//错误,s_name为NULL 不能写入 ,若s_name为数组,即可
	scanf("%s",sx.age);//VS2012版本
}
int main()
{
	struct School sx={};//其元素被初始化为0
	 Init_school();
}

//改正
struct School
{
	char *s_name;
	int s_age;
};
void Init_school(struct School & sx)
{
	static char name[20];//需要加static或者开辟堆区不然将成为失效指针
	scanf("%s",name,20);//VS2019
	sx.name=name;
	scanf("%s",sx.age,4);
}
int main()
{
	struct School sx={};//其元素被初始化为0
	 Init_school();
}

结构体数组 *

所谓结构体数组,是指数组中的每个元素都是一个结构体。在实际应用中,c语言结构体数组常被用来表示一个拥有相同数据结构的群体,比如一个班的学生、一个公司的员工等。



struct Student
{
	char name[20];//结构体里是数组时,其name是这个数组的首地址,可以对其直接
	int age;
	float score;
};
void Print(const struct Student *cla,int n);//结构体数组也是数组
{
	for(int i=0;i<n;i++)
	cout<<cla->name<<cla->age<<cla->score;
}
int main() 
{
	struct Student cla[] =
	{
	{ "Tu lun",18,145.5 },
	{ "Cheng Lei",20,130.5 },
	{ "Wang ling",19,140.0},
	{"Zhang pint",17,134.5},
	{"Yang yang",19,135.0},
	{"Hu Ming",18,140.0 }
	
	int n=sizeof(cla)/sizeof(cla[0]);
  return 0;
  
}

ps:sizeof() 确定的是编译时该变量名的类型大小。
int f[10]={};// f 的类型 int [10] 。

结构体与动态内存

//结构体内一般不用数组,数组大开小用,malloc申请空间能操控(又由于指针与数组之间的转换,动态开辟的数组就像直接操作数组一样)

#include<iostream>
#include<stdlib.h>
using namespace std;
typedef struct Strudent
{
	char name[20];//结构体内
	int age;
	int score;	
}student;

student * init_student()
{
	student *s = (student *)malloc(sizeof(student));
	//student *s = (student *)malloc(sizeof(*s));正确,该设计sizeof是在编译时进行类型确认,所以在编译时是将计算了*s的类型,而类型确定是在编译的语义分析过程,而不是在运行时计算

	if(s==NULL) exit(EXIT_FAILURE);
	scanf_s("%s", s->name, 20);
	cin >> s->age >> s->score;
	return s;
}
void Destroy(student *ps)
{
	free(ps);//对其指向的空间释放
}
int main()
{
	
	student s;
	student *ps;//其是指向了student类型的指针,申请
	ps = init_student();
	student **pps=&ps;
	(*pps)->age = 100; //运算符结合性
	Destoty(ps);
	ps=NULL;
	return 0;
}


将结构体当做一个个节点连接起来,方便管理里,链表,但是以下面这种方法,需要改变程序来填加新的数据,就很!!(在栈上操作,无法控制添加的节点个数(不能在其他接口函数里插入节点,因为创建的该节点处栈帧,其生命期就结束了),需要利用堆空间,使得该结构能自由创建节点)

typedef struct Strudent
{
	char name[20];
	int age;
	int score;	
	struct Strudent *next;
}student1;
int main()
{
	student1 a,b,c;//在栈上缺d则再添加d,程序复用能力差,
	a.next=&b;
	b.next=&c;
	c.next=NULL;
	return 0;
}

进行改写

typedef struct Strudent
{
	char name[20];
	int age;
	int score;	
	struct Strudent *next;
}student1;

student * init_student()
{
	student *s = (student *)malloc(sizeof(student));
	if(s==NULL) exit(EXIT_FAILURE);
	scanf_s("%s", s->name, 20);
	cin >> s->age >> s->score;
	s->next=NULL;
	return s;
}
int main()
{
	student1 a,b,c;//在栈上缺d则再添加d,程序复用能力差,
	a.next=&b;
	b.next=&c;
	c.next=NULL;
	//t添加数据时
	c.next=init_student();//使得其能够添加节点,而不会被回收
	return 0;
}

柔性数组柔性数组是一种数组大小待定的数组。

在C语言中,可以使用结构体产生柔性数组,结构体的最后一个元素可以是大小未知的数组。

在struct sd_node结构体中data,仅仅是一个待使用的标识符,不占用存储空间。所以sizeof(struct sd_data)=8

用途︰长度为0的数组的主要用途是为了满足长度可变的结构体。
用法︰在一个结构体的最后,声明一个长度为0的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量,数组名这个符号本身代表了一个不可修改的地址常量。但对于这个数组的大小,我们可以进行动态分配。
注意∶如果结构体是通过calloc、malloc或realloc等动态分配方式生成,在不需要时要释放相应的空间。
优点︰比起在结构体中声明一个指针变量、再进行动态分配的办法,这种方法效率要高。因为简单。
实际使用过程中,往往在函数中开辟空间,然后返回给使用者指向 struct point_buffer 的指针,这时候我们并不能假定使用者了解我们开辟的细节,并按照约定的操作释放空间,增大了不同开发者的开发程度,因此使用起来多有不便,甚至造成内存泄漏。

缺点︰在结构体中,数组为0的数组必须在最后声明,在设计结构体类型有一定限制。

typedef struct Node
{
	int num;
	int size;
	char data[];//char data[0]
}node;

sizeof(node); //8  无论其添加数据与否,也说明了data是编译后才添加的数据

ps:对于编译器来说,数组的大小是类型与元素个数,此时元素为0,所以数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量,数组名这个符号本身代表了一个不可修改的地址常量。

//柔性数组的错误用法
typedef struct s_Node
{
	int num;
	int size;
	char data[];//char data[0]  编译时data只是size之后的一个地址,但它没有空间
}snode;

int main()
{
	char buff[100];
	sd_node x;
	x.num=4;
	x.size=45;
	x.data[0]='x';//编译时可以通过,但运行时报错
}

ps:由于编译时,结构体在栈内大小已经确定,data只是size之后的一个地址,但它没有空间即当x.data[0]=‘x’;时,栈内存里会发生越界,所以当具有柔性数组的结构体不能作为结构体变量来使用

//柔性数组的正确用法
typedef struct s_Node
{
	int num;
	int size;
	char data[];//char data[0]  编译时data只是size之后的一个地址,但它没有空间
}snode;

int main()
{
	s_node * x;
	x=(s_node *)malloc(x+10);//加10是加上柔性数组的大小,使得柔性数组有自己的空间,而堆空间可以根据实际需要进行开辟空间大小
	x->num=6;//一般表示柔性数组的元素个数
	x->size=10//一般表示柔性数组的大小
	strcpy(x->data,"sqh");
	int size=sizeof(*x);//8
	free(x)//用完之后还需要释放
}

ps:若不使用柔性数组,则会形成类似二维数组动态开辟的数据结构,即需指向一个申请一个空间,是结构体变量的空间,然后再在结构体变量里在申请一个空间来存放结构体里的字符串类型数据。即需要释放两次空间。

结构体的功能 把一个实体的信息归总起来作为一个集合来实现它,而存储这些信息的结构又可以分为链表和数组 各种信息头,协议

  1. 可以设计一个栈(由于栈是一个包含当前栈顶位置(值或指针),栈空间(数组或链表),元素个数)等复杂信息的一个集合,利用结构体来设计再好不过,而C++里类与结构体类似,且类有了更好的封装特性。 同时栈又存在于操作系统的方方面面,没有栈就没有函数,就没有操作系统

  2. 可以做一个队列(尾进头出),由于队列也是一个复杂信息的集合(队列空间,而队列空间数据结构又可以分为数组和链表,这考虑于当前队列是读操作多还是写操作多 ),当利用链表数据结构时,又可以将其每一个数据做成一个节点,然后再设计一个结构体作为该节点的指针,指向首节点和尾结点,还记录元素个数(这也相当于一个集合来完成一个功能),来更好的管理队列。

  3. 做一个链表,链表本身就需要节点,而节点(数据+指针)最好的方式就是进行集合,因为一个节点可能有好几样信息,如数据和指向下一个节点的指针。利用结构体对其集合,然后指向是很好的。。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值