typeid函数的主要作用就是让用户知道当前的变量是什么类型的,比如使用typeid(a).name()就能知道变量a是什么类型的。typeid()函数的返回 类型为type_info类型的引用
运行时的多态是通过vtable里面插入的std::type_info来做的,然后在运行时通过取vtable的type_info来达到目的。
如果表达式的类型是类类型且至少包含有一个虚函数,则typeid操作符返回表达式的动态类型,需要在运行时计算;否则,typeid操作符返回表 达式的静态类型,在编译时就可以计算。
type_info的name成员函数返回C-style的字符串,用来表示相应的类型名,但务必注意这个返回的类型名与程序中使用的相应类型名并不一定一 致(往往如此,见后面的程序),这具体由编译器的实现所决定的,标准只要求实现为每个类型返回唯一的字符串
const 语义是只读,所以可以在运行时进行初始化。const是**变量类型名的一部分,**即 part of type name,一个名字叫“const T”或者“T const”的类型,和T这个类型本身处于一种平级的关系,和T不同的就在于这个类型的对象自产生后就不能再更改了。
constexpr 语义是常量,所以编译时就要进行初始化。constexpr是声明的一部分,即 part of a declaration,他不是变量类型的一部分。当他出现在一个变量的声明中时,他要求编译器在编译期间就初始化并确定下来这个变量(否则编译报错);当他出现在一个函数的声明中时,他要求至少有一条return路径可以(但不是必须)在编译中确定下来,即返回的是编译期常量。
二者的联系就在于,在使用constexpr声明一个类型为T的变量时,constexpr会自动把这个变量定义为const T类型。即constexpr在完成它本职工作(告诉编译器这是个编译期常量)的同时,还把原来的T类型改为了const T类型。这就是二者的联系
C++11线程之间的锁有:互斥锁、条件锁、自旋锁、读写锁、递归锁
从代码到可执行二进制文件的角度上来说,const生效于编译阶段
从程序执行的角度上说,const变量必须在定义的时候初始化,这也导致如果类的成员变量有const类型的变量,该变量必须在类的初始化列表中进行初始化
传引用才有用
传值是形参创建一个和实参相同的临时变量,无论是否对该临时变量使用const,都不会影响实参的值
只有在形参是传引用或者传指针的时候,使用const才能保护实参不被修改
const变量的内存位于栈区(局部const)或者静态存储区(全局const)
虽然存在扩容,但是均摊下来的push_back时间复杂度还是O(1)
不能,因为在不需要返回值的场景中,不能得到区分
改变解决哈希冲突的规则:线性探测、再哈希(更换哈希函数)
若不改变开链法,就将链表树化。
还可以考虑一致性哈希中的虚拟节点
具体步骤如下:
使用读写锁
使用 std::thread 来创建一个线程实例,创建完会自动启动,只需要给它传递一个要执行函数的指针即可,这个函数指针可以指向普通函数、仿函数对象、Lambda表达式、非静态成员函数、静态成员函数。
回调函数在功能上相当于一个中断处理函数,由系统在符合程序员设定的条件时自动调用
指针是一个变量,是用来指向内存地址的。一个程序运行时,所有和运行相关的物件都是需要加载到内存中,这就决定了程序运行时的任何物件都可以用指针来指向它。函数是存放在内存代码区域内的,它们同样有地址,因此同样可以用指针来存取函数,把这种指向函数入口地址的指针称为函数指针。
C/C++中规定了函数参数的压栈顺序是从右至左,对于含有不定参数的printf函数,其原型是printf(const char* format,…);其中format确定了printf的参数(通过format的%个数判断)。假设是从左至右压栈,那么先入栈的是format(这里我们简化理解为参数个数),然后依次入栈未知参数,此时想要知道参数个数,就必须找到format,而要找到format,就必须知道参数个数,陷入一个逻辑矛盾。因此C/C++中规定参数压栈为从右至左,这样对于不定参数,最后入栈的是参数个数,只需要取栈顶就可以得到。
move是为了避免对临时变量的拷贝(拷贝临时变量到一个左值变量上,再回收临时变量,这种拷贝是不必要的)。使用move将传入的临时变量(左值)的资源直接转移给右值引用,能够避免不必要的拷贝。
std::move并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义
调用move后,可以销毁一个移后源对象,也可以赋予他新值,但不能使用一个移后源对象的值
终于能处理临时变量了,处理临时变量用右值引用string &&
, 处理普通变量用const引用const string &
…,每一次必须要重载一个新函数么?为了减少代码重复,引入了forward
forward中包含了move的功能:
技术上来说, forward确实可以替代所有的move,但还有一些问题:
forward
, 代码略复杂;完美转发:https://zhuanlan.zhihu.com/p/161039484
因为静态函数属于类的公共部分(不属于任何一个对象独有),没有this指针,也就不能在没有外力帮助的情况下访问类的非静态成员(因为非静态成员都是对象独有的)。
静态成员函数要想访问非静态成员,要借助外力:
class A {
public:
static void test(A *a) {
a->m_a += 1;
}
void hello(){}
private:
static int m_staticA;
int m_a
};
A g_a;
class A {
public:
static void test() {
g_a.m_a += 1;
}
void hello() {}
private:
static int m_staticA;
int m_a
};
class A {
public:
A() {
m_gA = this;
}
static void test() {
m_gA.m_a += 1;
}
void hello() {}
private:
static int m_staticA;
static A *m_gA;
int m_a
};
https://blog.csdn.net/terence1212/article/details/52287762
右值引用绑定的都是将要销毁的对象
右值引用 不能绑定 左值
左值引用 不能绑定 右值
const 左值引用 可以绑定 右值
虽然不能将一个左值直接赋给一个右值引用,但是可以通过move函数来实现将左值转化为右值,再赋给右值引用
模板为什么要特化,因为编译器认为,对于特定的类型,如果你对某一功能有更好地实现,那么就该听你的
从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。
1、brk是将数据段(.data)的最高地址指针_edata往高地址推;
2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。
这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。
brk分配的内存需要等到高地址内存释放以后才能释放,而mmap分配的内存可以单独释放
https://blog.csdn.net/edonlii/article/details/22601043
直接调用std::thread,就创建一个新线程了。该线程拿到任务后立即开始执行。
// createThread.cpp
#include
#include
void helloFunction() {
std::cout << "Hello C++11 from function." << std::endl;
}
class HelloFunctionObject {
public:
void operator()() const {
std::cout << "Hello C++11 from a function object." << std::endl;
}
};
int main() {
std::cout << std::endl;
// 线程执行函数 helloFunction
std::thread t1(helloFunction);
// 线程执行函数对象 helloFunctionObject
HelloFunctionObject helloFunctionObject;
std::thread t2(helloFunctionObject);
// 线程执行 lambda function
std::thread t3([] {
std::cout << "Hello C++11 from lambda function." << std::endl;
});
// 确保 t1, t2 and t3 在main函数结束之前结束
t1.join();
t2.join();
t3.join();
std::cout << std::endl;
};
假设B是一个指针,里面存着变量C的地址,那么
int* B = nullptr;
int C = 1;
B = &C; //B中存着变量C的地址
*B 为 C的值; //通过解引用B得到变量C的内容
若在函数中想更改B的指向,那么需要一个二级指针A,A中保存着B的地址
int** A = nullptr;
A = &B; //A中保存着指针B的地址
*A 为 B的值; //通过解引用A得到指针B中存储的内容(也就是变量C的地址)
**A 为 *B 为 C;//双重解引用A能够直接得到变量C的内容
*A = &C1;//可以实现在函数内更改指针B中存储的内容,也就是更改指针B的指向
https://blog.csdn.net/majianfei1023/article/details/46629065
在懒汉单例的线程安全版本中
class lhsingleClass {
public:
static lhsingleClass* getinstance() {
if (instance == nullptr) { //第一次检查
i_mutex.lock();
if (instance == nullptr) { //第二次检查
instance = new lhsingleClass();
}
i_mutex.unlock();
}
return instance;
}
private:
static lhsingleClass* instance;
static mutex i_mutex;
lhsingleClass(){}
};
lhsingleClass* lhsingleClass::instance=nullptr;
mutex lhsingleClass::i_mutex;//类外初始化
若线程A完成第一次检查后,还没有获得锁,发生上下文切换,线程B获得CPU,线程B同样完成了第一次检查,并获得了锁,实例化单例对象后释放锁,此时线程A获得锁,如果不进行第二次检查就会让线程A再次实例化一个对象,不满足单例设计模式的要求
不能,返回值不能作为函数重载的唯一区分。
另外,const在函数名之前修饰的是返回值为const,void const表示修饰一个空类型为const,可以但没必要
代码段(.text段):存储可执行文件的指令;也有可能包含一些只读的常数变量,例如字符串常量等。
代码段这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许自修改程序。 在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
在局部变量前加上“static”关键字,就成了静态局部变量。
应该保存在代码段中
如果是动态库中的函数,只有在运行时才能确定函数真正的保存位置
函数调用的时候,会将函数地址压入栈中
应该视(不同版本标准库中容器内存的具体实现算法)和(机器运行时刻的可用内存)而定
调用max_size() 可得
同一个文件中只能将一个头文件include一次。记住这个规则很容易,但是很可能在不知情的情况下将头文件包含多次,因为你include的头文件里可能还会include其它的头文件,这样层层嵌套,很容易出现上面的问题。这时就会带来编译的错误。
主要的解决方案有两种
#ifndef _COORDIN_H
#define _COORDIN_H
... // 头文件的内容
#endif
#pragma once
... ... // 一些声明语句
(1)#ifndef和#pragma once都发生在预处理阶段,#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是不同头文件的宏名不小心“撞车”。
(2)#ifndef是C/C++语言特性,而#pragma once是编译器提供的指令,同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。
(3)#pragma依赖于编译器,所以一些老的编译器不提供(比如说vc6之前),而#ifndef可移植性非常好。
static_cast用于将一种数据类型强制转换为另一种数据类型。什么都可以转,**最常用。**如下:
int a = 7;
int b = 3;
double result = static_cast<double>(a) / static_cast<double>(b);
但是在进行下行转换(把基类的指针或引用转换为派生类表示)时,由于没有动态类型检查,是不安全的。
向上转换:指子类向基类转换。
向下转换:指基类向子类转换。
这两种转换,因为子类包含父类,子类转父类是可以任意转的。但是当父类转换成子类时可能出现非法内存访问的问题。
dynamic_cast:
正因为static_cast从父类向子类转换时不安全,所以又引入了dynamic_cast。dynamic_cast只能用于含有虚函数的类转换,用于类向上和向下转换。
dynamic_cast通过判断变量运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。dynamic_cast可以做类之间上下转换,转换的时候会进行类型检查,类型相等成功转换,类型不等转换失败。
运用RTTI技术,RTTI是“Runtime Type Information”的缩写,意思是运行时类型信息,它提供了运行时确定对象类型的方法。在C++层面主要体现在dynamic_cast和typeid,在虚函数表中存放了指向type_info的指针,对于存在虚函数的类型,dynamic_cast和typeid都会去查询type_info
#include
#include
#include
#include
using namespace std;
class Base{
public:
Base() {};
virtual ~Base() {};
};
class Inherit :public Base{
public:
Inherit() {};
~Inherit() {};
void show();
};
void Inherit::show(){
std::cout << "Inherit funtion" << std::endl;
}
int main() {
Base* pbase = new Inherit();
Inherit* pInherit = dynamic_cast<Inherit*>(pbase);
pInherit->show();//这样动态转换,我们就可以调用派生类的函数了
system("Pause");
return 0;
}
//运行结果为
Inherit funtion
shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。
共享指针的循环引用计数问题采用weak_ptr指针:
weak_ptr是弱引用,weak_ptr的构造和析构不会引起引用计数的增加或减少。我们可以将其中一个改为weak_ptr指针就可以了。
首先初始化的类采用weak_ptr。
只建立在堆上
将析构函数设为私有,类对象就无法建立在栈上了
当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象。当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期。如果编译器无法调用类的析构函数,情况会是怎样的呢?比如,类的析构函数是私有的,编译器无法调用析构函数来释放内存。所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。
为了统一,可以将构造函数设为protected,然后提供一个public的static函数来完成构造,这样不使用new,而是使用一个函数来构造,使用一个函数来析构。
class A {
protected :
A(){}
~A(){}
public :
static A* create() {
return new A();
}
void destory() {
delete this ;
}
};
只建立在栈上
只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。将operator new()设为私有即可。
class A {
private :
void * operator new ( size_t t){} // 注意函数的第一个参数和返回值都是固定的
void operator delete ( void * ptr){} // 重载了new就需要重载delete
public :
A(){}
~A(){}
};
为什么需要内联函数?
函数调用包含一系列工作,例如保存寄存器,并在返回时恢复,可能需要拷贝实参,程序转向一个新的位置执行等,这些工作会有一定开销,如果把函数代码在调用点上内联地展开,就可以避免这些开销,加快了程序运行速度,代价是程序体积会随着内联的次数增大
简单点来说:内联函数就是把函数直接用函数体里面的代码替换。
内联函数发生替换是在编译期间,在编译期间编译器需要找到内联函数的定义,所以在为了方便编译器找到定义,每个文件引用头文件后,都直接拥有这种定义,而不用再去写。
而普通函数可以申明和定义分离,主要是编译阶段就不需要函数定义。首先编译阶段找到函数的声明,链接阶段才会去找函数的定义,将之关联起来。
https://blog.csdn.net/solstice/article/details/8547547
shared_ptr 的线程安全级别和内建类型、标准库容器、std::string 一样
shared_ptr中有两个成员,这也导致它线程不安全的原因
不是,shared_ptr的引用计数本身是安全且无锁的,但对象的读写则不是
不是,和shared_ptr一样,多个线程同时读安全,一个线程写安全,多个线程写需要加锁才能安全
为什么用lock_guard?
lock_guard的工作过程
为什么用unique_lock?
unique_lock具有lock_guard的所有功能,而且更为灵活。虽然二者的对象都不能复制,但是unique_lock可以移动(movable),因此用unique_lock管理互斥对象,可以作为函数的返回值,也可以放到STL的容器中。
std::unique_lock对象以独占所有权的方式(unique owership)管理mutex对象的上锁和解锁操作,即在unique_lock对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而unique_lock的生命周期结束之后,它所管理的锁对象会被解锁。
lock_guard和unique_lock的区别
static_cast有相关性检查,不能进行没有相关性的转换
强制类型转换可以进行无关类型的转换,但是转换以后的操作是否安全没有保障
在多态的情景下,单继承下会存在一个虚函数表指针,指向成员函数地址;多继承下,会根据继承基类的个数生成相应的多个虚函数表指针 ,从而访问虚函数的地址。
https://blog.csdn.net/weixin_43796685/article/details/103742329?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163010956916780255291277%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=163010956916780255291277&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-5-103742329.pc_search_result_cache&utm_term=%E8%8F%B1%E5%BD%A2%E7%BB%A7%E6%89%BF%E7%9A%84%E8%99%9A%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88&spm=1018.2226.3001.4187
内存溢出:你要分配的内存超出了系统能给你的,系统不能满足需求,于是产生了溢出
char str[5] = "1234567";
内存越界:你想系统申请一块内存,在使用的这块内存的时候,超过出了你申请的范围
int a[10];
a[12] = 10
对应的delete原理:
#define MAX(a,b) (((a)>(b))?(a):(b))
#define SWAP(x,y) x=x+y;y=x-y;x=x-y;
栈区、堆区、全局区、常量区、代码区
栈区:由编译器自动分配释放,存放为函数运行的局部变量,函数参数,返回数据,返回地址等。操作方式与数据结构中的类似。
堆区:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表
全局数据区:也叫做静态区,存放全局变量,静态数据。程序结束后由系统释放
文字常量区:可以理解为常量区,常量字符串存放这里。程序结束后由系统释放
程序代码区:存放函数体的二进制代码。但是代码段中也分为代码段和数据段。
关于文字常量区
文字常量区,在大多数解释中,都仅仅说明常量字符串存放这里。但是如果深究字眼,那么其他常量比如整型是否存放这里呢?我查阅了一些资料,是这么解释的:常量之所以称为“文字常量”,其中“文字”是指我们只能以它的值的形式指代它,“常量”是指它的值是不可变的。同时注意一点:文字常量是不可寻址的(即我们的程序中不可能出现获取所谓常量20的存储地址&20这样的表达式),虽然常量也是存储在内存的某个地方,但是我们没有办法访问常量的地址的。
还有就是我们都知道的常量是有类型的。所以总的来说,只要是常量都存放在文字常量区!
new操作符其实是调用了两个函数:
当我们在程序中执行new A创建对象时,
编译器会把new操作符拆分成上面的两个操作,然后生成相应的汇编指令。
也就是说operator new是new操作符的子操作。
注:在执行new A时不一定完全生成两个操作,编译器可能会进行优化。比如:使用默认构造函数时,就没有调用构造函数的步骤,只使用operator new分配的内存。
总结:
https://blog.csdn.net/darker0019527/article/details/103411418
struct A;
std::shared_ptr<A> p1 = std::make_shared<A>();
std::shared_ptr<A> p2(new A);
区别就是std::shared_ptr构造函数会执行两次内存申请,而std::make_shared只执行一次
std::shared_ptr在实现的时候使用的引用计数技术,因此内部会有一个计数器(控制块,用来管理数据)和一个指针,指向数据。因此在执行std::shared_ptr p2(new A)
的时候,首先会申请数据的内存,然后申请内控制块,因此是两次内存申请,而std::make_shared()
则是只执行一次内存申请,将数据和控制块的申请放到一起。
考虑异常安全场景:
void f(std::shared_ptr<Lhs> &lhs, std::shared_ptr<Rhs> &rhs){...}
f(std::shared_ptr<Lhs>(new Lhs()),
std::shared_ptr<Rhs>(new Rhs())
);
因为C++允许参数在计算的时候打乱顺序,因此一个可能的顺序如下:
此时假设第2步出现异常,则在第一步申请的内存将没处释放了,上面产生内存泄露的本质是当申请数据指针后,没有马上传给std::shared_ptr
https://www.cnblogs.com/shengjianjun/p/3691928.html?utm_source=tuicool&utm_medium=referral
可以,但不能调用虚函数,以及依赖于类构造完成的函数。
首先在一个类的所有非静态函数中,都隐藏了一个this指针。所以对构造函数这个非静态函数来说,是存在此指针的。(证明了理论上是可以调用的)
至于楼主的意思,是怕在对象未全部构造完时就对其数据成员进行操作,这种考虑是正确的。所以,当你在构造函数中调用member function时,务必不要在member function中使用(注意是使用,不包括赋值)未被构造函数初始化过的数据成员。也就是说,这是在程序员的角度对这个调用进行把关。(证明了实践中的局限性)
https://bbs.csdn.net/topics/50303784
判断一个成员函数是不是虚函数(重写),有两个三个条件:
注意:如果这两个函数的返回类型分别为基类和派生类,返回值为指向基类和派生类的指针或引用,则也构成重写。此返回类型称为协变。
因为子类继承父类之后,获取到了父类的内容(属性/字段),而这些内容在使用之前必须先初始化,所以必须先调用父类的构造函数进行内容的初始化.
在子类的构造函数中的第一行会隐式的调用 super();即调用了父类的构造函数
如果父类里面没有定义参数为空的构造函数,那么必须在子类的构造函数的第一行显示的调用super(参数);语句调用父类当中其它的构造函数.
https://www.cnblogs.com/redips-l/p/11611240.html
https://blog.csdn.net/h674174380/article/details/78037294
返回值string&,参数const string&,函数体判断自赋值情况,不是则深拷贝,返回*this
//声明
MyString& operator=(const char *s); // 普通字符串赋值
MyString& operator=(const MyString &s); // 类对象之间赋值
//实现
MyString& MyString::operator=(const char *s) {
if (m_p != NULL) {
delete []m_p;
m_p = NULL;
}
m_len = strlen(s);
m_p = new char[m_len+1];
strcpy(m_p,s);
return *this;
}
MyString& MyString::operator=(const MyString &s){
if(this == &s)
return *this;
if (m_p != NULL) {
delete []m_p;
m_p = NULL;
}
m_len = s.m_len;
m_p = new char [m_len+1];
strcpy(m_p,s.m_p);
return *this;
}
strcpy:复制char*时会复制’\0’,所有在new 字符数组的时候应该预留’\0’的空间,否则到时候delete的时候会报错
//报错版本
char *arr = new char[10];
strcpy(arr, "wangzhaaaa");
delete []arr;
//正确版本
char *arr = new char[11];
strcpy(arr, "wangzhaaaa");
delete []arr;
改进用memcpy:与strcpy相比,memcpy并不是遇到’\0’就结束,而是一定会拷贝完n个字节
memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度
让一个不是函数 的东西拥有函数的功能,说的通俗点就是在一个类中利用运算符重载重载"()"让类拥有函数的使用特性和功能
template<class T>
struct A {
bool operator()(const T& a,const T& b) {
return (a == b);
}
};
int main() {
A<int>a;
cout << a(1, 2) << endl;
system("pause");
return 0;
}
https://blog.csdn.net/lizun7852/article/details/88753218?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163049977816780269867042%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=163049977816780269867042&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-1-88753218.pc_search_result_hbase_insert&utm_term=C%2B%2B%E6%9C%AC%E8%BA%AB%E6%9C%89%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%88&spm=1018.2226.3001.4187
template<typename T> //在命名空间std中
typename remove_reference<T>::type&& move(T&& param) {
using ReturnType = typename remove_reference<T>::type&&; //别名声明
return static_cast<ReturnType>(param);
}
std::move无条件地把它的参数转换成一个右值,而std::forward只在特定条件满足的情况下执行这个转换。
https://blog.csdn.net/f110300641/article/details/83477160?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163054139716780357264028%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=163054139716780357264028&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-1-83477160.pc_search_result_hbase_insert&utm_term=std%3A%3Amove%28%29+%E7%9A%84%E5%86%85%E9%83%A8%E5%AE%9E%E7%8E%B0&spm=1018.2226.3001.4187
sizeof既是关键字又是运算符,但不是函数
sizeof是唯一一个以单词形式出现的运算符,它用来计算存放某一个量需要占用多少字节,它的结合性是从右到左。
sizeof不是函数。产生这样的疑问主要是因为有时候sizeof的外在表现确实有点类似函数,比如:i = sizeof(int);这样的式子,就很容易让人误以为sizeof是一个函数呢。但如果sizeof是函数,那么sizeof i;(假设i为int变量)这种式子一定不能成立,因为函数的参数不可能不用括号括起来。事实上这个sizeof i;可以正常运行,这就说明sizeof绝对不是函数。
子类不能直接访问父类私有成员
可以在子类构造函数的成员初始化列表中,通过对父类对象进行初始化来达到访问父类私有成员的目的
也可以通过在子类成员函数中调用父类的protected和public接口实现对父类private成员的访问
在头文件中定义static变量会造成变量多次定义,造成内存空间的浪费,而且也不是真正的全局变量。应该避免使用这种定义方式。
//Header.h-----------------头文件中定义static变量
#pragma once
static int g_int = 3;
//Source1.cpp
#include
#include "Header.h"
void TestSource1() {
wprintf(L"g_int's address in Source1.cpp: %08x\n", &g_int);
g_int = 5;
wprintf(L"g_int's value in Source1.cpp: %d\n", g_int);
}
//Source2.cpp
#include
#include "Header.h"
void TestSource2() {
wprintf(L"g_int's address in Source2.cpp: %08x\n", &g_int);
wprintf(L"g_int's value in Source2.cpp: %d\n", g_int);
}
//两个源文件中的g_int变量完全不是同一个
要想实现多个源文件共享头文件中定义的全局变量,就要用extern关键字声明该变量,而不是static
//Header.h
#pragma once
extern int g_int;
用nothrow new,空间分配失败的时候不抛出异常,而是返回NULL
char* p = new(nothrow) char[10];
if (p == NULL) {
cout << "alloc failed" << endl;
}
https://blog.csdn.net/weixin_30806145/article/details/112583567
https://blog.csdn.net/robinhjwy/article/details/78700492
首先说一条指导规则:通常情况下,不应该在类内部初始化成员!!无论是否为静态、是否为常量、是否为int等!!统统不建议在类内初始化,因为本质上类只是声明,并不分配内存,而初始化会分配内存,类内初始化会将两个过程混在一起!
如果非要在类内初始化以上成员
static成员:
c++禁止在类内初始化非静态常量。
class A {
public:
//static int a = 1; 这样报错,静态的非常量必须在类外初始化
static int a;
static const int b = 1; //静态的类内成员必须为常量才能在类内初始化
};
int A::a = 1;
常量:
class A {
public:
//A(): {} 这样报错,常量成员必须在类的初始化列表中初始化
A(): a(1) {}
const int a;
};
普通成员:
class Student {
public:
Student(string in_name, int in_age) {
name = in_name;
age = in_age;
}
private :
string name;
int age;
};
class Student {
public:
Student(string in_name, int in_age):name(in_name),age(in_age) {}
private :
string name;
int age;
};
https://blog.csdn.net/lws123253/article/details/80353197
引用变量内存放的是被引用对象的地址,但是对一个引用取地址 == 对被引用对象取地址。在实际使用时,引用被当作被引用对象的一个别名来使用。但是在底层实现上,通过汇编验证了引用是有自己的内存的
https://www.cnblogs.com/chenke1731/p/9651275.html
**C++规定,默认参数只能放在形参列表的最后,而且一旦为某个形参指定了默认值,那么它后面的所有形参都必须有默认值。**实参和形参的传值是从左到右依次匹配的,默认参数的连续性是保证正确传参的前提。
下面的写法是正确的:
void func(int a, int b=10, int c=20){ }
void func(int a, int b, int c=20){ }
但这样写不可以:
void func(int a, int b=10, int c=20, int d){ }
void func(int a, int b=10, int c, int d=20){ }
除了函数定义,也可以在函数声明处指定默认参数。不过当出现函数声明时情况会变得稍微复杂,有时候可以在声明处和定义处同时指定默认参数,有时候只能在声明处指定:
C++ 规定,在给定的作用域中只能指定一次默认参数,即函数声明与函数实现都有默认参数的话,要放在两个文件中