优快云话题挑战赛第2期
参赛话题:学习笔记
终于,来到了终篇,我坚持做到了!每天一篇博客记录自己的学习(虽然中间有两天去干农活而断更了)!啊呜呜呜呜~激动!
但是,这不会是我的终点,这是新的起点。之后在总结篇会把这个专栏中没有写好的地方再重新写一下,以便于查阅和复习。昨天有群友夸我博客写得好,我很开心,第一次被夸夸呢!我会继续努力,也感谢群友和路人粉丝们的支持!学无止境,一起学习,一起进步~
好了,进入正题(这篇练习不多,因为都是定义复习,便只放了些感兴趣的题目),后面再说一些学习心得。
放我男神照片当门面~
目录
题目2:写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。
题目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!
后续会在总结专栏进一步汇总学过的知识,以及考虑出一个简便版本的知识点专栏。
江湖见!