1 | typedef struct { |
1 | typedef struct { |
sizeof(A)=24而sizeof(B)=16
A的内存分布:(每一个元素都存在最大元素大小的空间内)

B的内存分布:

大端模式,是指数据的高字节保存在内存的低地址中。

4个两位16进制数=8*4=32位=4B=1int
编程判断大小端的两种方法:
在union中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址。即上述的union虽然定义了两个成员,但其实这个union只占用了4个字节(32位机器中),往a成员赋值,然后读取b就相读取a成员的低位第一个字节的值。如果机器使用大端模式,则u.a=1那a的最高字节值为1;1
2
3
4
5
6
7
8
9
10
11typedef union {
int i;
char c;
}my_union;
int checkSystem1(void)
{
my_union u;
u.i = 1;
return (u.i == u.c);
}
1 | int checkSystem2(void) |
函数中传递的是p指针的形参(只能修改arr[1]的值,无法修改*p指向的位置)

graph LR
classDef className fill:white,stroke:#333,stroke-width:1px;
id3[*p 形参]-->id1[7]
subgraph arr数组
id[10]-->id1[7]
id1[7]-->id2[5]
end
subgraph *p
id3[*p 形参]
end
class id,id1,id2,id3 className;
graph LR
classDef className fill:white,stroke:#333,stroke-width:1px;
id4[*p]-->id1[3]
id3[*p 形参]-->id[10]
subgraph *p 形参
id3[*p 形参]
end
subgraph *p
id4[*p]
end
subgraph arr数组
id[10]-->id1[3]
id1[3]-->id2[5]
end
class id,id1,id2,id3,id4 className;
*p--=3实际上是*p=3,然后(p--)1 | #include<iostream> |
graph LR
classDef className fill:white,stroke:#333,stroke-width:1px;
id3[*&p]-->id1[7]
subgraph arr数组
id[10]-->id1[7]
id1[7]-->id2[5]
end
subgraph *&p
id3[*&p]
end
class id,id1,id2,id3 className;
graph LR
classDef className fill:white,stroke:#333,stroke-width:1px;
id4[*p]-->id[10]
id3[*&p]-->id[10]
subgraph *&p
id3[*&p]
end
subgraph *p
id4[*p]
end
subgraph arr数组
id[10]-->id1[3]
id1[3]-->id2[5]
end
class id,id1,id2,id3,id4 className;
1 | #include<iostream> |
在c语言中,对同一个变量或者函数进行多次声明是不会报错的。所以如果h文件里只是进行了声明工作,即使不使用# ifndef宏定义,多个c文件包含同一个h文件也不会报错。
但是在c++语言中,#ifdef的作用域只是在单个文件中。所以如果h文件里定义了全局变量,即使采用#ifdef宏定义,多个c文件包含同一个h文件还是会出现全局变量重定义的错误。
使用#ifndef可以避免下面这种错误:如果在h文件中定义了全局变量,一个c文件包含同一个h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误;如果加了#ifndef,则不会出现这种错误。
其实“被重复引用”是指一个头文件在同一个cpp文件中被include了多次,这种错误常常是由于include嵌套造成的。比如:存在a.h文件#include “c.h”而此时b.cpp文件导入了#include “a.h” 和#include “c.h”此时就会造成c.h重复引用。
1 | #ifndef x //先测试x是否被宏定义过 |
循环引用的后果:
有些头文件重复引用只是增加了编译工作的工作量,不会引起太大的问题,仅仅是编译效率低一些,但是对于大工程而言编译效率低下那将是一件多么痛苦的事情。
有些头文件重复包含,会引起错误,比如在头文件中定义了全局变量(虽然这种方式不被推荐,但确实是C规范允许的)这种会引起重复定义。
执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行函数代码(也许还需将返回值放入寄存器中),然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定的开销。
内联函数提供了另一种选择。编译器将使用相应的函数代码替换函数调用。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。
1 | inline double square(double x){return x*x;} |
内联函数与宏定义的区别:
使用宏和内联函数都可以节省在函数调用方面所带来的时间和空间开销。二者都采用了空间换时间的方式,在其调用处进行展开:
(1) 在预编译时期,宏定义在调用处执行字符串的原样替换。在编译时期,内联函数在调用处展开,同时进行参数类型检查。
(2) 内联函数首先是函数,可以像调用普通函数一样调用内联函数。而宏定义往往需要添加很多括号防止歧义,编写更加复杂。
(3) 内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了(无法将this指针放在合适位置)。
可以用内联函数完全替代宏。
在编写内联函数时,函数体应该短小而简洁,不应该包含循环等较复杂结构,否则编译器不会将其当作内联函数看待,而是把它决议成为一个静态函数。
有些编译器甚至会优化内联函数,通常为避免一些不必要拷贝和构造,提高工作效率。
频繁的调用内联函数和宏定义容易造成代码膨胀,消耗更大的内存而造成过多的换页操作。
https://blog.csdn.net/zwe7616175/article/details/81334711
https://zhidao.baidu.com/question/123304290.html
说明:
使用虚函数,系统会有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(位于类内其他成员前面),是一个指针数组,存放每个虚函数的入口地址。系统在进行动态关联的时间开销很少,提高了多态性的效率。

编译器生成的析构函数都是非虚的,除非是一个子类,其父类有个虚析构函数,此时的虚函数特性继承自基类。有虚函数的类,一般情况下要定义一个虚析构函数。
纯虚函数不能new。
构造函数不能声明为虚函数的原因?
构造一个对象时,必须知道对象实际类型,而虚函数是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功,编译器就无法知道对象的实际类型,是该类本身,还是派生类,还是其他。
虚函数的执行依赖于虚函数表,而虚函数表是在构造函数中进行初始化的,即初始化虚表指针(vptr),使得正确指向虚函数表。而在构造对象期间,虚函数表(vtable)还没有被初始化,将无法进行。
什么情况下,类的析构函数应该声明为虚函数?为什么?
类A中有了虚函数就会在类的数据成员的最前面添加一个vfptr指针(void** vfptr),这个指针用来指向一个vtable表(一个函数指针数组)(一个类只有一个该表),该表存储着当前类的所有虚函数的地址。这样vfptr就成为了一个类似成员变量的存在。访问虚函数的时候通过vfptr间址找到vtable表,再间址进而找到要调用的函数。这样就在一定程度上摆脱了类型制约。
当B类继承A类的时候,因为A中有虚函数,编译器就自动的给B类添加vfprt指针和vtable表。也可以理解为B类继承来了A类中的那个vptr指针成员。(B对象的vfptr浅拷贝到A对象的vfptr)因此,A对象的vfptr所指向的是B对象的虚函数表,而B的析构函数位于书函数表0的位置,因此,这样就可以通过A类对象的指针d,找到B类对象的析构函数,从而在delete d;时,可以销毁B对象,而不会产生内存泄漏和异常。
1 | # bug复现 |
内联函数不能是虚函数
内联函数会在预编译时会进行代码展开,省略函数调用,因此内联函数不能是虚函数。虽然使用inline和virtual共同修饰一个函数时能够通过编译,并在调用时会表现出虚函数的性质,但这是因为编译器在函数声明中遇到virtual关键字时,会选择忽略inline关键字,不进行代码展开。
静态函数不能声明为虚函数
虚函数体现了对象在运行时的多态性,而静态函数属于整个类,不属于某个对象,不能声明为虚函数。
graph TD
classDef className fill:white,stroke:#333,stroke-width:1px;
id(预处理,做优化,生成.i文件)-->id1[编译器,生成.s文件]
id1[编译器,生成.s文件]-->id2[汇编器,生成.o文件]
id2[汇编器,生成.o文件]-->id3[链接器,连接库文件和其他目标文件]
id3[链接器,连接库文件和其他目标文件]-->id4(生成可执行文件)
class id,id1,id2,id3,id4 className;
处理#开始的预编译指令:
(1)宏定义(#define):对宏定义进行字符串替换。
(2)文件包含(#include):文件包含命令把指定头文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件。
(3)条件编译(#ifdef):当满足某条件时语句进行编译,而当条件不满足时则编译另一组语句。条件编译对程序的移植和调试是很有用的。1
2
3
4
5
6
7#ifndef Linux
linux平台下运行的函数
#else
#ifndef windows
Windows平台下运行的函数
#endif
#endif
直接生成了汇编代码
(1)函数重载:同名函数,不同函数输入变量/输出变量的类型/不同功能。
(2)inline函数:如果函数体不太大,对此函数的所有调用都以函数本体去替代,注意inline只是对编译器的一个建议申请,不是强制命令
(3)模板处理:函数定义(包括具现化后的函数模板,类模板的成员函数),变量定义(包括函数模板的静态数据变量,类模板的静态数据变量,类模板的全局对象等)
(4)虚函数:每一个多态class都有一份虚函数表,定义或继承了虚函数的对象会有一个隐含成员:指向虚表的指针vptr,在构造或析构对象的时候,编译器生成的代码会修改这个指针。按道理说,一个多态class的虚表应该恰好被一个目标文件定义,这样链接就不会有错,但c++编译器有时无法判断是否应该在当前编译单元生成虚表定义,为保险起见,只能每个编译单元都生成虚表,然后交给链接器来消除重复数据。
Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.
1 | $ hexo new "My New Post" |
More info: Writing
1 | $ hexo server |
More info: Server
1 | $ hexo generate |
More info: Generating
1 | $ hexo deploy |
More info: Deployment

在实际应用中,通过梯度下降一类的方法需要进行归一化,比如逻辑回归,线性回归,SVM,神经网络,Adaboost,SVM,LR,Knn,KMeans等。但是决策树,朴素贝叶斯,隐马尔可夫等模型不需要归一化。因为前者是基于数值的决策,对单位敏感度高,如果不进行归一化操作可能会导致x/y中有一个变量的影响力$\uparrow$,可能需要更多轮次的迭代才能得到最终解。后者基于交叉熵的信息增益,基于概率分布模型,而概率本身是归一化的(%),所以不需要归一化。
如果你用了L1L2正则的话需要,因为不用正则时,我们的损失函数只是仅仅在度量预测与真实的差距,加上正则后,我们的损失函数除了要度量上面的差距外,还要度量参数值是否足够小。而参数值的大小程度是与特征的数值范围相关的。