C++常用知识大总结
B站c++教程
内存四区
代码区:存放函数体二进制代码,有操作系统管理
全局区:存放全局变量,静态变量(static修饰变量)和常量(string类型数据,const修饰的内容)
栈区:由编译器自动分配和释放,存放函数参数值,局部变量。(注意:不要再函数里返回函数变量地址,会被编译器自动释放然后丢失)
堆区:由程序员自动分配和释放。不释放就由系统回收。
输出变量地址,可以获得结论。
new
new/malloc 区别
使用new申请的内存由free store自由存储区分配给程序员,malloc是堆区,可以由程序员自行使用。相比于malloc,new更安全,无需指定内存大小,允许重载,可以初始化数组对象,返回完整类型指针,默认抛出异常。
int *a=new int(10);//为a开辟1个int,4字节空间
//如果这个作为函数成员变量,函数结束后不会自动释放;
delete a;//释放a内存
int *arr=new int[10];//为arr分配大小为10个int的数组
delete [] arr;//释放arr数组
new operator和operator new区别
new就是new operator. operator new调用了malloc分配内存。
当使用new关键字时,编译器:
1.用operator new()分配内存
2.调用对应数据类型(string,int等等)构造函数
3.返回对应类型指针
placement new
保持一块内存反复构造,析构。可以省去反复分配内存。
引用
引用:给变量起别名,外号。
int a=10;
int &b=a;//使用b这个称号也能操作数值10
//注意引用必须初始化,int &b写法是错误的
//引用初始化后不能改变
//引用 的外号 可以操作变量内存
b=20;
cout<<b<<endl;//结果是20
- 引用 做函数参数
用swap(a,b)作为例子,地址传递和引用的方法一样,但是写法更简洁
void swap1(int *a,int *b){
int tmp=*a;//tmp 为一个地址
*a=*b;
*b=temp;
}
void swap2(int &a,int &b){//引用写法写入实参
int c=a;
a=b;
b=c;
}
- 引用作函数返回值
// !!不能返回局部变量引用
int& test(){
static int a=10;//如果不添加static,返回引用就会失败,因为栈区内存被编译器释放
return a;
}
int main(){
int &ref=test();//ref=a=10
test()=1000;//引用使得函数可以变成左值被赋值,ref=a=1000
}
-
引用和指针的区别。
- 引用有指针的一部分功能,可以看作常量指针,int &tmp=a;与int const * tmp =a基本一致。 - 再之后的调用tmp=20就相当于 *tmp=20一样。tmp指向的这个地址是不可修改的常量
static const
- 关键字 static 有什么作用?
1.隐藏,当我们同时编译多个文件时, 所有未加static前缀的全局变量和函数都全局可用,用static
定义的不同文件的同名函数和变量不用担心命名冲突。
2.使变量内容持久,程序刚开始就会把静态变量初始化,默认的初始化值为0,并且不做修改,两种变量存储在静态存储区:全局变量和static变量。
静态成员函数 static func().静态成员函数 只能访问静态变量
- 关键字 const 有什么作用?(constant缩写:不变的)
const 修饰的内容不可改变。const修饰的函数变量参数返回值受到强制保护。
例如:const int N=1e5;
类与对象
封装
类的关键字和方法
public 内容都可以访问
protected 需要访问权限,子类可以访问
private 需要访问权限,子类不可以访问,像银行卡密码一样
struct里面默认public,class里面默认private
C++创建class会默认创建三个函数,构造(用于初始化对象),析构(释放对象后运行),拷贝函数(复制对象)。
#include<iostream>
using namespace std;
class Person {
//不宣称类型就会默认private
public:
string person_name;
int age=18;
Person(string name) {//构造
person_name=name;
}
~Person() {//析构
cout<<"对象被销毁了"<<endl;
}
Person(const Person &p){//拷贝
person_name= p.person_name;
age=p.age+1;
}
};
int main(){
//初始化列表也可以构造对象
Person jack=Person("jack111");
cout<< jack.person_name<<endl;
cout<< jack.age<<endl;
Person jim=Person(jack);
cout<< jim.age <<endl;
system("pause");//这句结束才执行析构函数
}
A类为B类属性时,先构造B,再构造A。释放是先析构A,再析构B;
浅拷贝 深拷贝
默认的拷贝函数只是浅拷贝,浅拷贝会复制地址,地址是同一个地方。浅拷贝存在一个问题就是:会导致堆区的内存被重复释放导致异常。(图中m_height是new申请的内存)
因此实现深拷贝需要再分配一份行的空间(地址),将原数据复制。
public:
Person (const Person &p){
m_height=new int(*p.m_height);
//p.m_height是一个地址,*是解析地址,读取地址里的数据
}
继承
写法:
三种继承方式的差异
且父类中所有非静态的成员都会继承,private属性不可访问,但任然会继承
- 子类父类的顺序和使用成员类属性一样。
- 访问父子同名成员名/函数,默认子类,访问加父类作用域就行(这里被称为隐藏)
- 多继承
多态
多态分为两类:
静态多态:函数/运算符重载,复用函数名
动态多态:通过派生类和虚函数实现运行时多态。
区别:静态多态地址早绑定会在编译时就确定函数地址,动态则是晚+运行时确定。
- 下面是展示区别例子:
地址早绑定会导致只执行父类的方法
在父类 方法前加上关键字virtual使得函数编程虚函数就可以实现地址晚绑定。
#include<iostream>
using namespace std;
class Animal {
public:
virtual void speak(){//不加virtual将会是另一种结果
cout<<"动物在说话"<<endl;
}
};
class Cat : public Animal{
public:
void speak(){
cout<<"猫猫在说话"<<endl;
}
};
void doSpeak(Animal &animal){
animal.speak();
}
void test(){
Cat cat;
doSpeak(cat);
}
int main(){
test();
}
虚函数内存分析:
纯虚函数抽象类
重载,隐藏,重写(覆盖)
- 重载,隐藏,重写(覆盖)三者区别?
- 重载:是指同一访问区内被声明的几个具有不同参数列的同名函数,根据参数列表决定调用。
返回值类型不同不能作为重载标识
void test1(int a){
cout<<"11111";
}
//const int a 可以作为重载条件
void test1(){
//这就是重载test1,两个函数根据参数不同可以一起使用,但返回值类型不同不能作为重载标识
cout<<"00000";
}
struct node{
int id,pos,d;
bool operator <(const node&x)const {//运算符重载
return pos<x.pos;
}
}
//这里就是程序对 < 功能的重载,< 传入参数会判断是node结构体还是具体数值
//使用用不同的比较标准来开始比较
- 隐藏:是指派生类的函数屏蔽了与其同名的基类函数。只要同名就会被隐藏。比如static修饰的同名函数会被隐藏掉。
- 重写(覆盖):是指派生类中存在重新定义的函数。
与其函数名,参数列表,返回值类型都必须与原来重写的函数一致,只有函数体不同,
派生类只会调用重写的函数,而不会调用原函数(被重写函数)
(这个和java子类重写父类方法有点相似)
运算符重载
写法: 返回值类型 operate 运算符 (返回值类型 & 参数名){}
调用: p.operate+(),也可以按算符原先的写法写。
通过成员函数重载就传入一个参数,全局函数就传入两个。
重载+号
重载<< 运算
用全局函数来重载<<实现结构体所有属性的输出。