硅基计划 学习总结 拾

发布于:2025-05-01 ⋅ 阅读:(15) ⋅ 点赞:(0)


一、结构成员

##引子##

如果我么想规划一个区域来存储我们的相关信息,但又不想很杂乱的存储,那我们可以用到结构体这个函数,来帮助我们结构化管理信息,我们暂时只介绍“.”

1.结构体

它可以用于复杂的、多个对象的,属于自定义数据类型,你可以自定义一些值的集合,值可以是不同类型的变量,甚至是嵌套结构体

我们给出示例:

struct tag(函数名)
{
 //一些成员列表
}(变量的列表,可有可无,可以在创建结构化函数时候创建变量,默认是全局变量);

##我们给出一段代码,参照上面的注释,我想你应该能明白,这里不过多赘述啦

struct adr
{
	char name[10];
};

struct stu//先规定好结构体内部信息
{
	struct adr one [10];//这里的one是结构体名称
	char name[10];
	int age;
	float score;
};

int main()
{
	//struct stu s1 = {"xiao_san",20,95.5f};//我们可以利用这个结构体存储不同的信息,这里s1代表一号学生
	//我们也可以不按照顺序初始化,可以指定初始化内容,利用“.”
	/*struct stu s1 = { .name = "zlh",.age=18,.score=95.5 };*/
	//如果结构体里面套一个结构体呢?这个时候我们如何初始化呢?
	struct stu s3 = { {"heihei"},"wangwu",20,99.9f };//这里我们的括号里的括号就是针对adress来初始化的,如果里面还有数组,我们再加括号即可
	printf("%s\n", s3.one[0].name);//你可以添加多个“.”来访问多层结构体里的元素,注意结构体中你规定的存了10个信息,下标也是从0开始
	return 0;
}

二、操作符优先级

1.优先级

表达式含有各个运算符,优先级高的先执行,例如3+4*5=23;

2.结合性

假设优先级相同,那这个时候结合性说了算,究竟是左结合还是右结合,大部分都是左结合,少部分右结合,例如5*6/2=15,具体参照下表:

##你肯定想到这么多,我们懒得记,我们就记一些常用的吧

1.圆括号( () ),自增运算符( ++ ),自减运算符(-- )

2.单目运算符( + 和 ) • 乘法( * ),除法( / ) • 加法( + ),减法( )

3.关系运算符( < 、 > 等)

4.• 赋值运算符( = )

如果你不想这么麻烦,有一个终极办法,你想哪个部分先计算,就把哪个部分用括号括起



三、表达式求值

1.整型提升

整型默认是int类型,如果你想用long long类型你只需在数据后加上字母“ll”,为了获得这种精度,表达式中字符和短整型使用之前要被转化为普通整型

整型提升规则

1.有符号整型按照变量的数据类型的符号位提升,即其他位要么补0要么补1

2.无符号整数提升高位直接补0

为什么要这样做,这是因为CPU难以用除4字节或者8字节其他非4字节类型计算

##我们举个例子:

int main()
{
	char a = 20;
	char b = 120;
	char c = a + b;
	printf("%d", c);//为什么结果是-116?
	return 0;
}

为什么结果是-116?为什么你正数结果后面给我搞出个负数?诶,我们娓娓道来

##拓展:%d我们也可以十进制形式打印一个有符号整型int

1.首先我们要知道char类型的取值范围是-128~127,而你变量C求出来后是140,存不下呀

2.而且你char类型的变量只能接收一个字节大小,即8个比特位,对于二进制8位,那必然要对你二进制表示进行取舍

3.我们开始存取数据

变量a的值是20,转化成二进制后的补码是0000 0000 0000 0000 0000 0000 0001 0100,而你char类型只能存8比特位,即存入0001 0100,相当于把20这个值截断后存入

同理我们来存入b变量值120,转化成二进制补码是0000 0000 0000 0000 0000 0000 0111 1000,同理char类型变量我们存入8比特位,即0111 1000

4.要想相加,你只有8比特位显然是不行的,要进行整型提升,按照规则,a提升后为(高位补0):0000 0000 0000 0000 0000 0000 0001 0100,同理b为:0000 0000 0000 0000 0000 0000 0111 1000,我们再把两个整型提升后的变量相加,结果是:0000 0000 0000 0000 0000 0000 1000 1100

5.我们再把这个变量存入char类型中,截断后为1000 1100,但是你打印类型是整型,我们得再次发生整型提升(根据规则,符号位是1,我们高位补1)之后再转换成原码进行打印:1000 0000 0000 0000 0000 0000 0111 0100,打印的结果就是-116了

##怎么样,看似简单的算式背后可是有很复杂的逻辑

 2.算数转化:如果你的各个操作数属于不同类型,我们要将其中一个操作数类型转化成另外一个操作数类型,我们根据下面这个表由下到上转化优先级(int是起点)

感谢比特教育科技提供的图解,感谢

四、问题表达式

##有时候有些人在写程序,也不知道脑回路是怎样的,写的程序让编译器都不知所措

1.加法优先级问题

a*b+c*d+e*f;

 这个表达式如果是你,你肯定先算乘法再加起来

但是机器不管这些,都有自己独特算法

比如我们先计算前两个乘法,相加后,第三个乘法计算后,再相加

又或是先计算三个乘法,把前两个结果相加后得到的结果,再和第三个乘法结果相加

如果是变量值还好,但假设是表达式呢,是不是会影响最终结果

2.C到底何时加

C+ ++C;

这个代码问题在于到底是什么时候加上C,是我++之后还是++之前呢?

3.逻辑混乱

int i = 10;
i = i-- - --u*(i=3)*i++ + ++i;

 第一眼你看到这个代码是不是想这个表达式到底要传达什么意思,如果你用不同编辑器去计算,会有不同结果,这个算式出自于一本书《C和指针》作者都抓狂了这个代码

感谢比特教育机构提供的图

4.Fun函数究竟什么时候开始调用

int fun()
{
 static int count = 1;
 return ++ count;
}

int main()
{
 int answer;
 answer = fun()-fun()*fun()
printf("%d\n",answer);
return 0;
}

##如果你把这个表达式放在VS2022中,结果就是2-3*4=-10

5.这位更是重量级

int main()
{
	int i = 1;
	int ret = (++i) + (++i)+(++i);
	printf("%d\n", ret);
	printf("%d\n", i);
	return 0;//VS中是12 4,而你用Dev是10 4
}

为什么是截然不同的结果

在VS中是算出三个++i之后的i的结果再把三个i相加
而在Dev中是先算出前两个++i之后,先把前两个i相加,最后再i再加1,之后再把上一次相加值加到这一次

我搞不明白为什么有的学校把这个当成期末考试题还给出选项


因此,我们不要写以上这些难看的low代码,想先算哪个加括号就好


五、内存

1.我们都知道1byte(比特)=8字节,其他都是1024进制转化

2.我们把内存划分成一个个小的空间,每一个空间都是一个字节,为一个内存单元,我们可以给内存单元编号,就是指针,就是地址

3.各个硬件之间进行数据传递,CPU与内存之间有很多“线”连接起来,一般有“地址总线”,“数据总线”,“控制总线”,我们读写都是通过这些进行的

4.CPU访问内存的时候需要知道其位置,我们就给内存编址,给每个内存单元编号(硬件设计的时候已经编好)

32位机器有32根地址总线,每根总线有0和1两张状态,攻击2的32次方中编号,64位机器择优2的64次方种编号

六、指针变量和地址(初阶)

1.取地址操作符(&)

我们回忆下,变量创建的本质是什么,就是在内存中申请4个字节空间,以此来存储数据

例如int a =10,其中这个a不是给编译器和计算机看的,是给你看的,它们看的是地址

你可以在调试中找到监视输入取地址操作符查看变量a的地址,中间那一行是十六进制存储的,比如10=1011=啊,我们就可以看到000a

但是a它有四个字节啊,不应该有四个地址吗,如果你通过取地址操作符,你会看到是地址中较小的地址,为什么?

因为这样可以顺理成章求出其他地址,存放地址的变量就成为指针变量,如:

int *p=&a,这里的int *就为a的类型,而且指针变量是专门存放地址的,在它的眼里什么都是地址

2.解引用操作符(“*”)

int a =10;
int *p=&a;
*p=0;

我们注意区分下面两个式子区别,下面那个式子中,“*”是用来间接访问的,通过p的地址找到地址所对应的对象,这是另一种放大求变量

3.指针变量的大小

因为指针也是变量,也是需要向内存中申请空间的

我们根据地址存储大小,32位的机器通过32跟地址线传输,需要32bit=4字节空间存储

因此指针变量大小是4字节,不管你指针变量里面的数据是什么类型

统统都是4字节,而六十四位机器就是8字节 

 七、指针变量类型的意义

1.指针解引用

话说每个指针变量大小都相等

那我们为啥还要规定指针的变量呢?这么做的目的是什么?我们可以举个例子来看看

int main()
{
	int a = 0x11223344;//我们这么做的意义是让每两个十六进制位各占一个字节,从而占满我们int类型的四个字节
	int* pa = &a;//把a的地址存入指针变量pa中
	*pa = 0;
	return 0;
}

a中地址就全为0了

通过调试我们看看,的确都变成了0

但若换成char呢

你会发现只有第一个字节变成了0,其他没有变化

因此我们规定指针变量类型的意义就在于

每种类型决定了指针变量它的权限有多大,它们各自的“范围” 不同,产生的效果也就不同

如果你换成double等其他大空间类型,会发生越界访问

所以,权力越大越危险(开玩笑的)

2.指针加减

我们先举个例子,我们把下列程序结果打印

int a = 10;
int *pa = &a;
char *pc = &a;

我们发现无论是int还是char类型的指针变量,打印的地址都是一致的

但是如果你打印printf+1或者-1呢?

你就会发现,怎么结果不一样啊,所以,这就是我们要说的

你的指针变量类型就决定了你指针向前/向后能够走多远

3.Void*指针

##它是指没有具体类型的指针,它也叫泛型指针

通常用途函数参数部分,或者是泛型编程,无论你是什么数据类型都可以用

##但是它也是有缺点,不能进行直接的加减整数和解引用

八、指针运算

1.“++”“--”整数型运算

对于数组元素,我们只需要知道第一个元素地址即可顺着下去知道其他元素地址

结合我们用到的指针知识,我们可以通过指针来循环打印数组

以下只是一只示例,还有其他很多种写法,就不一一列举了,你可以用while循环来写

#include <stdio.h>
int main()
 {
  int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  int *p = &arr[0];
  int sz = sizeof(arr)/sizeof(arr[0]);
  for(int i = 0; i < sz; i++)
  {
   printf("%d ", *(p + i));
  }
  return 0;
 }

 通过观察我们发现,你p加上i,就是数组第i元素,也就是数组第i元素地址,很巧妙吧

2.小练习

试着规定一个数组,用循环打印hello world,打印遇到“\0”我们就停止

char arr[20] = "hello world";
char* pa = arr;
while(*pa != '\0')//不等于要这么写,打印字符用%c
{
	printf("%c",*pa);
	pa++;
}

我们发现我们也可以利用指针特性来打印字符串了,相信你还有其他更多点子

3.指针-指针

##指针相减前提是两个指针指向同一块空间,否则无法相减

比如我们举个数组例子

int arr[10]={0};
printf("%lld",&arr[9]-&arr[0]);

我们可以看到结果是9,为什么?

这个结果好像我们的数组的元素个数减去1啊

不难发现,我们根据&arr[0]+9==&arrr[9],通过移项变号

我们就可求得两个指针之间的元素个数,地址较高的在前在后相减会有正负

因此|&arr[0]-&arrr[i]|==i

4.指针关系的运算,我们以打印数组为例

##如果你输入的数组元素下标超过了数组本身大小,就会产生越界

指针也是一样,通过指针我们判断数组元素边界,就为首元素地址加上数组大小

#include <stdio.h>
int main()
 {
  int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  int *p = &arr[0];
  int sz = sizeof(arr)/sizeof(arr[0]);
  while(p<arr+sz)//通过指针我们可以比较数组的边界
  {
   printf("%d ",*p);
   p++;
  }
 return 0;
}

九、Const修饰指针

##如果你在变量前加入const,变量就赋予了常属性

即变量不能再改变,但本质上还是变量,只是在语法层次上进行了修改

但是如果我们非要修改,怎么办?诶,我们可以找指针这个中间商!

const int a = 100;
int *pa = &a;
*pa = 20;

通过打印结果我们发现,还真改了

假设我们在指针变量左边加入const呢?

 我们不难发现,我无法通过指针*pa改变这个变量a的值了,但是我可以改变pa中存放的地址

同理我们再指针变量右边加入const呢?结果就反过来了

我们得出结论

const如果放在指针变量左边,修饰的是指针指向的内容,这个内容不能通过指针改变, 但指针变量本⾝的内容可改变

const如果放在指针变量右边,修饰的是指针变量本⾝,使得指针变量的内容不可改,但指针指向的内容,可以通过指针改变

##你还可以两边都加入const,让其两张方式都不可改


作者学术不精,难免有疏漏,若有错误欢迎指出,评论区讨论


END


网站公告

今日签到

点亮在社区的每一天
去签到