学习笔记
1. new和new() 区别
#include "add.h" #include <stdio.h> #include <iostream> #include <string.h> using namespace std; struct A { int m; }; // POD struct B { ~B(){}; int m; }; // non-POD, compiler generated default ctor struct C { C() : m() {}; ~C(){}; int m; }; // non-POD, default-initialising m int main() { A *aObj1 = new A; A *aObj2 = new A(); cout << aObj1->m << endl; cout << aObj2->m << endl; B *bObj1 = new B; B *bObj2 = new B(); cout << bObj1->m << endl; cout << bObj2->m << endl; C *cObj1 = new C; C *cObj2 = new C(); cout << cObj1->m << endl; cout << cObj2->m << endl; delete aObj1; delete aObj2; delete bObj1; delete bObj2; delete cObj1; delete cObj2; return 0; }
执行结果:
0 0 0 0 0 0
2.析构函数能够定义为私有:
通常构造函数/析构函数的声明位于public区段。
如果析构函数设置成私有,不能在栈上创建对象。只能在堆上创建内存。保证只能在堆上new一个新的类对象。
原因:当在栈上生成对象时,对象会自动析构,也就说析构函数必须可以访问。 而堆上生成对象,由于析构时机由程序员控制,所以不一定需要析构函数。保证了不能在栈上生成对象后,需要证明能在堆上生成它。
class One { private: ~One(){ cout<<"destructor\n"; } }; int main() { One one // 这样是不行的,不能访问析构函数 One *one //ok One *one = new One() //ok return 0; }
3.构造函数声明为私有:
在程序中实例化一个对象,编译器将调用构造函数。如果构造函数是private,由于在class外部不允许访问私有成员,将导致编译失败。
对于类本身,可以利用static公有成员,因为它独立于class对象之外,不必构造对象就可以使用它们!在某个static函数中创建该class的对象,并以引用或指针的形式将其返回(不以对象返回,主要是构造函数是私有的,外部不能创建临时对象),就获得了这个对象的使用权。
static成员函数不可以访问非static成员,却可以访问私有的构造函数。
用处:实现这样一个class:它在内存中至多存在一个,或者指定数量个的对象(可以在class的私有域中添加一个static类型的计数器,它的初值置为0,然后在GetInstance()中作些限制: 每次调用它时先检查计数器的值是否已经达到对象个数的上限值,如果是则产生错误,否则才new出新的对象,同时将计数器的值增1。 最后,为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,复制构造函数也要特别声明并置为私有。
class OnlyHeapClass { public: static OnlyHeapClass* GetInstance() { // 创建一个OnlyHeapClass对象并返回其指针 return (new OnlyHeapClass); } void Destroy(); private: OnlyHeapClass() {} ~OnlyHeapClass() {} }; int main() { OnlyHeapClass *p = OnlyHeapClass::GetInstance(); ... // 使用*p delete p; return 0; }
4.多态
C++的多态性具体体现在运行和编译两个方面:在程序运行时的多态性通过继承和虚函数来体现;
在程序编译时多态性体现在函数和运算符的重载上;
虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。
纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在 纯虚函数不具备函数的功能,一般不能直接被调用。
从基类继承来的纯虚函数,在派生类中仍是虚函数。如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。
抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。
5.虚函数
https://blog.csdn.net/weixin_43329614/article/details/89103574
子类的同名虚函数会覆盖父类的虚函数,子类的虚函数表会继承父类的虚函数表,并追加子类自己的虚函数。每个类的所有对象公用一张虚函数表。
#include "add.h" #include <stdio.h> #include <iostream> #include <string.h> using namespace std; class A{ public: virtual void say(){ cout<<"I am father"<<endl; } }; class B:public A{ public: virtual void say(){ cout<<"I am son"<<endl; } }; int main() { A *a=new A; A *b=new B; A *AA[4]; AA[0]=a; AA[1]=b; AA[2]=a; AA[3]=b; for(int i=0;i<4;i++){ AA[i]->say(); } return 0; }
6.变量的内存的分配方式
1.从静态存储区域分配:内存在编译的时候就已经分配好
2.在栈上分配,自动分配和释放
3.在堆上分配,new,malloc分配
7.map和unordered_map
map内部实现了一个红黑树。
缺点: 空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间。
优点:有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作
适用处:对于那些有顺序要求的问题,用map会更高效一些
unordered_map内部实现了一个哈希表。
缺点: 哈希表的建立比较耗费时间。
适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map
8. || 或在前一个成立时不再判断后一个。
9.sizeof是编译时运算符,编译时就确定了 ,可以看成和机器有关的常量。
10.数组和指针的区别
指针可以指向任意的内存区域,包括文字常量区,当指向文字常量区时,对指针进行更改会在运行时出错,但是编译时不会报错。
数组在作为函数入参时会退化为指针,在函数中使用sizeof时指针的大小8,而不是数组的大小。
11.const与#define
const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。修饰函数入参防止被修改,修饰类成员可以防止类成员函数更改类内的变量。修饰返回值防止返回值被改变。
const 有数据类型,编译器进行安全检查。
define字符替换,没有类型安全检查
12.全局对象的构造函数会在main函数之前执行。
class A{ public: A(){ cout<<"this is gouzao"<<endl; } void say(){ cout<<"member func"<<endl; } }; A a; int main(){ char arr[]="hello123"; cout<<sizeof(arr)<<endl; a.say(); }
13.重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?
重载:同名不同参
重写:是指子类重新定义父类虚函数的方法。
14、结构与联合有和区别?
(1). 联合中只有一个成员被赋值, 而结构体的所有成员都可以被赋值。
- 联合体所有成员变量共享内存,相对于联合体首地址偏移量都为0
- 联合体大小是最大成员的size
union u{ char c; //1字节 int i; //4字节 double d; //8字节 }u1; struct s{ char c; int i; double d; }s1; int main(){ u1.c='a'; cout<<sizeof(u1)<<endl; u1.i=2; cout<<sizeof(u1)<<endl; u1.d=12.2; cout<<sizeof(u1)<<endl; s1.c='a'; cout<<sizeof(s1)<<endl; s1.i=2; cout<<sizeof(s1)<<endl; s1.d=12.2; cout<<sizeof(s1)<<endl; }
运行结果:
8 8 8 16 16 16
16.static
用static修饰类数据成员,会被类的所有对象共享,以及被派生类的对象共享,在内存中只占用一份内存空间。
用static修饰类的成员函数,作用是为了能处理静态数据成员,静态成员函数也不属于某一对象,没有this指针。
类的静态成员在类实例化之前就存在了,并分配了内存。
17.new和malloc
1)new能自动计算分配的内存空间,malloc需要传入内存大小。
2)new可调用构造函数,对应的delete可调用析构函数而;malloc不会自动调用构造函数;free也不会自动调用析构函数。
4)new/delete返回的是具体类型的指针,malloc/free返回void类型指针。
malloc分配的内存在堆上开辟的内存空间逻辑上是连续的,物理上不一定连续,malloc分配的内存不进行初始化,返回的是void *类型指针,分配失败返回空指针。
#include<iostream> #include<stdlib.h> #include<string.h> using namespace std; int main(){ char * p=(char *)malloc(101); strcpy(p,"hello,world123"); cout<<sizeof(p)<<endl; cout<<p<<endl; //free(p);释放多次会运行会出问题 free(p); p=NULL; free(p);//空指针可以释放多次,释放之前先赋NULL }
18.volatile
用于多线程环境下修改变量用,cpu直接从对应的内存当中提取,而不从寄存器里读。
多线程环境下,变量的值可能别其他线程修改,但是寄存器里的值不会对应改变。
19.堆栈溢出和内存泄漏
堆栈溢出原因:数组越界/深层次递归调用(每次调用都开辟新的内存空间)/没有回收内存
内存泄漏原因:动态分配后未进行free或delete释放。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。
内存泄漏只能在程运行时发现,编译时无法识别。
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收。
C++内存泄漏原因:
1.堆中创建的内存没有手动释放
2.构造函数中动态分配的内存析构函数中没有释放。
3.指针未释放,直接被新申请的内存指针覆盖,会导致旧内存没有被释放。例如:
const int TIME_WAIT = 100000; //100 ms string redis_cmd = "GET " + redis_key; UB_LOG_TRACE("read output redis command: %s", redis_cmd.c_str()); for (unsigned int i = 1; i <= pay_define::MAX_RETRY_TIMES; i++) { try { redisReply* result = NULL; result = _rd_client.exec_command(redis_cmd.c_str()); if (!result) { UB_LOG_WARNING("get output from redis result is null"); usleep(TIME_WAIT); continue; } if (result->type != REDIS_REPLY_STRING) { if (result->type == REDIS_REPLY_ERROR) { UB_LOG_WARNING("get output from redis result error"); } else if (result->type == REDIS_REPLY_NIL) { UB_LOG_WARNING("get output from redis result is empty"); } freeReplyObject(result); usleep(TIME_WAIT); continue; } //copy redis value to out_pack mc_pack_t * pOutput = const_cast<yapserver::mc_pack_writer&>(out_pack); memcpy((void*)pOutput, result->str, result->len);