C++学习笔记
一.dynamic_cast
用途:将基类的指针(引用)显式转换为派生类的指针(引用)
特点:当转换失败时,会得到空指针(抛出异常)
此外,同static_cast,可以将派生类的指针转换为基类指针
二.虚函数
class Base1{
public:
virtual void write()
{
cout<<"Base1"<<endl;
}
};
class Base2:public Base1{
public:
virtual void write()
{
cout<<"Base2"<<endl;
}
};
class Derived:public Base2{
public:
virtual void write()
{
cout<<"Derived"<<endl;
}
};
void solve(Base1* ptr)
{
ptr->write();
}
int main()
{
Base1 base1;
Base2 base2;
Derived derived;
solve(&base1);
solve(&base2);
solve(&derived);
}
用指向派生类对象的指针仍然可以调用基类中被派生类覆盖的成员函数
ptr->Base1::write();
override
如果override标记的函数没有覆盖虚函数,那么编译器就会报错
class Base{
public:
virtual void write()
{
cout<<"Base"<<endl;
}
};
class Derived:public Base{
public:
void write() override;
};
final
不允许后续其他类覆盖final标记的函数,否则编译器报错
class Base{
public:
virtual void write() final
{
cout<<"Base"<<endl;
}
};
class Derived:public Base{
};
虚函数的动态绑定
方法一,每个虚函数设置一个函数指针,用来存放对应的函数代码入口地址
方法二,在方法一的基础上加以改进,每个类构建一个虚表,存放不同函数代码入口的地址,每个对象保存一个指向该类虚表的虚表指针。
纯虚函数和抽象类
带有纯虚函数的类是抽象类,抽象类不能实例化,即不能定义对象,但可以定义引用和指针。
派生类如果给出了抽象类中所有纯虚函数的函数实现,那么就不再是抽象类,否则仍然为抽象类。
class Base{
public:
virtual void write() const = 0;
};
class Derived:public Base{
public:
virtual void write() const;
};
int main()
{
Base b;//错误,Base是抽象类
Derived d;//正确,Derived不是抽象类
}
三.static
1.静态变量的生存期和程序的运行期相同
2.局部作用域中的静态变量的特点:不会随着每次函数调用而产生一个副本,也不会随着函数返回而失效。具有全局寿命,局部可见,只有第一次进入函数会被初始化。
3.静态数据成员属于整个类,不属于任何一个对象,由该类的所以对象共同维护,从而实现不同对象间的数据共享
4.类中只是对静态数据成员进行引用性声明,必须在类外进行定义性声明(可以同时初始化) 如果是用const修饰的静态常量,则也可以在类外进行定义性声明,但不能初始化
class Point{
private:
static int x;
const static int y=0;
};
int Point::x=0;
const int Point::y;
5.静态成员函数同样属于类,而不属于任何一个对象。可以通过类名和对象名两种方式调用(没有任何区别)。可以直接访问该类的静态数据和函数成员,而访问非静态成员,必须通过对象名。
四.指针
1.const
const int* ptr=&a;//指向const int类型的指针
int* const ptr=&b;//指向int类型的常量指针
2.void
void* ptr1=&a;
int* ptr2=static_cast<int*>(ptr1);
3.指针数组(数组,类型是指针)
int* ptr[5];
4.数组指针(指针,指向数组)
int (*ptr)[5];
数组指针和普通指针的区别:
int a[5];
int* ptr=&a;//ptr+1指向a[1]
int (*ptr)[5]=&a;//a[]为一个整体,ptr+1后指向a数组后的地址
函数返回数组指针:
int (*solve(int x, int y))[10]
typedef int arr[10];
或者using arr=int[10];
arr* solve(int x, int y)
5.指针函数(函数,返回类型是指针)
int* solve(int x, int y)
6.函数指针(指针,指向函数)
void (*ptr)(int, double);//函数返回类型为void,参数为int和double
typedef void (*PTR)(int, double);
PTR ptr
ptr=solve;
ptr(1, 1.0);
7.指向类的非静态成员函数的指针(需要通过对象来调用)
class Point{
public:
Point(int _x=0):x(_x){};
void write()
{
cout<<x<<endl;
}
void write2(int y)const
{
cout<<x<<" "<<y<<endl;
}
private:
int x;
};
int main()
{
void (Point::*ptr)();
ptr = &Point::write;
void (Point::*ptr2)(int)const = &Point::write2;
Point a(5);
(a.*ptr)();
a.write();
(a.*ptr2)(6);
a.write2(6);
}
8.指向类的静态成员函数的指针(可以不通过对象直接调用)
class Point{
public:
static void write()
{
cout<<x<<endl;
}
static void write2(int y)
{
cout<<x<<" "<<y<<endl;
}
static int x;
};
int Point::x=0;
int main()
{
int* ptr = &Point::x;
void (*ptr2)() = &Point::write;
void (*ptr3)(int) = &Point::write2;
cout<<*ptr<<endl;
ptr2();
ptr3(1);
}
四.动态内存分配
1.new单个元素
Point* ptr = new Point(1,2);
delete ptr;
2.new数组
对于内置数据类型元素(int,double...)需要加()进行初始化为0
对于类元素(手写的类或者string...)可加可不加,都会且只能调用默认构造函数
Point* ptr = new Point[5]();
delete[] ptr;
3.new多维数组
new操作创建多维数组,返回的是数组指针
int (*ptr)[25][10];
ptr = new int[5][25][10];
五.左值右值
1.左右值
在C++11中可以取地址的、有名字的就是左值。反之,不能取地址的、没有名字的就是右值
例如:
int a = b + c;
a = solve(a);
a就是可以取地址的左值,b + c 和 solve(a) 是右值
2.左右值引用
int x = 5;
int &y = x;//正确,左值引用
int &&y = x;//错误,x是左值,不能右值引用
int &&y = (int)x;//正确,通过int强转,将左值x转换成右值5,然后右值引用
int &&y = std::move(x);//正确,通过move将左值x转换成右值5,然后右值引用
int &y = x + 5;//错误,x + 5是右值,不能左值引用
int &&y = x + 5;//正确,右值引用
const int &y = x + 5;//正确,const的特性
const的特性:编译器会为传入的右值创建临时变量,所以cosnt修饰的左值引用其实是引用的这个临时变量。
六.explicit
explicit修饰的函数不允许隐式类型转换在参数中出现,避免自动发生的隐含转换发生错误。
传入的参数的类型必须符合定义,可以显式类型转换使符合定义。
explicit Point(int _x = 0, int _y = 0):x(_x), y(_y){};
七.对象作为函数参数和返回值的传递方式
原文链接:https://blog.csdn.net/qq_41791653/article/details/82354114
1.普通数据作为函数参数的和返回值的传递方式
(1)作为函数参数(形参结合)
主调函数调用被调函数时,主调函数已经被压入了运行栈中,首先将要传递的参数压入运行栈的一段特殊区域中(这段内存,主调函数和被调函数都可以访问到),再将被调函数压入运行栈中(被调函数的形参此时才具有内存,且在此时将主调函数的实参赋值给形参)完成了形参结合。
(2)作为函数返回值
主调函数调用被调函数时,被调函数运行结束,返回需要返回的数据时,编译器先创建一个临时的局部变量,该变量被赋值为需要返回的值,这个变量没有名字且生存期很短,仅存在于主调函数的表达式中。如:
cout<<add(3,4)<<endl;
该句运行完毕,变量生命周期结束。
2.对象作为函数参数
(1)作为函数参数:
要传递的对象已经存在时(两次复制构造)
point a(1,2);
fun1(a);
在运行栈创建一个临时对象,将需要传递的对象赋值给临时对象,再将临时对象赋值给形参。
当要传递的对象是临时创建时(一次复制构造)
fun1(point(4,5));
直接将point对象创建在运行栈上,然后将这个对象赋值给形参。
(2)作为函数返回值:
主流编译器方法:主调函数创建一段内存空间用于存放返回的对象,主调函数调用被调函数时,将该内存空间作为参数传入被调函数中(编译器自动完成,无需程序员自己显性写出)。因此,返回值所生成的对象的内存分配和构造是分步执行的,在主调函数中分配内存,在被调函数中进行构造。
八.const
1.常成员函数
(1)常对象只能调用常函数
(2)常函数不能更新对象的数据成员(除非被mutable修饰),也不能调用对象的非常函数
(3)const可以用于重载函数
void solve();
void solve()const;
非常对象调用solve()函数,两个函数都能匹配,编译器将选择最近的函数(不带const的函数)
2.常数据成员
常数据成员只能通过初始化列表来赋初值
class Point{
public:
Point(int _a = 0);
~Point(){};
private:
const int a;
static const int b;
};
const int Point::b = 0;
Point::Point(int _a):a(_a){};
九.类的继承
1.访问控制
公有继承:公有,不可访问,保护
私有继承:私有,不可访问,私有
保护继承:保护,不可访问,保护
2.类型兼容规则
(1)派生类对象可以隐含转换为基类对象
(2)派生类对象可以初始化基类对象的引用
(3)派生类对象的地址可以隐含转换为基类对象的指针
class Base{
};
class Derived:public Base{
};
int main()
{
Base b;
Derived d;
b = d;//规则(1)
Base &rb = d;//规则(2)
Base* ptr = &d;//规则(3)
}
3.派生类的构造函数
构造函数执行顺序:
(1)基类,按照继承的声明顺序
(2)派生类,按照类中的声明顺序
(3)构造函数体中的内容
class Base1{
public:
Base1(int x = 0){cout<<"Base1 "<<x<<endl;};
};
class Base2{
public:
Base2(int x){cout<<"Base2 "<<x<<endl;};
};
class Base3{
public:
Base3(int x = 0){cout<<"Base3 "<<x<<endl;};
};
class Derived:public Base1, public Base2, public Base3{
public:
Derived(int a, int b, int c, int d):Base2(c), member2(a), Base1(b), member1(d){};
private:
Base1 member1;
Base2 member2;
Base3 member3;
};
int main()
{
Derived d(1, 2, 3, 4);
}
/*
Base1 2
Base2 3
Base3 0
Base1 4
Base2 1
Base3 0
*/
4.派生类的复制构造函数
默认复制构造函数会先基类,再新增成员对象,一一执行复制
复制构造函数的写法与构造函数类似
Derived::Derived(const Derived &p):Base1(p), Base2(p), Base3(p){}
5.派生类的析构函数
默认析构函数的执行顺序与构造函数的执行顺序相反
6.虚基类
1.不使用虚基类:最远派生类中有多个同名副本,可以通过直接基类名来唯一标识,可以存放不同的数据,进行不同的操作,可以容纳更多的数据。
2.使用虚基类:只维护一份成员副本,使用更简洁,内存更节省。
3.虚基类的构造函数:
(1)执行虚基类(直接或间接)的构造函数
(2)执行其他基类的构造函数,但构造过程中不会再执行虚基类的构造函数
(3)新增成员对象
(4)构造函数的函数体
#include<bits/stdc++.h>
using namespace std;
class Base0{
public:
Base0(int _var = 0):var0(_var){};
int var0;
};
class Base1:virtual public Base0{
public:
Base1(int _var = 0):Base0(_var), var1(_var){};
int var1;
};
class Base2:virtual public Base0{
public:
Base2(int _var = 0):Base0(_var), var2(_var){};
int var2;
};
class Derived:public Base1, public Base2{
public:
Derived(int var = 0):Base0(var), Base1(var), Base2(var){};
int var;
};
int main()
{
Derived d(5);
}
十.运算符重载
加减乘除以及后置的自加返回的都是右值,而前置自加返回的是左值
注意:当重载了右值=后,c++11会将默认复制构造函数delete,那么就需要手写一个复制构造函数
class Number{
public:
Number(int _val = 0):val(_val){};
Number(const Number &p)
{
val = p.val;
}
Number operator+(const Number &p);
Number operator-(const Number &p);
Number& operator++();
Number operator++(int);
Number& operator=(const Number&p);
Number& operator=(const Number&&p);
void write()
{
cout<<val<<endl;
}
private:
int val;
};
Number& Number::operator=(const Number &p)
{
cout<<"Lvalue"<<endl;
val = p.val;
return *this;
}
Number& Number::operator=(const Number &&p)
{
cout<<"Rvalue"<<endl;
val = p.val;
return *this;
}
Number Number::operator+(const Number &p)
{
val+=p.val;
return *this;
}
Number Number::operator-(const Number &p)
{
val-=p.val;
return *this;
}
Number& Number::operator++()
{
val++;
return *this;
}
Number Number::operator++(int)
{
Number lst = *this;
++(*this);
return lst;
}
十一.函数模板与类模板
1.函数模板
被多个源文件引用的函数模板,应当把函数体一同放在头文件中,不能只将声明放在头文件中
template <typename T>
void write(T* a, int n)
{
for(int i=0;i<n;i++)
cout<<*(a+i)<<endl;
}
2.类模板
template <class T>
class Point{
public:
Point(){p = NULL; n = 0;}
Point(int _n);
Point(const Point& b);
~Point();
Point& operator=(Point& b);
Point& operator=(Point&& b);
private:
T* p;
int n;
};
template <class T>
Point<T>::Point(int _n)
{
cout<<"construct"<<endl;
n = _n;
p = new T[n];
}
template <class T>
Point<T>::~Point()
{
if(p!=NULL)
{
delete[] p;
p = NULL;
n = 0;
}
}
template <class T>
Point<T>::Point(const Point& b)
{
cout<<"Copy"<<endl;
n = b.n;
p = new T[n];
memcpy(p, b.p, n * sizeof(T));
}
template <class T>
Point<T>& Point<T>::operator=(Point& b)
{
cout<<"Lvalue"<<endl;
if(p!=NULL && this!=&b)
{
delete[] p;
}
n = b.n;
p = b.p;
b.n = 0;
b.p = NULL;
return *this;
}
template <class T>
Point<T>& Point<T>::operator=(Point&& b)
{
cout<<"Rvalue"<<endl;
if(p!=NULL && this!=&b)
{
delete[] p;
}
n = b.n;
p = b.p;
b.n = 0;
b.p = NULL;
return *this;
}
int main()
{
Point<int> a(5), b(7);
a = Point<int>(6);
a = b;
}