又很久没有写总结了,每次打开CSDN的时候都有点抵触,有很多原因,其一是不知道这样的总结对自己到底有没有作用,或者对有缘看到这些破烂的朋友们有没有帮助,其次是由于上网课和写总结总是差了一到两节的内容,有时候要翻前两章左右的代码,找起来麻烦,另外还有自己之前写的什么狗屁总结(1)、(2)、(3)具体是什么内容也不知道,只有点进去慢慢看才晓得。自己深刻反省,并尽量修改以后这些回顾的标题,希望这个系列可以一直坚持下去,作为自己学习的监督。
这次的回顾是老师上课讲的一些习题作业,有些是偏重理论知识的选择题,有些是代码实操题,此外,代码题在之前的课上有时候提及过,选择题没有做截图和笔录,就不再一一去找了,接下来的内容全是代码题,由于内容很多,可能会分两次来写完。
/*创建一个整型数组,完成对数组的操作*/
void Init(int arr[], int sz) // 实现Init函数,初始化数组为全0
{
int i = 0;
for (i = 0; i < sz; i++)
{
arr[i] = 0;
}
}
void print(int arr[], int sz) // 实现print函数,打印数组的每一个元素
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
上面的两个函数只是为了实现一个内容,没有完整的框架,这两个函数与数组相关,其实接下来有很多习题都与数组有关,而实现数组习题的核心思想(我认为的核心思想)主要靠循环操作。这两个代码块很简单,大家就看一看罢了。
void reverse(int arr[], int sz) // 实现reverse函数,完成数组每个函数的逆置
{
int left = 0;
int right = sz - 1; // 最右边下标
while (left < right)
{
int temp = arr[left]; // 将暂定值设置为最左边的值
arr[left] = arr[right]; //将最左边的值设置为最右边的
arr[right] = temp; // 将最右边的值设置为最左边的值
left++;
right--;
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
reverse(arr, sz);
//Init(arr,sz); // 将数组初始化为0
print(arr, sz);
return 0;
}
在最早的几篇学习回顾中(由于我那该死的标题设置,我也不记得是哪篇),里面详细提到了冒泡排序和左右下标寻找数组某值的方法,在这道习题中得以应用。此外,这个-reverse函数内部设置了一个局部临时变量作中间值,方便将左右值交换。
在这里回顾一下上次提到的,如果不第三个变量,将两数交换的方法。需要对异或这种运算有非常敏感的认识,如果忘记了,可以再看一眼,另外值得一提的是,一般做这种交换操作,都会需要使用第三个变量,因为这样方便观察,逻辑也清晰明了,而那种(不需要第三个变量)的操作,更多会出现在考核里面,大家不用刻意牢记。
/*将数组A中的内容和数组B中的内容进行交换*/
int main()
{
int arr1[] = { 1,3,5,7,9 };
int arr2[] = { 2,4,6,8,0 };
int tmp = 0;
int i = 0;
int sz = sizeof(arr1) / sizeof(arr1[1]);
for (i = 0; i < sz; i++)
{
tmp = arr1[i];
arr1[i] = arr2[i];
arr2[i] = tmp;
}
printf("arr1 = ");
print(arr1, sz);
printf("\n");
printf("arr2 = ");
print(arr2, sz);
}
这道题与上面交换左右下标的题目相联系,所有实现都在for循环内实现,在此便不再赘述了。需要注意的是,tmp已经被赋值为arr1[i],不要出现arr[tmp]这样的操作。
/*求代码结果*/
int main()
{
int arr[] = { 1,2,(3,4),5 };
printf("%d\n", sizeof(arr)); // arr内只有4个元素,由于类型是int 所以长度为4*4 = 16
return 0;
}
这个题原本是个选择题,但我觉得挺有意思的,就抄录下来了,注意这里的(3,4)其实是一个运算,中间的逗号是单目运算符,这里只取逗号右边的值,也就是4,因此整个长度就是16。
int main()
{
int arr[] = { 1,2,3,4,5 };
short* p = (short*)arr; // 将arr首元素的地址强制转换为short类型赋值给short* p
int i = 0;
for (i = 0; i < 4; i++)
{
*(p + i) = 0; // 由于p此时是short类型的指针,向前一个单位,只能前进2byte
// 而最开始int类型的arr则是4byte一个元素
// 因此循环结束之后,虽然p前进了4个单位,但相当于前进了8byte
// 对应arr里也就是两个元素的长度
}
for (i = 0; i < 5; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
这里需要回顾指针章节讲的内容,关于解引用操作*对于各个类型例如int和short的区别,注意看注释,在for循环内,每次增加的长度,取决于此时p的类型,由于这时第一个for循环内只循环4次(i = 0、1、2、3、),在这种情况下,p最多前进3个次,也就是6byte,而第二个for循环中,我们打印的是arr,它的类型是int,而int 一个元素4个单位,因此在这情况下,通过short改变的长度只有6byte,只够arr的前两个元素,因此只有2个元素变为0,打印出来最终是:
0 0 3 4 5
int main()
{
int a = 0x11223344;
char* pc = (char*)&a; // 将a的地址强制转化为char类型
// 并放入char类型的指针 pc 中
*pc = 0; // 由于pc是char类型的指针
// 解引用操作并赋值0只能赋值一个byte
// 因此内存内为:00332211
printf("%x\n", a); // 将内容以16进制呈现出来
// 需要将内存内的信息反转
// 因此打印出来为:11223300
return 0;
}
如果可以简要的理解第一个代码块,那么这个就是另一种运算方式,注释也写的很清楚了。
int i; // 全局变量不初始化,默认为0
int main()
{
i--; // -1
// 10000000000000000000000000000001 -- 原码
// 11111111111111111111111111111110 -- 补码
// 11111111111111111111111111111111 -- 反码
// 如果为无符号位,那么第一位就不是符号位
// 全为有效位,此时为正数,那就原、反、补相同
if (i > sizeof(i)) // sizeof() -- 计算变量/类型所占内存的大小
// >= 0 (无符号数) -- (unsigned int)
{
// 此时会把i转化为无符号数进行比较
// 而这样根据二进制,i会是一个很大的二进制数
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
这道题原本也是个选择题,我抄录下来了,问的是最终打印的是>还是<,大家如果有兴趣,可以试试看,原因是,因为sizeof计算的是变量\类型所占内存的大小,因此这个结果一定是大于等于零。所以sizeof将会返回一个无符号数,而将i与sizeof(i)相比,则会将i转化为无符号数,因此得出一个很大的数,最后答案是大于。
int main()
{
int a, b, c;
a = 5;
c = ++a; // c = 6
b = ++c, c++, ++a, a++; // c = 8, b = 7 a = 8
b += a++ + c; // b = b + a++ + c --> 7+8+8 = 23 a = 9
printf("a = %d b = %d c = %d", a, b, c);
return 0;
}
这题有点取巧的意思,主要考察了++前置后置的区别。一题就能贯穿所有情况。到底是先赋值还是先++。另外还要考虑逗号运算符的规则。
比如b = ++c,c++,++a,a++,这里,因为++前置,所以先算++c = 7然后是逗号运算符,逗号表达式将会把最后边的值赋值给b,因此在++c,c++,++a,a++之后,将最后值赋值给b,注意++a,a++,这里是前置++a再逗号表达式再后置a++,所以b的值是++a之后的值,再最后计算a++,a=8,但b=7。
同理可以看最后一个b+=这里,拆开来就是b = b + …,在此b = 7,a = 8,c = 8,在计算完b=23后再将a++,得出a = 9。
/*统计二进制中1的个数*/
int count_bit_one(unsigned int a) // 此时将int 改为unsigned int
{
int count = 0;
int i = 0;
for (i = 0; i < 32; i++)
{
if (((a >> i) & 1) == 1)
{
count++;
}
} // 第一种算法
while (a)
{
if (a % 2 == 1)
{
count++;
}
a /= 2;
} // 第二种算法
while (a)
{
a = a & (a - 1);
count++;
} // 最精简的算法
return count;
}
int main()
{
int a = 0;
scanf("%d", &a);
// 1101 -- 通过模2除2来获得每一位的二进制数
// 00000000000000000000000000001101
// 写一个函数,求a的二进制表示(补码)中,有几个1
int count = count_bit_one(a); // 如果是负数怎么办
printf("count = %d\n", count);
return 0;
}
这道题共有三种算法处理方式,第一种最简陋,没有考虑到输入的数是负数的情况,此时算法会崩溃。第二、第三种方法适用于任何情况,在这里计算二进制中,个人得出的心得是,一定要处理好模与除出现的结果。合理运用模与除,事半功倍。
另外在这里解释一下第三种算法:将数本身与数减1相与,再赋给这个数,这样此数字最右边的1就会消失,因此我们通过这种方法,每按位与一次,计数加1,直到按位与为全零为止。
这样,本次作业的第一部分就讲完了,过段时间再总结接下来的内容,一会要去上课了。
在此我有个感悟,对于函数模块的实现,其实方法千千万万,但为满足一个功能而编写一个函数,如何最大性价比这个函数,才是我们学习C语言的一大难点,如何将一种算法从四五六七行,逐步缩减到一二行,不但可行,又适用范围广,还方便阅读,才是最终目的。