总结:
头文件,相当于对要用到的所有函数进行集体声明;
把且仅把函数声明,放进头文件当中;
把函数定义,放进.c/.cpp文件当中;
这种编写配合习惯:对于不同人调用函数编写代码,避免出现有关函数声明与定义上的错误,排查函数错误,这三类工作都有帮助;
<stdio.h>, "2FuncA.h", "3FuncB.h", "4Func.h"
总结:
头文件,相当于对要用到的所有函数进行集体声明;
把且仅把函数声明,放进头文件当中;
把函数定义,放进.c/.cpp文件当中;
这种编写配合习惯:对于不同人调用函数编写代码,避免出现有关函数声明与定义上的错误,排查函数错误,这三类工作都有帮助;
<stdio.h>, "2FuncA.h", "3FuncB.h", "4Func.h"
总结:
关于函数定义和声明有三类错误:
1、未声明函数;语法错误,在编译阶段报错
2、未定义函数;
3、重定义函数;(编译阶段和链接阶段都会报错)
源文件如何被计算机一步步处理?
Step 1:预编译——宏定义,将文件内的别名和缩写进行替换;并将include的文件内容原封不动地粘贴进来。
Step 2:编译——源文件经过预编译和编译过程,变成对象文件(.o/.obj)——这期间主要检查语法错误,例如函数未声明
Step 3:链接——对象文件经过链接,变为可执行文件(.out)——这期间会报:函数未定义或重定义错误,未定义使得计算机不知道如何实现这个函数,重定义使得计算机不知道执行哪一个函数定义;都执行就是可能世界语义了,会生成多个由不同语义函数组合而成的结果;由此可知函数定义放在哪个文件不太重要,对象文件链接的时候有就行。
总结:
程序就是算法+数据结构;
算法就是,聪明人的做事方法;(因为这样做事情,高效)
if else如果都接单语句,可以用冒号表达式;
数据结构,等于结构定义加结构操作;
总结:
用va_list, va_start, va_arg, va_end来声明,定位,获取下一个,结束来定义一个可变参数列表。
对于scanf和printf这样一个参数列表是可变的函数,该如何去定义?
实现可变参函数max_int,从若干个传入的参数中返回最大值。
int max_int(int a, ...);
如何获得a往后的参数列表?va_list 类型的变量
如何定位a后面第一个参数的位置?va_start 函数
如何获取下一个可变参数列表中的参数?va_arg 函数
如何结束整个获取可变参数列表的动作? va_end 函数
//先弄一个va_list类型的变量;
//然后,用va_start定位第一个参数的位置
//然后,不断用va_arg来获取下一个参数
//最后,用va_end结束整个可变参数列表的获取动作
总结
如果不满足什么条件,就直接看下一个n,可以用《if... continue;》这样一种有趣的写法,称为对偶缩进;
它相对于《if...else...》 少写一个判断语句;
例如写一些特定序列的遍历算法(二分查找,三分查找及其他遍历方式),用函数指针会非常方便。
相当于定义了一个函数:算法名称(特定的函数序列,查找范围值)
int main() {
int n = 286;
for ( ; ; n++) {
if (binary_search(Hexagonal, Triangle(n) == -1)) continue;
//对偶缩进的写法
//“当35行成立的时候”,“当xx行成立的时候”这种表达,利于分析代码;
if (binary_search(Pentagonal, Triangle(n) == -1)) continue;
printf("%d\n", Triangle(n));
break;
}
return 0;
}
总结:
函数指针,可以用于定义一个分段函数!
int g(int (*f1)(int), int (*f2)(int), int (*f3)(int), int x) {
if (x <0) {
return f1(x);
}
if (x<100) {
return f2(x);
}
return f3(x);
}
printf("%d\n", g(fac1, fac2, fac3, n))
//g就是一个分段函数,其中fac1,fac2,fac3为分段函数对应的不同段的函数,n为传进去需要处理的整型变量;
int (*f1)(int),代表的也是参数列表的变量,代表传进来一个函数;
传参的时候,不仅可以传一个整形,浮点型,还可以传一个函数
int 代表返回值的类型;
*f1,把某个函数当成一个变量传进来,放进一个函数变量当中
后面的( )里面只需要写函数参数类型即可;
这个足够重要,足够复杂。
一个新的概念产生,它是去解决一类问题的。
函数指针方便去解决类似于分段函数的函数调用;
函数指针可以用于定义一个分段函数!
总结:
用函数封装的程序,有更好地易读性,查错效率会有指数级增长。
递归和递推不一样,递推是一种算法;递归是一种编程技巧,解题方式;
while ( ~scanf("%d", &n)) 也可以实现一个循环读入的功能
EOF等于读入-1,而 -1 的二进制表示全都是1,按位取反全都是0,0表示条件不成立,相当于读到了EOF,文本末尾;
有两个区域可以开空间:一个叫做栈区,一个叫做堆区;
系统在分配的时候,栈区占8MB的字节,大约200万个整型int;而堆区和内存有关系,内存16G就可以开很大很大。
在定义函数的时候,占用的就是栈区。
在函数里面调用自己的时候,如果没有合适的出口,调用自己的时候,会不断开辟新的变量。
打印一个数字的位数,printf(printf())
n /= 10,表示n在十进制的表示下,去掉一位;
n /= 2,表示n在二进制的表示下,去掉一位;
do while 和while的核心区别,do while至少执行一次!第一次不判断直接执行;
总结:
条件表达式的“乘法口诀”
如果a不等于b——if (a - b)
如果a不等于0——if (a)
如果a等于0——if (!a)
if (a - b) ——如果a不等于b
if (a)——如果a不等于0
if (!a)——如果a等于0
短路运算:(因为前半部分已经判断完了,后半部分不影响整体表达式的结果,因此不执行了,效率优先)
False &&(这部分不执行)
Ture || (这部分不执行)
i && ......
相当于
i == 0 || ......
int a = 0, b =0;
(a++) && (b++); //该值为0,因为逻辑与先对a和b进行逻辑运算,然后,a和b再进行++的自增加1的运算。
求val模2的值,可以写为(val & 1)
计算奇数个数(或者偶数个数)的计数器,可以这么写:
count += val & 1;//表示,如果为奇数,则count加1;否则加0;
任务:打印0 - 9,每个数字有一个空格, 最后没有空格:(需要处理的地方在于:前后的空格如何去掉)
如果i是0,则不执行后面的语句;否则执行;
for (int i = 0; i < 10; i++){
i && printf(" "); //短路运算原则,这么写比if else更加高效!
printf("%d", i);
}
for (int i = 0; i < 10; i++){
if (i) printf(" ");
printf("%d", i);
}
总结:
for括号里的第三部分,是在代码块以后操作!
for比while更加灵活。
船长们相比于while,更愿意用for语句,可以写出花一样的代码,它里面的条件有三部分:
for (初始化;循环条件;执行后操作){
代码块;
}
Step 1:初始化;
Step 2:循环条件判断;
Step 3:执行代码块;
Step 4:执行后操作;
Step 5:跳转到Step 2;
注意,这里“执行后操作”第三部分,是代码块以后操作,而非代码块之前操作!!!!
总结:
可以用Linux内核中的宏定义,来帮助CPU提前做条件分支预测,提高效率;
尽可能减少if else,条件分支判断会拖延CPU并行处理语句的效率。
取指令,指令预解析,取数据,执行,写回
【为什么CPU讨厌if语句?】
CPU会并行处理数据,如果遇到条件分支,那是做if条件里面的还是if条件外面的?
如果执行if里面的,赌错了,那么就全是无用功了,就变成串行了;
Linux内核里,写了这样一些宏定义:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
// likely 代表 x 经常成立
// unlikely 代表 x 不经常成立
这两行的区别,是给CPU看的。
F:取指令
D1:指令预解析
D2:取数据
EX:执行;(execute)
WB:写回;
条件分支判断总结:
写switch的时候,一定要注意灵活运用break!!
!!(x)是归一化的作用,负数也为非0值;
if (!n) 表示 if (n == 0)
表达式:可以是逻辑判断的表达式(a == b),也可以是数学计算的表达式(a + b);
2022/10/18 8:51
从86题,讲到102题
都刷过了,跳过。
虽然用的是C++,但是如果没有那个语句是C实现不了的功能,那没差别。
#include <stdio.h>
int main(){
int a = 7, b = 3, c = 2, d = 3, e = 4;
int *p = &a;
--*p;
printf("%d\n", a);
c = a ^ b;
printf("c = %d\n", a ^ b);
printf("a = %d\n", b ^ c);
printf("b = %d\n", a ^ c);
return 0;
}
output:
6
c = 5
a = 6
b = 3
这段代码,可以非常好地告诉我们,指针是什么意思。
*p,表示a变量的值,因此可以--*p;
由于原来a为7,--a = --*p = 7-1 = 6
如果n的二进制第一个位置上是1,则给p加一个小括号;
第二个位置上是1,则给p加一个中括号;
第三个位置上是1,则给p加一个大括号;
stdin:标准输入
stdout:标准输出
stderro:标准错误输出
当代码出bug了,stderr可以将标准输出和错误输出区分开来。
既然,标准输出,和标准错误输出,都可以重定向到文件中
那么如果,想看文件是否有bug,可以看,标准错误输出文件里面,是否有内容,这样就可以大大减少。。。的工作量。
不知道减少的是什么工作量;
似乎就是在编译的时候,如果有问题,就用标准错误输出,将文件写进标准错误输出文件里;
没有问题,就写进标准文件里;
这样,检查代码是否有问题,只需要看标准错误输出文件里有没有内容就可以了。
sscanf:把字符串里的内容,存到a里面去;
+4,+8,就是指从第几位开始读起;
由于,字符串str代表的就是字符串的首地址
而每个指针都可以看作一个数组,因此可以+4,+8.
getchar(),从输入缓冲区拿走一个字符
但没有说存放在什么地方;
那么,它的作用,很用可能就是,拿走回车,保证在输入数据的时候不是按了回车,就执行程序,而是可以多输入几行数据。
这里是看,printf,sprintf, fprintf三个的具体功能区别吗?
而且,这里似乎,新建立了一个文件,叫做fout。
总之,有一个文件函数操作,是超出我目前的知识的。
盲猜,大概是打开一个名为null的文件,如果没有这个文件则新建一个名为null的文件,并且在里面写w?
w |
打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。 |
那么这个程序就是,定义了了一个长度为100的字符串,还用只写模式定义了一个文件fout
(这个定义方式,看起来很像定义了一个指向文件的指针,因为fout前面有一个*号)
然后输入内容以后
分别打印了:
成功打印的字符个数;
成功打印的字符串个数;
成功打印的“文件个数”?
这个学习时长和流程,控制的太好了。特别是1分钟阅读的条件,简直完美。
第9、10章:分别是指针和结构体,重中之重中之重。
11点02分
在不熟悉编程的情况下,对于新手而言,看到最重要的几条分别是:
1、【好好写注释】说明功能,以及实现方法,让别人更加清晰愉悦地读懂自己写的代码;
2、【名称写得直观易懂】不要过分简化也不要过分复杂,是让别人更加清晰愉悦地看自己的代码(例如马斯克在企业内不让大家使用缩写来表达,这个使得交流低效);
3、【团队合作】一起写代码,就像加入一支球队一样,适应这个球队的风格,就是,适应这个代码的编码风格;
4、【TODO注释】可以帮助自己快速地找到可以提升的待改进的代码部分。