文章目录
Cpp基础知识与常见问题。
const int a;
a还是一个int型变量,const int 顺序可以互换:
int const a;
const int a[5];
int const a[5];
只读数组
const char *p;
char const *p;//这两种,const都是修饰*p,则p指向的变量只读。(与下一种对照记忆)
char * const p;//const很明显修饰指针p,则指针p不能被修改。
const char * const p;//指针p不能被修改,指向的对象也不能被修改。
void apple(const char *a){//防止修改a指向的字符串
...
}
void apple(char * const a){//防止修改a本身
...
}
void apple(const vector<int> &num){//防止修改容器内容
...
}
int getSum() const {//修饰函数,表示该函数内不能修改变量
return get_Name_sum;
}
friend提供了在类外访问类的私有成员的能力,friend可以修饰函数或类。当在类内声明一个友元函数时,该函数可以访问类的私有成员。当在类内声明友元类时,则友元类可以访问当前类的私有成员。
如果需要在const成员方法中修改一个成员变量的值,那么需要将这个成员变量修饰为mutable。即用mutable修饰的成员变量不受const成员方法的限制。
assert是一个宏,用于在DEBUG版本下判断表达式的真假,如果表达式为假,它会先向stderr打印错误信息,然后调用abort终止程序运行。
#include <bits/stdc++.h>
using namespace std;
int main(){
cout<<"test assert:"<<endl;
assert(2*2==4);
assert(2*2==5);
return 0;
}
/*
test assert:
20200930_test: 20200930_test.cpp:10: int main(): Assertion 2*2==5 failed.
已放弃 (核心已转储)
1.在头文件中一定不要使用,否则在别人引用你的头文件后,如果std中的函数名和其他库中的冲突了,可能会带来麻烦。 2.在cpp文件中:
* 在"一般情况下"可以使用,但是注意一定要在所有include语句之后使用。
* 可以在函数中使用,使其只在有限作用域内有效。
* 不直接使用命名空间using namespace xxx,可以使用using std::cin;using std::cout;这种方式。
* 也可以在需要的地方全部加上std:: 。
1.通用的做法是写一个类noncopyable,凡是继承该类的任何类都无法复制和赋值。
将拷贝构造函数和拷贝赋值运算符设置为私有,这样继承nocopyable的类给对象赋值或拷贝构造时,会先调用父类nocopyable的函数,但是这两个函数是私有的,所以会引发编译错误。
将noncopyable的构造函数和析构函数设置protected,这样该类无法创建对象,但是子类中可以调用。
#include <bits/stdc++.h>
using namespace std;
class noncopyable {
protected:
noncopyable() = default;
~noncopyable() = default;
private:
noncopyable(const noncopyable&) = delete;
const noncopyable& operator=( const noncopyable& ) = delete;
};
class StockFactory:noncopyable{
public:
StockFactory(double _price):price(_price){}
private:
double price;
};
int main(){
StockFactory s1(10);
//StockFactory s2=s1;
return 0;
}
2.使用c++11标准的简单实现:
class noncopyalbe{
protected:
noncopyable()=default;
~noncopyable()=default;
noncopyable(const noncopyable &)=delete;
noncopyable &operator=(const noncopyable &)=delete;
}
3.google开源项目风格指南建议的做法是使用 DISALLOW_COPY_AND_ASSIGN 宏:
// 禁止使用拷贝构造函数和 operator= 赋值操作的宏
// 应该类的 private: 中使用
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
void operator=(const TypeName&)
在 class foo 中使用方式如下:
class Foo {
public:
Foo(int f);
~Foo();
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};
绝大多数情况下都应使用 DISALLOW_COPY_AND_ASSIGN 宏。如果类确实需要可拷贝,应在该类的头文件中说明原由,并合理的定义拷贝构造函数和赋值操作。注意在 operator= 中检测自我赋值的情况。
RAII(Resource Acquisition Is Initialization),翻译过来是资源获取即初始化。也就是说当创建一个对象的时候,就对其进行初始化,同样当不需要该对象时,也要对其资源进行释放。比如当调用new来申请空间时、调用open()打开文件时,都需要对应的delete、close来释放资源,但是往往就忘掉释放资源。所以可以利用类的构造函数和析构函数,将需要分配资源的对象进行一层封装,将其获取资源和释放资源分别绑定到构造函数和析构函数里,这样当该对象生命周期结束,就会自己释放资源。
示例一:编程中可能出现以下情况,但是中间如果发生异常或者return了,就执行不了unlock()了。就会导致该资源一直被占用了。
std::mutex mutex_;
void function()
{
mutex_.lock();
......
......
mutex_.unlock();
}
所以正确的方式是使用std::unique_lock或者std::lock_guard对互斥量进行状态管理:
std::mutex mutex_;
void function()
{
std::lock_guard<std::mutex> lock(mutex_);
......
......
}
这样只管创建一个lock对象就可以,lock生命周期结束时会自动对mutex_解锁。
虚函数是实现运行时多态的一种机制,比如两个父类指针分别指向子类A和子类B的实例,父类指针调用虚函数时,会根据不同的子类来调用不同的函数。 当类中声明虚函数之后,编译器会在类的开始位置设置一个指针,来指向一个虚函数列表,当子类继承父类时,会一块继承这个指针,如果子类对父类中的虚函数进行了重写,就会用新函数的地址覆盖虚函数表中的旧函数。 http://taowusheng.cn/2019/05/18/20190518%20C++%E8%99%9A%E5%87%BD%E6%95%B0%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86%E7%82%B9/
第一种通过模板实现。
class Dog{
public:
void sound(){
cout<<"汪汪"<<endl;
}
};
class Cat{
public:
void sound(){
cout<<"喵喵"<<endl;
}
};
template<typename T>
void animalSound(T t){
t.sound();
}
//===================
Dog d;
Cat c;
animalSound(d);
animalSound(c);
第二种重载。函数名相同,参数不同。
访问权限 | 类内 | 子类 | 类外 |
---|---|---|---|
public | ✔ | ✔ | ✔ |
protect | ✔ | ✔ | |
private | ✔ |
1.将构造函数设置为protected或private。 2.在类内声明纯虚函数。
1.编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。因此,将析构函数设为私有,类对象就无法建立在栈上了。 缺点:(1).无法解决继承问题。如果A作为其它类的基类,则析构函数通常要设为virtual,然后在子类重写,以实现多态。因此析构函数不能设为private。还好C++提供了第三种访问控制,protected。将析构函数设为protected可以有效解决这个问题,类外无法访问protected成员,子类则可以访问。(2).类的使用很不方便,使用new建立对象,却使用destory函数释放对象,而不是使用delete。(使用delete会报错,因为delete对象的指针,会调用对象的析构函数,而析构函数类外不可访问)这种使用方式比较怪异。(3)为了统一,可以将构造函数设为protected,然后提供一个public的static函数来完成构造,这样不使用new,而是使用一个自定义函数来构造,使用一个自定义函数来析构。
class A{
protected:
A(){}
~A(){}
public:
static A* create(){
return new A();
}
void destory(){
delete this;
}
};
2.只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。虽然你不能影响new operator的能力(因为那是C++语言内建的),但是你可以利用一个事实:new operator 总是先调用 operator new,而后者我们是可以自行声明重写的。因此,将operator new()设为私有即可禁止对象被new在堆上。
class A{
private:
void* operator new(size_t t){} // 注意函数的第一个参数和返回值都是固定的
void operator delete(void* ptr){} // 重载了new就需要重载delete
public:
A(){}
~A(){}
};
#include <iostream>
using namespace std;
class A{
public:
A(int a=9,int b=8,int c=7){
x=a;
y=b;
z=c;
}
void show(){
cout<<x<<" "<<y<<" "<<z<<endl;
}
private:
int x,y,z;
};
int main(){
A a1;
A a2(1);
A a3(1,1);
A a4(1,1,1);
a1.show();//9 8 7
a2.show();//1 8 7
a3.show();//1 1 7
a4.show();//1 1 1
return 0;
}
一般构造函数都是公有地,创建一个对象时就会自动调用构造函数。(对象是算作类外的,它不是类本身) 构造函数设置为私有,那岂不是没法创建对象了。但是对于强大的Cpp来说,有方法可以绕过去。 构造函数还是要调用的,我们可以在有权限的地方调用,比如static函数、友元函数或友元类。
#include <iostream>
using namespace std;
class Student{
public:
static Student* makeObj(){
cout<<"hello"<<endl;
return (new Student);
}
private:
Student(){}
};
int main(){
Student* s=Student::makeObj();
return 0;
}
这种方式可以用在单例模式的实现上。
指针也是一个变量,里面存储的内容是一个地址。而引用本质上是一个常量指针,引用只允许初始化,不能再修改。 编译指针和引用的代码,在汇编上是一样的:c++中,引用和指针的区别是什么? - RainMan的回答 - 知乎 https://www.zhihu.com/question/37608201/answer/545635054
对齐规则
内存对齐作用
malloc
operator new与new operator
operator new
placment new
nothrow new
set_new_handler
该函数接收一个函数指针,当new或new[]失败时,就会执行传进来的函数。
// new_handler example
#include <iostream> // std::cout
#include <cstdlib> // std::exit
#include <new> // std::set_new_handler
void no_memory () {
std::cout << "Failed to allocate memory!\n";
std::exit (1);
}
int main () {
std::set_new_handler(no_memory);
std::cout << "Attempting to allocate 1 GiB...";
char* p = new char [1024*1024*1024];
std::cout << "Ok\n";
delete[] p;
return 0;
ptrdiff_t是C/C++标准库中定义的一个与机器相关的数据类型。ptrdiff_t类型变量通常用来保存两个指针减法操作的结果。ptrdiff_t定义在stddef.h(cstddef)这个文件内。ptrdiff_t通常被定义为long int类型。
为什么?当我们利用模板的参数推导机制,实现一个对不同迭代器通用的函数时,函数的参数类型(智能指针)能够推导出来,但是如果函数内部需要用到指针指向的类型,就很不方便了。再就是函数的返回值也要用到指针指向的类型时,仅利用模板的参数推导是做不到的。
如何实现?首先需要每个迭代器来配合,迭代器内部应当储存所指向数据的类型value_type,然后我们利用typedef来将不同迭代器中的value_type都加个新名字。然后在需要用到萃取类型的地方,用我们的typedef所创造的新名字就行了。关键实现如下:
template<typename I>
struct my_traits{
typedef typename I::value_type value_type;
}
//使用的时候如下:
template<typename Iter>
typename my_traits<Iter>::value_type
addone(Iter iter){
typename my_traits<Iter>::value_type tmp=*iter;
tmp++;
return tmp;
}
每个类型I的迭代器都会储存指向元素的类型value_type,像原生指针、const指针不是一个类,没有value_type,就得自己实现特化版本。
*当插入或删除中间一个元素后,原位置之后的迭代器会失效。
在push_back()的时候会检查是否还有剩余空间,如果没有了,就申请一块原来尺寸2倍的空间,将原来的数据直接复制过去,然后把最后一个元素添加到最后面。并释放原来的空间。
deque结构:有一个map指针数组,每一个元素都指向一个缓冲区,扩容时申请空间为原map数组长度二倍,然后把原数组内容复制到新空间的中间。
简介:
理解move要先知道左值和右值,以string str=”hello”为例,str这个变量是一个左值,可以被改变。”hello”是一个右值,不能被改变了。 然后对左值使用&进行左值引用,对右值使用&&进行右值引用。
对于左值,我们可以使用&进行引用,对于右值,我们可以用&&给它续命。
int a=10;
int &b=a;
int &&c=10;
如果我们就想用左值引用绑定到左值上,那就需要用到move()了。
int a=10;
int &&b=std::move(a);
//std::move()做的是转移控制权,将a储存的右值的所有权交给b。
//因为转移了所有权,所以除了对a赋值或销毁外,不要再使用a。
//注意使用该函数时加std::,避免潜在的名字冲突。
从实现上讲,std::move基本等同于一个类型转换:static_cast(lvalue);
参考:
forward会保留参数的类型、const、引用等属性。
template<typename F,typename T1,typename T2>
void flip1(F f,T1 t1,T2 t2){
f(t1,t2);
}
当传递一般的函数f或参数时,flip1一般能正常工作。但是传递的f函数是下面这样时,就得不到想要的结果。
void f(int v1,int &v2){
cout<<v1<<" "<<++v2<<endl;
}
我们期望的结果是:
f(42,i); //f能够改变实参i的值。
但实际:
flip1(f,42,j); //j实参不会被改变。
使用std::forward()和右值引用可以解决这个问题:
template<typename F,typename T1,typename T2>
void flip1(F f,T1 &&t1,T2 &&t2){
f(std::forward<T1>(t1),std::forward<T2>(t2));
}
简介
从名字可以看出是一个共享指针,允许多个shared_ptr指针指向一个资源,shared_ptr内部会有一个计数,记录指向该资源的指针个数。当计数为0,就会自动释放资源。
使用场景
当需要频繁申请内存时,使用shared_ptr来管理内存,可以在创建对象时自动初始化资源,也能在生命周期结束时自动释放内存。
创建方式
shared_ptr<int> p=std::make_shared<int>(10);//推荐使用make_shared()方式。
shared_ptr<T> p=std::make_shared<T>();
shared_ptr<int> p(new int(10));
使用方式
shared_ptr<int> p1=std::make_shared<int>(10);
shared_ptr<int> p2=std::make_shared<int>(20);
p1=p2;//p2的资源计数会加1,p1资源计数会减1(若p1指向资源计数为0,则释放资源)
注意事项 1.不混合使用普通指针和智能指针
//有如下函数:
void process(shared_ptr<int> ptr){
使用ptr
}//离开作用域,ptr被销毁。
//给process()传递参数时,不能传递普通指针和临时shared_ptr。
int *x(new int(1024));
process(x);//错误,不能将int*转换为shared_ptr<int>
process(shared_ptr<int>(x));//能编译通过,但是内存会被释放
int j=*x;//未定义行为,x已经是一个空悬指针!
可以这样使用:
shared_ptr<int> p1=std::make_shared<int>(1024);
process(p1);
2.智能指针内部有一个get()函数,可以获取到原生指针。注意get()到的指针不要再初始化另一个智能指针。
shared_ptr<int> p(new int(1024));
int *q=p.get();
{
shared_ptr<int>(q);
}//程序块结束会释放掉内存。
int foo=*p;//访问了释放掉的内存。
3.get()返回的指针不要去delete、reset其他智能指针、初始化其他智能指针。 4.如果资源不是new申请到的,要注意给智能指针传递一个删除器。
5.不要两个指针相互引用,会造成内存泄漏,可以用weak_ptr解决。
简介
这是一个弱指针,它必须跟shared_ptr结合来用,它指向shared_ptr所管理的对象,但是它不会导致资源的引用计数变化.
使用场景
使用shared_ptr会有循环引用的问题,可以用weak_ptr来解决这个问题。
使用方式
//方式一
weak_ptr<T> w(sp);
//方式二
weak_ptr<T> w;
w=p;//p可以是shared_ptr或weak_ptr。
注意事项 1.weak_ptr在使用时,它指向的shared_ptr可能已经释放了,可以使用前先调用lock()。(检查weak_ptr是否为空指针)
if(shared_ptr<int> np=wp.lock()){
...
}
简介
与shared_ptr不同,在某个时刻只能有一个unique_ptr指向一个给定对象,当unique_ptr被销毁时,它所指向的对象也被销毁。
使用场景
如果不需要对资源进行共享,优先使用unique_ptr.
//要采用直接初始化形式。
unique_ptr<double> p(new int(42));
注意事项 1.不能拷贝初始化、不能拷贝
unique_ptr<string> p2(p1);//错误
unique_ptr<string> p3;
p3=p2;//错误
2.不能拷贝的规则有一个例外,返回一个将要被销毁的unique_ptr可以发生拷贝。
unique_ptr<int> clone(int p){
return unique_ptr<int>(new int(p));
}
或
unique_ptr<int> clone(int p){
unique_ptr<int> ret(new int(p));
return ret;
}
简介
跟unique_ptr有一些相似的特性,同一时刻只能指向一个对象。但是这个允许拷贝,unique_ptr不允许拷贝。在cpp11已经被遗弃。
000000
1.static_case(静态转换)
主要执行非多态的转换操作,用于代替C中通常的转换操作。
隐式转换都建议使用 static_cast 进行标明和替换。
在有类型指针与void *之间转换,不能使用static_cast在有类型指针间转换。
// 1. 使用static_cast在基本数据类型之间转换
float fval = 10.12;
int ival = static_cast<int>(fval); // float --> int
// 2. 使用static_cast在有类型指针与void *之间转换
int *intp = &ival;
void *voidp = static_cast<void *>(intp); // int* --> void*
long *longp = static_cast<long *>(voidp);
// 3. 用于类层次结构中基类和派生类之间指针或引用的转换
// 上行转换(派生类---->基类)是安全的
CDerived *tCDerived1 = nullptr;
CBase *tCBase1 = static_cast<CBase*>(tCDerived1);
// 下行转换(基类---- > 派生类)由于没有动态类型检查,所以是不安全的
CBase *tCBase2 = nullptr;
CDerived *tCDerived2 = static_cast<CDerived*>(tCBase2); //不会报错,但是不安全
// 不能使用static_cast在有类型指针内转换
float *floatp = &fval; //10.12的addr
//int *intp1 = static_cast<int *>(floatp); // error,不能使用static_cast在有类型指针内转换
2.dynamic_cast(动态转换)
用于将一个父类的指针/引用转化为子类的指针/引用(下行转换)。
基类必须要有虚函数,因为 dynamic_cast 是运行时类型检查,需要运行时类型信息,而这个信息是存储在类的虚函数表中。
CBase *p_CBase = new CBase; // 基类对象指针
CDerived *p_CDerived = dynamic_cast<CDerived *>(p_CBase); // 将基类对象指针类型转换为派生类对象指针
CBase i_CBase; // 创建基类对象
CBase &r_CBase = i_CBase; // 基类对象的引用
CDerived &r_CDerived = dynamic_cast<CDerived &>(r_CBase); // 将基类对象的引用转换派生类对象的引用
3.const_cast(常量转换)
常量指针(或引用)与非常量指针(或引用)之间的转换。
cosnt_cast 是四种类型转换符中唯一可以对常量进行操作的转换符。
去除常量性是一个危险的动作,尽量避免使用。
int value = 100;
const int *cpi = &value; // 定义一个常量指针
//*cpi = 200; // 不能通过常量指针修改值
// 1. 将常量指针转换为非常量指针,然后可以修改常量指针指向变量的值
int *pi = const_cast<int *>(cpi);
*pi = 200;
// 2. 将非常量指针转换为常量指针
const int *cpi2 = const_cast<const int *>(pi); // *cpi2 = 300; //已经是常量指针
const int value1 = 500;
const int &c_value1 = value1; // 定义一个常量引用
// 3. 将常量引用转换为非常量引用
int &r_value1 = const_cast<int &>(c_value1);
// 4. 将非常量引用转换为常量引用
const int &c_value2 = const_cast<const int &>(r_value1);
4.reinterpret_cast(不相关类型的转换)
用在任意指针(或引用)类型之间的转换。
能够将整型转换为指针,也可以把指针转换为整型或数组。
reinterpret_cast 是从底层对数据进行重新解释,依赖具体的平台,可移植性差。
尽量不使用这个转换符,高危操作。
int value = 100;
// 1. 用在任意指针(或引用)类型之间的转换
double *pd = reinterpret_cast<double *>(&value);
cout << "*pd = " << *pd << endl;
// 2. reinterpret_cast能够将指针值转化为整形值
int *pv = &value;
int pvaddr = reinterpret_cast<int>(pv);
cout << "pvaddr = " << hex << pvaddr << endl;
cout << "pv = " << pv << endl;
/*
输出结果:
*pd = -9.25596e+61
pvaddr = 8ffe60
pv = 008FFE60
*/
https://www.cnblogs.com/linuxAndMcu/p/10387829.html
效率,列表初始化是在变量创建的时候就进行初始化,相比构造函数体内的赋值要节省一遍拷贝操作,能提高运行时的效率。
类内有const变量要用列表初始化,而不能赋值。
#include <iostream>
using namespace std;
class Student{
public:
Student(int a):birthday(a){//如果不用列表初始化,下面的birthday无法编译通过。
cout<<birthday<<endl;
}
private:
const int birthday;
};
int main(){
Student s1(1);
return 0;
}
类内有类对象(该对象没有默认构造函数)必须用。
class CAnimal{
public:
CAnimal(int weight) :m_weight(weight) {
}
int m_weight;
};
class CDog {
public:
CAnimal m_a;//CAnimal类没有默认构造函数,但m_a必须得被初始化呀,Cdog构造函数就可以对m_a进行列表初始化。
const int m_b;
CDog(int a, int b) : m_a(a),m_b(b) {
}
};
1.作用:用来获取数据类型。
int tempA = 2;
decltype(tempA) dclTempA;
2.decltype和auto都可以用来推断类型,但是二者有几处明显的差异。
auto忽略顶层const,decltype保留顶层const;
对引用操作,auto推断出原有类型,decltype推断出引用;
对解引用操作,auto推断出原有类型,decltype推断出引用;
auto推断时会实际执行,decltype不会执行,只做分析。总之在使用中过程中和const、引用和指针结合时需要特别小心。
持续更新~
欢迎与我分享你的看法。 转载请注明出处:http://taowusheng.cn/