持续更新,欢迎纠正~
有需要的同学可以看目录找到相应的知识点
设计思想上:
C++是面向对象的语言,而C是面向过程的结构化编程语言。
语法上:
C++具有封装、继承和多态三大特性。
C++相比较C,增加了许多类型安全的功能,比如强制类型转换、智能指针。
C++支持泛型编程,比如模板类,函数模板等。
在构造函数
后面加冒号,表示冒号后面接构造函数初始化列表(constructor initialize list),主要有三种应用场景:
class thread_guard {
std::thread& t;
int b;
public:
explicit thread_guard(std::thread& x,const int a) : t(x), b(a) {}
~thread_guard();
}
https://blog.csdn.net/lixiaogang_theanswer/article/details/81090622
https://blog.csdn.net/weixin_38339025/article/details/89161324?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.base&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.base
= default:
当我们编写一个类时,若不显著写明,则类会默认为我们提供如下几个函数:构造函数、析构函数、拷贝构造函数、拷贝赋值函数(operator=)、移动构造函数。
简单来说:=defalut是使得函数为编译器默认的形式。
= default只能用于(类的)特殊的成员函数(默认构造函数,复制构造函数,析构函数等)。例如,当我们声明了一个有参构造函数时,编译器就不会创建默认构造函数(无参)。这个时候声明对象时不带参就会出错,此时我们就要自己声明一个无参构造函数。但是自己声明默认构造函数就会失去编译器对于默认构造函数的一些优化,跟POD有关,所以我们可以在自己声明的无参构造函数后面加上=defalut来说明为默认构造函数,这样他就会做默认构造函数会做的一些事情。
= delete:
= delete就是禁用该种函数使用,比如
① 禁用拷贝构造函数
{A& operator=(const A&) = delete}
A a1, a2, a3;
a1 = a2;//错
② 禁用不需要用的参数转换
{A(int) {}
A(double) {}}
A a1(1);//正确
A a2(2.2);//错误,被禁用了
总结:
=defalut使得被修饰的函数为编译器默认的形式。=delete使得编译器禁止该类型的成员函数生成。
对特殊成员函数使用以上修饰符使得代码更容易阅读。
类的大小计算没那么简单,涉及到虚函数成员、静态成员、虚继承、多继承以及空类等,不同情况有对应的计算方式,在此对各种情况进行总结。
首先明确一个概念,平时所声明的类只是一种类型定义,它本身是没有大小可言的。我们这里所说的大小,其实指的是类的对象所占的大小。因此,如果用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小。
记住以下几点:
解释:
https://blog.csdn.net/fengxinlinux/article/details/72836199
在C中定义一个结构体类型要用typedef:
typedef struct student {
int a;
}Stu;
这时声明变量就可以:Stu stu1;
如果上面没有加typedef就必须用struct Student stu1来声明。这里的Stu实际上就是struct Student的别名。
另外这里也可以不写Student(typedef struct {}Stu;那声明变量的时候就不能struct Student stu1;了只能Stu stu1;)
而C++中,直接struct name {}就行了。
另外,在C++中加了typedef又会不一样
① struct name {int a;}stu; 这里stu是一个变量。
② typedef struct name{int a;}std;这里的stu是一个结构体类型。
const只能加在类的成员函数后面(普通函数不可以),也就是说这些成员函数是只读函数。
1、非静态成员函数后面加const(加到非成员函数或静态成员后面会产生编译错误)
2、加了cosnt的成员函数,表示成员函数隐含传入的this指针为const指针,决定了在该成员函数中,任意修改它所在的类的成员的操作都是不允许的(因为隐含了对this指针的const引用)
费静态成员函数后面加const和mutable是反义词
加了const的成员函数可以被非const**对象**
和const对象
调用。注意是对象。
但不加const的成员函数只能被非const对象
调用。
函数前面加cosnt表示返回值是const,函数后面加const表示不可以修改class的成员。
什么时候用?
当不改变数据成员的函数都加上cosnt关键字,提高程序的可读性,提高程序的可靠性,保护数据。比如函数为get_a, get_b, get_sum;
1、常引用:所引用的形参不能被更新
void display(const double& a);
2、常对象:在生存期内不能被更新,但必须被初始化
A const a(3, 4);
3、常成员函数:不能修改对象中数据成员,也不能调用类中没有被const修饰的成员函数(常对象唯一的对外接口)。如果声明了一个常对象,则该对象只能调用它的常函数!另外,可以用于对重载函数的区分。
void print();
void print() const;
4、extern int a;使得其他文件也能访问该变量
声明一个函数或定义函数时,冠以static的话,函数的作用域就被限制在了当前编译单元,当前编译单元内也必须包含函数的定义,也只在其编译单元可见,其他单元不能调用这个函数(每一个cpp文件就是一个编译单元)
内存分配大致上可以分成5块:
栈区(stack)
。栈,就是那些由编译器在需要时分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。(由编译器管理)堆区(heap)
。一般由程序员分配、释放,若程序员不释放,程序结束时可能由系统回收。注意,它与数据结构中的堆时两回事,分配方式类似于链表。全局区(静态区)(static)
。全局变量和静态变量被分配到同一块内存中。程序结束后由系统释放。常量存储区
。常量字符串就是放在这里的,不允许修改,程序结束后由系统释放。程序代码区
。存放函数体的二进制代码。—静态变量只被所属源文件使用。
static关键字有如下几种作用:
1、全局静态变量
在全局变量前加上关键字static,全局变量就被定义成一个全局静态变量。
存放区:存放在静态存储区,在整个程序运行期间一直存在。
初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非它被显式初始化)
作用域:全局静态变量在声明它的文件之外是不可见的,准确来说作用域是从定义之处开始,到文件结尾。
2、局部静态变量
在局部变量前加上关键字static,局部变量就被定义成一个局部静态变量。
存放区:静态存储区。
初始化:未经初始化的局部静态变量会被自动初始化为0(自动对象的值是任意的,除非它被显示初始化)
作用域:作用域仍然是局部作用域,当定义它的语句块结束时,作用域结束。但是当局部静态变量离开作用域后,并没有被销毁,而是仍然驻留在内存当中,只不过我们不能对其进行访问,除非该函数再次被调用,并且该局部静态变量值不变。
3、静态函数
在函数返回类型前加static,函数就被定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只在声明它的文件当中可见,不能被其他文件所用。
这个函数只可以被本cpp内使用,不会和其他cpp中的同名函数引起冲突。
warning:(没懂)不要在头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件中去,否则cpp内部声明需加上static修饰。
4、类的静态成员
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会被破坏隐藏的原则,也就是保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用。
5、类的静态函数
静态成员函数和静态成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名,可以直接使用class_name::static_func()就可以访问。
静态成员函数没有this指针,只能访问静态成员(包括静态成员变量和静态成员函数)
普通成员函数有this指针,可以访问类中的任意成员(普通成员变量和静态成员变量,好像静态成员函数没有this指针不能通过this访问,但是可以通过类名::访问);而静态成员函数没有this指针。
explicit关键字只能用于类的构造函数的声明上。
它的作用是防止构造函数进行的隐式转换。
在C++中,一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数),承担了两个角色。
① 第一是构造
② 第二是默认且隐含的类型转换操作符。即如果构造函数接收到的参数会默认进行隐式转换。
隐式转换看起来很方便,但是某些情况下违背了程序员的本意。这个时候就要加上explicit修饰,指定这个构造器只能被明确的调用/使用,不能进行隐式转换。
class A
{
public:
explicit A(int a, int b) : m_a(a), m_b(b) {};
private:
int m_a;
int m_b;
}
int main()
{
A test(1, 2); //正确
A test1(2.2, 2); // 错误,存在double->int的隐式转换。
}
待更新
四中cast转换是C++的强制转换。
cast-name
1、const_cast
唯一一个可以改变const性质的转换
2、static_cast
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast
3、dynamic_cast
用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果时非法的对于指针返回null,对于引用抛出异常。要深入理解内部转换的原理
向上转换:指的是子类向基类的转换。
向下转换:指的是基类向子类的转换。
它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。
4、reinterpret_cast
几乎什么都可以转,可能会出问题,尽量少用。
5、为什么不用C的强制转换?
C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。
C的强制转换 (type_name) expression
mutable的中文意思时可变的、易变的,跟constant(即C++里的const)是反义词。在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量(mutable只能用于修饰类的非静态数据成员),将永远处于可变的状态(可以修改成员数据之类的),即使在一个const函数中。
如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能修改,且该返回值只能被赋给加const的同类型指针。一般只在返回值为引用或指针时使用,返回其他值时没有必要。
const char* get_string(void);
char *str = get_string();//编译报错
const char *str = get_string();//正确
#define N 2+3
const double a = N/2; //结果是2+3/2 = 2+1 = 3
const double b = (double)N / (double)2;
//我们预想的答案是2.5,可实际输出的值是2 + 3 / 2 = 3.5
在C++程序中一般只使用const常量而不是用宏常量,即const常量完全取代宏常量。
宏可以提高程序的易读性,比如数组大小,模式mode
C++中的static对象是指存储区不属于stack和heap、“寿命”从被构造出来直至程序结束为止的对象。这些对象包括全局对象,定义于namespace作用域的对象,在class、function以及file作用域中被声明为static的对象。其中,函数内的static对象成为local static对象,而其他static对象成为non-local static对象。
这两者在何时初始化(构造)的问题上存在细微的差别:
建议:
关联式容器:map,multimap,set,multiset(代表允许重复元素)
中包含pair这种对组的结构体。
函数get_allocator()用于获取map或multimap的内存配置器,内存配置器类似于指针的首地址
没写文,更多请看原文
auto_ptr只能对new分配的内存使用,不能对new[]的使用
algorithm中的for_each(T.begin(), T.end(), function); find(T.begin(), T.end(), value)
T.reserve(x):预留空间。实际空间小于它则扩充。
没写完,更多请看原文。
- 容器适配器:包括栈(stack)、队列(queue)、优先队列(priority_queue)。使用容器适配器,stack就可以被实现为基本容器类型(vector、dequeue、list)的适配。可以把stack看做是某种特殊的vector、deque或者list容器,只是其操作仍然收到stack本身属性的限制。queue和priority_queue与之类似。容器适配器的接口更为简单,只是受限比一般容器更多。
容器适配器也是同样的道理,简单的理解容器适配器,其就是将不适用的序列式容器(包括vector、deque和list)变得适用。容器适配器的底层实现和模板A,B的关系也是完全相同的,即通过封装某个序列式容器,病重新组合该容器中包含的成员函数,使其满足某些特定场景的需要。
容器适配器本质上还是容器,只不过次容器模板类的实现,利用了大量其他基础容器模板类中已经写好的成员函数。当然,如果必要的话,容器适配器中也可以自创新的成员函数。
需要注意的时,STL中的容器适配器,其内部使用的基础容器并不是固定的,用户可以在满足特定条件的多个基础容器中自由选择。
- 迭代器适配器:修改为某些基本容器定义的迭代器的接口的一种STL组件。反向迭代器和插入迭代器都属于迭代器适配器,迭代器适配器扩展了迭代器的功能。
- 函数适配器:通过转换或者修改其他函数对象使其功能得到扩展。这一类适配器由否定器(相当于“非”操作)、帮顶起、函数指针适配器。函数对象适配器的作用就是使函数转化为函数对象,或者将多参数的函数对象转化为少参数的函数对象。
STL的分配器(allocator)用于封装STL容器在内存管理上的底层细节。在C++中,其内存配置和释放如下:
- new运算分两个阶段:① 调用::operator new配置内存;② 调用对象构造函数构造对象内容
- delete运算分两个阶段:① 调用对象析构函数;② 调用::operator delete释放内存。
为了精密分工,STL allocator将两个阶段操作区分开来:内存配置由alloc::allocate()负责,内存释放由alloc::deallocate()负责;对象构造由::construct()负责,对象析构由::destroy()负责。- 同时为了提升内存管理的效率,较少申请小内存造成的内存碎片问题,SGI STL采用了两级适配器,当分配的空间大小炒作128B时,会使用第一级空间配置器;当分配的空间大小小于128B时,将使用第二级空间配置器。第一级空间配置器会直接使用malloc(),realloc(),free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。
程序编译是指将源文件翻译成二进制目标代码的过程。主要是检查语法错误,正确的源程序文件经过编译后在磁盘上生成目标文件。便已产生的目标文件是可重定位的程序模块,不能直接运行。链接则是把目标文件和其他分别进行编译生成的目标程序模块以及系统提供的标准库函数链接在一起,生成可运行的可执行文件。
c++中,class和struct都可以定义一个类。它们有以下两点区别:
1、默认继承权限,如果不指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理。
2、成员的默认访问权限。class的成员默认是private权限,struct默认是public权限。
以上两点(权限)是struct和class最基本的差别,也是最本质的差别。
但是语义上一般倾向于把struct昂做C时代的struct来用,即只有成员变量,没有逻辑(或只有极其简单的数据存入读取逻辑),用来把多个变量打包成一个类型,而不用struct来做面向对象变成意义上的class。这个是编码风格的范畴,为的是方便代码阅读。
都看到这句话了不如点个赞吧~~(收藏不赞,增益减半)