从0开始学c语言-终篇41-练习、学习分享

优快云话题挑战赛第2期
参赛话题:学习笔记

        终于,来到了终篇,我坚持做到了!每天一篇博客记录自己的学习(虽然中间有两天去干农活而断更了)!啊呜呜呜呜~激动!

        但是,这不会是我的终点,这是新的起点。之后在总结篇会把这个专栏中没有写好的地方再重新写一下,以便于查阅和复习。昨天有群友夸我博客写得好,我很开心,第一次被夸夸呢!我会继续努力,也感谢群友和路人粉丝们的支持!学无止境,一起学习,一起进步~

        好了,进入正题(这篇练习不多,因为都是定义复习,便只放了些感兴趣的题目),后面再说一些学习心得。

​​​​​​​ 

        放我男神照片当门面~

目录

题目1:#define定义替换

题目2:写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。 

题目3:写一个宏,计算结构体中某变量相对于首地址的偏移

学习心得

书籍链接:


题目1:#define定义替换

设有以下宏定义:

#define N 4

#define Y(n) ((N+2)*n)     /*这种定义在编程规范中是严格禁止的*/


则执行语句:z = 2 * (N + Y(5+1)); 后,z的值为(   )

        主要是复习#define的替换原则

         1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

        2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。

        3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

#define N 4
#define Y(n) ((N+2)*n)     /*这种定义在编程规范中是严格禁止的*/

z = 2 * (N + Y(5+1))
z = 2*( 4 + ((4+2) * 5+1)) ) 
  = 2*(4+ (6*5+1) )
  = 2*(35)
  = 70

题目2:写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。 

        思路:分别拿出奇数位和偶数位的数字,让偶数位数字向后挪动一位、奇数位数字向前挪动一位,再让这两位相加即可实现整数二进制位的奇数位和偶数位交换。

        举例:交换整数 5 二进制位的奇数位和偶数位拿出来是什么样子

00000000 00000000 00000000 00000101 -5
00000000 00000000 00000000 00000101 -5的奇数位
00000000 00000000 00000000 00000000 -5的偶数位
00000000 00000000 00000000 00000101 -5 = 5的奇数位+5的偶数位

        可以看到整体思路是没问题的,难就难在怎么只拿出奇数位或者偶数位。

        可以这样想,二进制中只有0和1,那么想原原本本拿出一个bit位上的二进制数字应该使用哪个位操作符和哪个数字呢?我想到了之前学过的如何判断一个bit位上的数字是不是1的学习。

        我们用一个数字和1(按位与)&,便可以得到最后一个bit位上的数字是不是1。那么除了0就是1,你判断出来是1,那便肯定不是0了。(看乱了把,说详细就容易乱,后面直接点题了啊)

        拿出奇数和偶数位:

             现在我们尝试拿出 -1 的偶数位。

11111111 11111111 11111111 11111111 - -1
10101010 10101010 10101010 10101010 - 和-1&的数字(0xaaaaaaaa)
10101010 10101010 10101010 10101010 - -1的偶数位

           那怎么拿出奇数位呢?换一下按位与&的数字就行。

11111111 11111111 11111111 11111111 - -1
01010101 01010101 01010101 01010101 - 和-1&的数字(0x55555555)
01010101 01010101 01010101 01010101 - -1的奇数位

        拿出来后,让偶数位数字向后挪动 (右移) 一位、奇数位数字向前挪动 (左移) 一位,再让这两位相加即可实现整数二进制位的奇数位和偶数位交换。

        现在可以写代码了。

#define Change(A)  ( ( A & 0xaaaaaaaa ) >> 1 ) + ( ( A & 0x55555555 ) << 1 ) 
int main()
{
	int a = 10;
	printf("%d\n", Change(a)); //output:5
	return 0;
}

题目3:写一个宏,计算结构体中某变量相对于首地址的偏移

        偏移量是结构体变量中成员的地址结构体变量地址

        要注意偏移量是谁和谁的差,是地址之间的差,那么就很快能想到通过指针储存地址然后相减得到地址的差。(一个指针储存成员地址、一个指针储存结构体变量的地址)但是你有没有想过他俩的指针类型不相同。

       要提醒你的是,我们学过用offsetof宏来计算偏移量。而实际上这个题考察的就是我们如何模拟实现宏

         可以看到offsetof宏参数一个是结构体名字,一个是成员变量名字。返回值是成员变量相对于结构体的偏移量,以字节大小为单位。用法如下:

#include <stddef.h>
struct stu
{
	int age;
	short weight;
	char name[20];
};
	printf("%d\n", offsetof(struct stu , age));

        那么按照这个参数,如果我们通过上面两个指针相减的方法,似乎结构体本身甚至不需要向内存申请分配空间(也就是创建结构体变量),只需要一个结构体类型声明就够了。

不知道为啥的复习这个:从0开始学c语言-22-结构体声明和初始化、结构体大小、结构体成员访问、结构体传参_阿秋的阿秋不是阿秋的博客-优快云博客

        那么你甚至都没有向内存申请分配空间的结构体变量,你又该如何取到结构体变量的地址呢?

        这时候就需要改变一下思路了,可以看到我们的第一个参数是结构体类型,第二个参数是成员变量名字。而 偏移量是结构体变量中成员的地址结构体变量地址

        这是两个地址的差,而地址本身又是用一些编号来表示的,那么这些编号本身实际上是整型数据类型,现在我们把一个整型数字强制类型转换为结构体指针类型,那么现在这个数字就相当于一个地址(因为实际上指针保存的是地址,那指针不就是地址嘛)。(0x表示这个数字是16进制的)就像下面这样:

(struct stu*)0x20  //0x20的类型是struct stu*

        既然现在这个数字有了这个类型,(注:这并不意味着我们向内存申请分配空间了,20可是个常量), 那么现在这个数字就有了访问结构体成员的能力,(注:访问结构体成员并不意味着结构体成员本身是占用内存的) 。

        注意:从始至终,我们都没有创建一个结构体变量向内存申请分配空间!!!

        现在我们使用这个拥有指针类型的20访问一下结构体成员,并取出这个结构体成员的地址。然后减去一开始的20(是十六进制的,要加0x,因为地址是十六进制数字表示的),便能得到我们想要的偏移量,记得减之前先把指针类型强制类型转换为int类型。(注意:这里很容易因为括号出问题,要特别清楚每个括号的作用,少了就容易出错整个过程要清楚每一步所对应的数据类型是什么,便知道该什么时候加括号,什么时候强转为int类型了。(注:访问到age成员的时候,实际上这时候的数据类型是int类型,所以取地址出来的是int*指针)

(int)&( ((struct stu* )0x20)-> age         )-0x20
(int)&( ((struct_name*)0x20)-> member_name )-0x20
#define OFFSETOF(struct_name,member_name) (int)&( ((struct_name*)0x20)-> member_name )-0x20

        现在展示一下我们的成果。

#include <stddef.h>
struct stu
{
	int age;
	short weight;
	char name[20];
};
#define OFFSETOF(struct_name,member_name) (int)&( ((struct_name*)0x20)-> member_name )-0x20
int main()
{
	printf("%d\n", offsetof(struct stu , age));
	printf("%d\n", OFFSETOF(struct stu , age));
	return 0;
}

        但是这不够简便,我们可以把数字20换为0,那么就不需要减去一开始的20了。

#define OFFSETOF(struct_name,member_name) (int)&( ((struct_name*)0)-> member_name )

         这才是最完美的答案。

        可能看完,你最大的疑问还是有没有访问到这个内存, 还是这句话:

          注意:从始至终,我们都没有创建一个结构体变量向内存申请分配空间!!!

        但是你又会疑惑,既然没有在内存中占用空间,你是怎么利用这个指针访问到这个结构体成员的?又是怎么取出来它的地址的?

        我就这么说吧,相当于你利用一个数字给它变了变类型,然后模拟实现了一下这个过程,这个模拟过程和内存半毛钱关系都没有!(而且你利用的这个数字本身也没有占用内存)

学习心得

        到此,我们的c语言学习就算是过完一遍了。在学习过程中,我有很多东西想记录下来,想告诉同样学习c语言的伙伴们,也不断提醒今后的自己继续努力,不要忘记初心。

        1·在前期学习过程中,看不懂的先记录下来,在慢慢运用的过程中就会其意自现。
        2·遇到问题先自己思考,要学会利用调试来看看自己犯了什么错,犯错的机会就是成长的机会,大概率是自己代码写错了。解决不了就去找群友或者优快云上查,或者问我(我很乐意解答别人的问题)。
        3·其实学习c语言,除了理解概念之外,最重要的是要有编程思维,这一点因为我觉得写起来麻烦,便只在个别文章里写得比较细致。
        4·别追求速度,要追求质量。该做的笔记都要做好,别做代码的复制机器。要经常复习。这样遇到问题的时候才有思考的方向。(复习可以看我写的这个专栏,需要什么就查什么)
        5·最重要的一点,坚持下去。

        我还推荐大家多读书,但是审核不给我通过链接,有需要的可以联系我(免费)。我深知自己还差得很远,接下来的路还很长,我的每一天都要全力以赴,希望我的学习速度能够赶上我的博客更新,呜呜呜,努力ing!

        后续会在总结专栏进一步汇总学过的知识,以及考虑出一个简便版本的知识点专栏。

江湖见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值