const things
1.const常量与#define宏定义常量区别
const常量编译时期可以进行安全检查,#define宏定义并没有具体的数据类型,只是字符替换罢了,不能安全检查
2.const与指针
const char* a; //指向const char的指针
char const* a; //指向 const char的指针
char* const a; //const* 指向char
const char* const a; //const*指向 const char
我记得之前在哪看到过一句话,就是阅读这样的代码,从右向左读就好。
另外,不能用void 保存const对象的指针,必须用const void*类型来保存const对象的地址。
const int p=1;
const void* vp=&p;
void* vp=&p;//!!!error
是否应该将void func(int x)改写为void func(const int &x),以便来提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而且复制也非常快,值传递跟引用传递的效率几乎相当.
void fun(const int i)
{
i=10;//!!!error
}
void fun(const char* src,char* des)
{
strcpy(des,src);
}
上面第一种会报错,因为有const修饰int,不可改变值;当然你可以里面不改变i,编译不会报错,程序也没有问题,但是没有意义,因为按照值传递,这个值本来就是个copy量,超了这个函数的作用域里面就没用了,属于过保护了,这时候就会有人问了,那什么时候应该用呢?在指针与引用的时候,主要是不改变之前的值,你看第二个fun, const char* src是--指向const char的指针变量,那么就意味着这时候指针指向的值是个常量不可在函数体中被更新赋值,而引用是指针的一种变体,所以同理。
const指针可以接受非const与const指针,但非const指针只能接受非const指针。
static things
在C++中,static静态成员变量不能在类中进行初始化,在类里面只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化。(这一段话作者光城是放在 const里面的,估计是个引子,承接static)。
static变量的生命周期是在整个程序的生命周期内的,而且是属于初始就分配一个空间,然后整个程序共用这个,我做C#写工控程序的时候,一般仪器之类的都会用static来做,因为这些仪器意味着程序开始到结束都会存在。但我也听说过一句话,将所有变量都用静态分配,那将是一个灾难。
inline things
内联函数是可以提高函数效率的,但并不是绝对的,而且内联是以膨胀为代价的,他是要把你的代码在编译阶段就替换到指定的位置,所以这很老蒋(以空间来换时间)。所以有时候就涉及到什么时候用与什么时候不用:
1.如果内联函数体内代码多,这时候会导致内存消耗过大,不宜用。
2.如果函数体内出现循环,那么执行函数体内代码的开销要比函数调用更达,此时也不宜使用。
3.涉及到虚函数的,虚函数也是可以内联函数的,但如果涉及到多态就不行了。个人猜测是因为inline是在编译的阶段就进行了代码替换,而多态只有在运行的时候才知道调用的是哪个派生类。
个人一般喜欢在一两句return变量的时候用隐式内联函数。
哦,对了,好像是在a tour of cpp上看到一句话的意思是,虽然显式的写上inline,但编译器是执行者,它可以选择采纳与不采纳这个建议,实际的内联函数是由编译器自身优化后的选择,编写代码者只是把想要写成的内联函数推荐给编译器,再由编译器内部进行选择与执行。
siezof things
之前根本没有考虑过类的大小与计算,看了一下有点豁然开朗的感觉。
1.空类的大小是一个字节
2.一个类中,虚函数本身、成员函数(包括静态和非静态)和静态数据成员都不占用类对象的储存空间。
a.成员函数是不占内存的,成员函数只是创建一个作用域,在这个作用域里面进行计算走逻辑,一般情况下出了作用域就清空了,除非有些地方new并且没delete
b.静态数据成员是因为编译的时候就已经分配好内存了,应该是在静态储存区
3.对于包含虚函数的类,不管多少个虚函数,其实只有一个虚指针大小
a.虚函数是因为要存在一个虚函数指针,并且指向虚函数表,是通过改变指针下标来指向同一个类的不同虚函数,所以有几个虚函数所占的内存跟只有一个虚函数所占内存是一样的。我在x64上面编译,vptr占8个字节,字节对齐(8个)
4.普通继承,派生类继承了所有基类的函数与成员,要按照字节对齐来计算大小
5.虚函数继承,不管是单继承还是多继承,都只是继承基类的vptr。多继承的话是继承多个基类的vptr。
#include <iostream>
class A{};
class B
{
public:
int a;
int b;
int c;
virtual void func(){}
};
class C
{
public:
virtual void func(){}
};
class D
{
public:
virtual void func(){}
};
class E:public C,public D
{
};
int main()
{
std::cout<<sizeof(A)<<std::endl;
std::cout<<sizeof(B)<<std::endl;
std::cout<<sizeof(C)<<sizeof(D)<<sizeof(E)<<std::endl;
return 0;
}
pure virtual And abstract things
纯虚函数就是没有函数体直接=0;抽象类就是包含纯虚函数的类
class Abstractfunc
{
public:
virtual void show()=0;
void a(){...}
}
抽象类不能直接实例化,需要派生类去覆写他的全部纯虚函数,否则这个派生类也是个抽象类。
纯虚函数与抽象类主要应用在:接口定义,多态性实现、代码复用与规范等方面。像编写画图界面的时候需一个抽象基类shape,然后因为画图所以需要一个纯虚函数draw,至于画什么由派生类circle、dot、rectangle等图形类来覆写他,一般你只要传一个shape指针过去,至于是什么则在实现的是选择,用static_cast、dynamic_cast转换一下
vptr vtable things
这里是虚函数指针与虚函数表,虚函数指针是直接继承,然后有override就会直接override原有表中的虚函数,顺序是跟基类函数一致的,如果派生类自身新建的虚函数则跟在后面。多重继承的话会存在多个虚函数表,上一句派生类自身的新建虚函数跟在哪个表后面,我猜测是第一个。
这段作者里面取函数运行的地址以及后面(*f1)(),通过函数地址运行函数,还是很有意思的。
virtual things
默认参数是静态绑定的,虚函数是动态绑定的。默认参数的使用需要看指针或者引用本身的类型,而不是对象的类型。
啥意思呢?就是你用基类的指针可以调用派生类的函数,这就是多态,但是呢如果你用基类的指针那么参数就是默认是基类的。
1.静态函数可以声明成为虚函数么?
答:不可以,静态函数不可声明为虚函数,同时不能被const、volatile关键字修饰。
a.static成员函数不属于任何类对象或者类实例,所以加上virtual也没有任何意义
b.虚函数主要依靠的是vptr与vtable来处理的,这个vptr的作用域就是在这个类中,是通过类的构造函数生成,析构函数清理掉的,只能通过this指针来访问。而静态函数作用域是整个程序声明周期,根本没有this这个指针。
2.构造函数可以声明为虚函数么?
答:不可以,虽然虚函数表编译的时候已经建立了,但虚函数指针vptr是要在运行的时候才会产生。构造函数时要初始化实例的,必须时明确的。虚函数则不然,就是为了不明确,运行的时候来确定的。就像医院那么多科室,就是构造函数嘛,各个科室都有,因为医院不知道来的病人有哪些病,需要挂哪个科。
这边有个很有意思的地方,虚复制构造函数,就是基类中有个static 创建基类指针的静态函数,然后基类中还有个Clone虚函数,每个派生类都要有个复制构造函数,再覆写Clone.但是这个并不属于虚拟构造函数啊,只是绕道而已。
3.析构函数可以成为虚函数么?
答:不要有疑问,当然可以,最好可以,一定可以。只要一个是一个基类,就应该声明成虚析构函数,不用管析构函数里面有没有代码之类的。
4.虚函数可以设置成私有函数么?
答:vptr会被派生类继承,一般我看都是protected或者public,私有函数的话应该编译应该会有问题。看作者说可以。a.基类跟main函数要声明为基类的friend,然后虚函数在基类中属于private,但因为指针调用父类,这时候父类要写成public,2.基类public,父类private。我觉得这就搞得很***钻,总觉得实际意义不大。
5.虚函数可以内联么?
答:虚函数是可以内联的,但是一旦涉及到多态的话,就不能内联。因为内联是编译时期替换代码来顶替函数调用,固定写死了没了灵活性,多态则是灵活多变,在运行阶段肆意变换。
volatile things
volatile这个修饰词主要应用:
1.硬件设备
a.硬件储存器
int* output=(unsigned int*) oxff800000;//定义一个IO端口
int init(void)
{
int i;
for(i=0;i<10;i++)
{
*output=i;
}
}
编译器很聪明的,他发现这段代码最终的结果就是给*output赋值9;这时候编译器开始整活,优化了代码变成:
int* output=(unsigned int*) oxff800000;//定义一个IO端口
int init(void)
{
*output=9;
}
但其实你硬件初始化的时候就要从0到9一个个写入才能初始化完成,编译器自作聪明的结果导致你一直初始化失败,你从代码逻辑中还找不到原因,这时候就问你气不气,恨不恨!!!
volatile int* output=(unsigned int*) oxff800000;//定义一个IO端口
这时候volatile的作用就显示出来了,就是很直白的告诉编译器,你很聪明,但别把你的聪明用在这个变量上,我喜欢原汁原味的,懂伐?
b.中断服务
static int i=0;
int main()
{
while(1)
{
if(i)
dosomething();
}
}
//interrupt service routine
void IRS()
{
i=1;
}
逻辑看起来也很一目了然,就是中断机制,希望通过中断来控制i的值变化去做事,编译器有可能在优化的时候,觉得main函数里面不存着修改i这个值,所以就只执行一次从寄存器里面对i的读操作,然后每次判断都用i的副本进行判断,那不是歇菜了...
2.多线程应用
volatile bool bStop=false;
void threadFunc1()
{
while(!bStop)
{}
}
void threadFunc2()
{
bStop=true;
}
这种因为bStop在第一个线程的时候已经被读进了寄存器中,对于第一个线程来说寄存去中的值固定了,所以这个时候就需要volatile让程序每次都从内存中读值。
适用volatile的时候,其实是要理解内存、cpu寄存器的基本概念的,程序都需要在内存上开辟空间,编译的时候编译器优化可能会把有些值copy进了CPU寄存器上,不从内存直接读值了,这样程序有些地方改了内存的值,没有同步更新到CPU寄存器上,两边值不一致,就会引发意想不到的Bug,所以且行且珍惜。
assert things
bit field things
extern
这个主要针对于C++会用到C写的库文件,然后两边编译出来的函数名规则不一样,所以需要定义这个库文件的编译方式
struct things
个人理解C++中的struct其实就是class,只不过在变量里面public这些访问权限不一样,struct应该是默认public的,class是private的。偷懒的话都class好了。如果都写struct就太怪了...
union things
联合就是为了节省空间,多个数据成员共用一个内存,内存是按最大的那个成员变量来的。
explicit things
这个关键字是显示声明,主要是防止隐式转换,我再Qt中看很多构造函数上来就进行显式声明。
friend things
友元其实是破坏了程序的private,可以通过友元来访问一个程序中的私有类型成员。友元函数声明在区域中,定义在类的外部
友元类也是差不多这样的意思,主要的目的应该是想少些代码走友元访问变量与成员函数。
using things
一个主要是namespace这边,一个是对于重载派生类要用基类的函数.
enum things
enum一般要在namespace里面,主要的作用是限制其作用域。C++11之后再枚举后面加了class,推荐这种写法。
enum class Color
{
RED=1,
GREEN,
BLUE
};
Color c=Color::RED;
cout<<"static_cast<int>(c)<<endl;
decltype things
推导类型,类似于auto吧,提高代码的可读性。auto是个占位符,=右边变量必须先声明,然后auto才可以推导。decltype则是推导的表达式,decltype(expression).
pointer、reference things
引用必须初始化,指针可以不必初始化,因为引用不能为空,但指针可以为空,
指针可以随时改变其指向,但引用一旦绑定就只能指向初始化的对象。
引用一般可以理解为特别的指针,他在性能上是跟指针一样的。
macro things
目前用的最多的是在头文件里面 #ifndef #define #endif之类的。
宏就是在预编译阶段将替换字符串,其中还有##是指前后两个连接,#@单引号,#双引号,还有个\续行操作符。