内存分配大致上可以分成5块:
函数声明时加上explicit可以阻止函数参数被隐式转换。
Class A
{
explicit A(int a);
}
Void main()
{
A a1=12; //不加explicit时会被隐式转换位 A a1=A(12);加了此时编译器会报错。
}
被声明为explicit的构造函数通常比non-explicit 函数更受欢迎。
mutalbe的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量(mutable只能由于修饰类的非静态数据成员),将永远处于可变的状态,即使在一个const函数中。
我们知道,假如类的成员函数不会改变对象的状态,那么这个成员函数一般会声明为const。但是,有些时候,我们需要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutalbe来修饰。(使用mutable修饰的数据成员可以被const成员函数修改)。
如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。例如函数
Const char * GetString(void);
// 如下语句将出现编译错误:
char*str = GetString();
// 正确的用法是
Const char *str =GetString();
一般只在返回值为引用和指针时使用,返回其他值时没有这个必要。
C++中的static对象是指存储区不属于stack和heap、"寿命"从被构造出来直至程序结束为止的对象。这些对象包括全局对象,定义于namespace作用域的对象,在class、function以及file作用域中被声明为static的对象。其中,函数内的static对象称为local static对象,而其它static对象称为non-local static对象。
这两者在何时被初始化(构造)这个问题上存在细微的差别:
建议:
关联式容器:map,multimap和set,multiset(multi代表允许重复元素)。
< map>中包含 pair这种对组的结构体。
函数get_allocator()用于获取map或multimap的内存配置器,内存配置器类似于指针的首地址。
MAP::allocator_type m1_alloc;
MAP m1;
m1_alloc=m1.get_allocator();
int ctg=count_if(myvector.begin(),myvector.end(),bind2nd(greater<int>(),2))
bind2nd(greater<int>(),2)表示数值大于2的情况,为真时才执行
//类似的:
find_if(myvector.begin(),myvector.end(),bind2nd(greater<int>(),3))
- auto_ptr只能对new分配的内存使用,不能对new[]的使用。
- algorithm 中的for_each(T.begin(),T.end(),function);find(T.begin(),T.end(),value)
- T.reserve(x):预留空间。实际空间小于它则扩充
迭代配接器是特殊的迭代器,可使算法能够以逆向,安插模式进行工作。
比如:
- 逆向迭代器:T.rbegin(),T.rend(),rbegin()指向最后一个元素,++操作逐次向前。
- 插入型迭代器:T.front_inserter(),T.back_inserter(),
copy(d1.begin(),d1.end(),inserter(d2,d2.begin()):把每个元素都插在前一个元素的前面- 流迭代器:copy(d1.begin(),d1.end(),ostream_iterator(cout,”, ”)):把d1复制到输出流上输出,每个元素以,隔开。
- 迭代器辅助函数:advance():使迭代器前进或后退。如advance(it,-1);
distance():求两个迭代器之间的距离,要求同类型而且前者可以自增到后者
iter_swap():交换两个迭代器的所知内容。
- 容器适配器:包括栈(stack)、队列(queue)、优先(priority_queue)。使用容器适配器,stack就可以被实现为基本容器类型(vector,dequeue,list)的适配。可以把stack看作是某种特殊的vctor,deque或者list容器,只是其操作仍然受到stack本身属性的限制。queue和priority_queue与之类似。容器适配器的接口更为简单,只是受限比一般容器要多。
- 迭代器适配器:修改为某些基本容器定义的迭代器的接口的一种STL组件。反向迭代器和插入迭代器都属于迭代器适配器,迭代器适配器扩展了迭代器的功能。
- 函数适配器:通过转换或者修改其他函数对象使其功能得到扩展。这一类适配器有否定器(相当于"非"操作)、绑定器、函数指针适配器。函数对象适配器的作用就是使函数转化为函数对象,或是将多参数的函数对象转化为少参数的函数对象。
STL的分配器(allocaotr)用于封装STL容器在内存管理上的底层细节。在C++中,其内存配置和释放如下:
- new运算分两个阶段:(1)调用::operator new配置内存;(2)调用对象构造函数构造对象内容
- delete运算分两个阶段:(1)调用对象希构函数;(2)掉员工::operator delete释放内存
为了精密分工,STL allocator将两个阶段操作区分开来:内存配置有alloc::allocate()负责,内存释放由alloc::deallocate()负责;对象构造由::construct()负责,对象析构由::destroy()负责。- 同时为了提升内存管理的效率,减少申请小内存造成的内存碎片问题,SGI STL采用了两级配置器,当分配的空间大小超过128B时,会使用第一级空间配置器;当分配的空间大小小于128B时,将使用第二级空间配置器。第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。
Char a=’c’;
Char& b=a;
Const int& rc=a;
b=’x’;
const引用类型初始化时前后数据类型不一致时,生成一个新的只读类型。此时b改变a的值时,rc所指的值不变。
C++不支持引用数组
class danli
{
private:
danli()
{
}
static danli* instance;
public:
static danli* getInstance()
{
if (instance == NULL) // instance == NULL不代表instance一定没被new过
{
// 双重判断,避免多线程环境下每次调用getInstance()函数都会先加锁再判断
// 只有在instance == NULL时再加锁去申请对象
unique_lock<muytex> mymute(resource_mutex);
if (instance == NULL){
instance = new danli();
static huishou h1;
}
}
return instance;
}
class huishou
{
public:
~huishou()
{
if (instance)
{
delete instance;
instance = NULL;
}
}
};
静态成员变量在main()之前创建,再main()之后回收。
全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。
C++中,class与struct都可以定义一个类。他们有以下两点区别:
如果父类中存在有虚函数,那么编译器便会为之生成虚表(属于类)与虚指针(属于某个对象),在程序运行时,根据虚指针的指向,来决定调用哪个虚函数,这称之与动态绑定,与之相对的是静态绑定,静态绑定在编译期就决定了。
auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错.
此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!
采用所有权模式,还是上面那个例子
unique_ptr<string> p3 (new string ("auto")); //#4
unique_ptr<string> p4; //#5
p4 = p3;//此时会报错!!
编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。
- shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。
- 成员函数
use_count 返回引用计数的个数
unique 返回是否是独占所有权( use_count 为 1)
swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get()是等价的
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言(而不是C++)的方式进行编译。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
这个功能十分有用处,因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。
每一个函数调用都会分配函数栈,在栈内进行函数执行过程。调用前,先把返回地址压栈,然后把当前函数的esp指针压栈。(ESP(Extended Stack Pointer)为扩展栈指针寄存器,是指针寄存器的一种,用于存放函数栈顶指针)
C语言参数压栈顺序?:从右到左
不能。如果是这种情况下,调用拷贝构造函数的时候,首先要将实参传递给形参,这个传递的时候又要调用拷贝构造函数(aa = ex.aa; //此处调用拷贝构造函数)。。如此循环,无法完成拷贝,栈也会满。
编译器预处理阶段查找头文件的路径不一样
对于C/C++编写的程序,从源代码到可执行文件,一般经过下面四个步骤:
内存泄漏通常是因为调用了malloc/new等内存申请操作,但是缺少了对应的free/delete。
为了判断内存是否泄漏,我们一方面可以使用Linux环境下的内存泄漏检查工具Valgrind,另一方面我们写代码的时候,可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否有泄漏。
内存泄漏分类:
一句话来说,段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址等等情况。这里贴一个对于“段错误”的准确定义。
总结起来整个过程就三步:
1)根据调用的函数名找到函数入口;
2)在栈中审请调用函数中的参数及函数体内定义的变量的内存空间
3)函数执行完后,释放函数在栈中的审请的参数和变量的空间,最后返回值(如果有的话)
详细请查阅:函数调用过程 / C/C++函数调用过程分析
struct Stu
{
int id;
char sex;
float hight;
};
那么一个这样的结构体变量占多大内存呢?也就是
cout<
然而事实并非如此!
字节对齐原则:在系统默认的对齐方式下:每个成员相对于这个结构体变量地址的偏移量正好是该成员类型所占字节的整数倍,且最终占用字节数为成员类型中最大占用字节数的整数倍。
在这个例子中,id的偏移量为0(0=40),sex的偏移量为4(4=14),hight的偏移量为8(8=24),此时占用12字节,也同时满足12=34.所以sizeof(Stu)=12.
struct A {
char y;
char z;
long long x;
}; 16字节
struct A {
char y;
char z;
int x;
}; 8字节
struct A {
char y;
char* z;
int x;
};12字节
struct A {
char y;
}; 1字节
详细请查阅:struct/class等内存字节对齐问题详解
详细请查阅:C++(vs)多线程调试 (转)
(过程)优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗源;比如嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。缺点:没有面向对象易维护、易复用、易扩展。
(对象)优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统。缺点:性能比面向过程低。
Class B;
Class D : public B;
B& b;
D& d;
B& b1 = d ; //父类可以作为子类的引用,此时b1表现和指针形式一致(会调用B的非虚函数)
D& d1 = b; //错误,不能将子类作为父类的引用
详细请参阅:C++ 模板类的声明与实现分离问题 / C++ 模板类的声明与实现分离问题(模板实例化)
如果一个类是这样定义的:
Class A
{
public:
A(int pram1, int pram2, int pram3);
privite:
int a;
int &b;
const int c;
}
假如在构造函数中对三个私有变量进行赋值则通常会这样写:
A::A(int pram1, int pram2, int pram3)
{
a=pram1;
b=pram2;
c=pram3;
}
但是,这样是编译不过的。因为常量和引用初始化必须赋值。所以上面的构造函数的写法只是简单的赋值,并不是初始化。
正确写法应该是:
A::A(int pram1, int pram2, int pram3):b(pram2),c(pram3)
{
a=pram1;
}
采用初始化列表实现了对常量和引用的初始化。采用括号赋值的方法,括号赋值只能用在变量的初始化而不能用在定义之后的赋值。
凡是有引用类型的成员变量或者常量类型的变量的类,不能有缺省构造函数。默认构造函数没有对引用成员提供默认的初始化机制,也因此造成引用未初始化的编译错误。并且必须使用初始化列表进行初始化const对象、引用对象。
头文件:#include
memset() 函数用来将指定内存的前n个字节设置为特定的值,其原型为:
void * memset( void * ptr, int value, size_t num );
参数说明:
【函数说明】memset() 会将 ptr 所指的内存区域的前 num 个字节的值都设置为 value,然后返回指向 ptr 的指针。
无法下面这样初始化,这样的结果是a被赋值成168430090,168430090.。。。。。。。。。
int a[10];
memset(a, 1, sizeof(a));
这是因为int由4个字节(说)表示,并且不能得到数组a中整数的期望值。
但我经常看到程序员使用memset将int数组元素设置为0或-1。其他值不行!
int a[10];
int b[10];
memset(a, 0, sizeof(a));
memset(b, -1, sizeof(b));
//假设a为int型数组:
memset(a,0x7f,sizeof(a));
//a数组每个空间将被初始化为0x7f7f7f7f,原因是C函数传参过程中的指针降级,导致sizeof(a),返回的是一个 something*指针类型大小的的字节数,如果是32位,就是4字节。所以memset按字节赋值。
memset(a,0xaf,sizeof(a));
//a数组每个空间将被初始化为0xafafafaf
总结:建议编译器不要对该变量进行优化
volatile是“易变的”、“不稳定”的意思。volatile是C的一个较为少用的关键字,它用来解决变量在“共享”环境下容易出现读取错误的问题。
定义为volatile的变量是说这变量可能会被意想不到地改变,即在你程序运行过程中一直会变,你希望这个值被正确的处理,每次从内存中去读这个值,而不是因编译器优化从缓存的地方读取,比如读取缓存在寄存器中的数值,从而保证volatile变量被正确的读取。
在单任务的环境中,一个函数体内部,如果在两次读取变量的值之间的语句没有对变量的值进行修改,那么编译器就会设法对可执行代码进行优化。由于访问寄存器的速度要快过RAM(从RAM中读取变量的值到寄存器),以后只要变量的值没有改变,就一直从寄存器中读取变量的值,而不对RAM进行访问。
而在多任务环境中,虽然在一个函数体内部,在两次读取变量之间没有对变量的值进行修改,但是该变量仍然有可能被其他的程序(如中断程序、另外的线程等)所修改。如果这时还是从寄存器而不是从RAM中读取,就会出现被修改了的变量值不能得到及时反应的问题。如下程序对这一现象进行了模拟。
#include
using namespace std;
int main(int argc,char* argv[])
{
int i=10;
int a=i;
cout<<a<<endl;
_asm
{
mov dword ptr [ebp-4],80
}
int b=i;
cout<<b<<endl;
}
/*
程序在VS2012环境下生成Release版本,输出结果是:
10
10
*/
阅读以上程序,注意以下几个要点:
详细请参考:
https://blog.csdn.net/weixin_41656968/article/details/80958973
https://www.cnblogs.com/god-of-death/p/7852394.html
https://blog.csdn.net/garrulousabyss/article/details/83500576
需要注意一下几点:
当右值引用和模板结合的时候,就复杂了。T&&并不一定表示右值引用,它可能是个左值引用又可能是个右值引用。例如:
template<typename T>
void f( T&& param){
}
f(10); //10是右值
int x = 10; //
f(x); //x是左值
如果上面的函数模板表示的是右值引用的话,肯定是不能传递左值的,但是事实却是可以。**这里的&&是一个未定义的引用类型,称为universal references,它必须被初始化,它是左值引用还是右值引用却决于它的初始化,**如果它被一个左值初始化,它就是一个左值引用;如果被一个右值初始化,它就是一个右值引用。
注意:只有当发生自动类型推断时(如函数模板的类型自动推导,或auto关键字),&&才是一个universal references。
所谓转发,就是通过一个函数将参数继续转交给另一个函数进行处理,原参数可能是右值,可能是左值,如果还能继续保持参数的原有特征,那么它就是完美的。
void process(int& i){
cout << "process(int&):" << i << endl;
}
void process(int&& i){
cout << "process(int&&):" << i << endl;
}
void myforward(int&& i){
cout << "myforward(int&&):" << i << endl;
process(i);
}
int main()
{
int a = 0;
process(a); //a被视为左值 process(int&):0
process(1); //1被视为右值 process(int&&):1
process(move(a)); //强制将a由左值改为右值 process(int&&):0
myforward(2); //右值经过forward函数转交给process函数,却称为了一个左值,
//原因是该右值有了名字 所以是 process(int&):2
myforward(move(a)); // 同上,在转发的时候右值变成了左值 process(int&):0
// forward(a) // 错误用法,右值引用不接受左值
}
上面的例子就是不完美转发,而c++中提供了一个std::forward()模板函数解决这个问题。将上面的myforward()函数简单改写一下:
void myforward(int&& i){
cout << "myforward(int&&):" << i << endl;
process(std::forward<int>(i));
}
myforward(2); // process(int&&):2
上面修改过后还是不完美转发,myforward()函数能够将右值转发过去,但是并不能够转发左值,解决办法就是借助universal references通用引用类型和std::forward()模板函数共同实现完美转发。例子如下:
#include
#include
#include
using namespace std;
void RunCode(int &&m) {
cout << "rvalue ref" << endl;
}
void RunCode(int &m) {
cout << "lvalue ref" << endl;
}
void RunCode(const int &&m) {
cout << "const rvalue ref" << endl;
}
void RunCode(const int &m) {
cout << "const lvalue ref" << endl;
}
// 这里利用了universal references,如果写T&,就不支持传入右值,而写T&&,既能支持左值,又能支持右值
template<typename T>
void perfectForward(T && t) {
RunCode(forward<T> (t));
}
template<typename T>
void notPerfectForward(T && t) {
RunCode(t);
}
int main()
{
int a = 0;
int b = 0;
const int c = 0;
const int d = 0;
notPerfectForward(a); // lvalue ref
notPerfectForward(move(b)); // lvalue ref
notPerfectForward(c); // const lvalue ref
notPerfectForward(move(d)); // const lvalue ref
cout << endl;
perfectForward(a); // lvalue ref
perfectForward(move(b)); // rvalue ref
perfectForward(c); // const lvalue ref
perfectForward(move(d)); // const rvalue ref
}
上面的代码测试结果表明,在universal references和std::forward的合作下,能够完美的转发这4种类型。
详细请查阅:C++:浅谈右值引用 / 左值和右值区别 / 我理解的右值引用、移动语义和完美转发