1.C++的动态内存
new/delete
new[]/delete[]
操作符
new和malloc的区别?
2.C++的引用
引用的底层实现其实就是指针
引用必须初始化 一经初始化不能更改目标 引用不能为空
引用提高传参效率,节省内省,比指针操作简单
引用与指针的区别?
3.C++的类型转换
隐式类型转换(自动)
强制类型转换
显示类型转换:
static_cast<>()
const_cast<>()
reinterpret_cast<>()
dynamic_cast<>()
4.C++面向对象
类: 一为事物的抽象描述 泛化的概念 泛指
对象: 类实例化具体化的结果 特指
抽象: 把事物共同的特征抽象为属性 把事物共同的行为抽象为方法
封装: 把一类事物用类的语言来描述,加以访问控制属性的限制
public\protected\private
类的语法:
class 类名{
//成员属性
//成员方法
//构造方法 函数
//没有返回值类型(也不能是void) 函数名和类名一样 可以重载 有初始化列表
//如果一个类没有提供构造方法,那么会自动添加一个无参构造方法 一旦显式提供则不再提供默认的无参构造
//初始化列表的执行顺序只与成员属性的声明顺序有关
类名(形参列表):初始化列表{
}
//成员方法中有一个隐含的this指针 指向正在调用或者构造的那个对象
//当成员变量与局部变量同时名 可以用 this-> 来访问成员变量
//常方法
返回值类型 方法名(形参列表)const{
//const修饰的是 this 指针所指向的对象
}
};
对象的实例化
类名(实参列表); 类名 变量名(实参列表);
类名 变量名;
类名 变量名(); //不是实例化对象 而是声明一个函数
new 类名;
new 类名();
new 类名(实参列表);
常对象: 具有常属性的对象const
常对象只能调用常方法(不能调用非常成员方法)
非常对象 可以 自动提升为 常对象
但是常对象的 const 属性 不能随便去掉
常版本与非常版本的 成员方法 构成重载
非常对象如果在调用时 有 非常版本的函数 调用 非常版本函数 如果没有则也可以调用常版本的函数
对于重载:
参数列表不同:
参数类型不同: 指针和引用的常属性不同也构成重载
常成员方法中不能对成员属性进行修改 const *this
但是如果用mutable关键字修饰的成员属性 就可以被修改
静态属性(类属性) 和 静态方法(类方法)
属性类 而不属于某个对象
所有该类的对象共同拥有一份 类属性存储位置和普通属性存储的位置不一样
静态属性必须在类外声明初始化
静态方法中没有隐含的this指针 所以在静态方法中不能访问成员属性和调用成员方法
静态方法中只能访问静态属性和调用静态方法
静态属性和静态方法也可以通过普通对象来访问和调用
静态属性和静态方法相当于是加了类作用域的全局变量和函数
单参构造:
如果一个类有单参构造函数
A(B b){}
那么任何B类型的对象都可以自动(调用该构造函数)转换为A类型对象
如果要禁止自动(必须明确调用构造函数 A(b)) 则在构造方法前加 explicit
必须使用初始化列表的情况:
1.常属性成员变量
2.引用型成员变量
3.没有无参构造的类类型的成员变量
构造对象时,先按照声明顺序去构造 成员变量,即调用每个对象所属类的无参构造函数 最后执行本类的构造函数体
析构时 正好和构造的顺序相反
5.析构函数
一个类有一个默认的无参构造函数 该函数没有实现任何功能
当一个对象消亡或者 delete时 就会调用该构造函数
所以当一个对象在构造时如果new的动态内存,那么就需要自己手动实现析构函数
在析构函数中释放之前new申请的动态内存
析构函数: 没有返回值类型 也不是void
~类名(){
}
析构函数不能重载 因为析构无参
6.拷贝构造函数(深拷贝 、 浅拷贝)
用一个已经存在的对象来构造一个新的同类型的对象 调用的就是拷贝构造函数
所谓拷贝构造 本质 就是复制克隆一个新对象
编译器会自动给类提供一个拷贝构造函数
拷贝构造函数形如:
类名(const 类名& other){
}
默认的拷贝构造函数实现的方式是按字节拷贝(memcpy) 构造的新对象 和 旧对象是完全一模一样
按字节拷贝持方式是浅拷贝
深拷贝:
拷贝指针所指向内存地址中的数据 而不是拷贝指针的值本身
之间旧的C++版本对同一块动态内存多次delete会出错
如果一个类需要实现深拷贝的功能 则需要实现拷贝构造函数
用一个旧的对象来构造一个新的对象
类名 对象 = 旧对象;
类名 对象(旧对象);
void func(类名 obj){}
func(对象);
旧对象 = 旧对象; 两个对象都已经存在了,则不会调用拷贝构造
对象之间可以用 = 进行相互赋值
默认的赋值是按字节拷贝的形式进行赋值
如果是指针 则复制指针的值 并不是把指针所指向内存中的数据进行复制
7.拷贝赋值函数
如果没有提供拷贝赋值函数 则编译器会自动提供拷贝赋值函数
拷贝赋值函数默认实现也是按字节复制(浅拷贝)
如果有需要实现深拷贝,则需要自己实现
形如:
类名& operator=(const 类名& other){
}
8.string
class string{
private:
char *s;
public:
string(const char *str);
~string();
string(const string& str);
string& operator=(cosnt string& str);
};
8.默认的无参构造 拷贝构造 拷贝赋值函数
默认无参 调用 成员的无参构造
如果需要调用指定的有参 需要在初始化列表
拷贝构造 调用 成员的拷贝构造 进行 构造
拷贝赋值 调用 成员的拷贝赋值函数 进行 赋值
在实现拷贝构造和拷贝赋值函数的时候,类类型成员需要手动调用拷贝构造 和 拷贝赋赋值
class string{
private:
char *str;
public:
string(const char* s=""):str(strcpy(new char[s==NULL?1:strlen(s)+1],s==NULL?"":s)){
}
string(const string& s):str(strcpy(new char[strlen(s.str)+1],s.str)){
}
string& operator=(const string& s){
if(this != &s){
string stmp(s);
swap(str,stmp.str);
}
return *this;
}
~string(){
if(str != NULL){
delete [] str;
str = NULL;
}
}
};
9.成员属性指针 成员函数指针
定义成员属性指针变量
属性类型 类名::*指针变量;
初始化 or 赋值
属性类型 类名::*指针变量 = &类名::属性名; //不是一个真实的地址
指针变量 = &类名::属性名;
成员指针直接解引用 对象
对象.*成员指针
成员指针间接解引用 对象的指针
对象的指针->*成员指针
定义成员函数指针变量
成员函数返回值 (类名::*指针)(成员函数形参列表);
初始化 or 赋值
成员函数返回值 (类名::*指针)(成员函数形参列表) = &类名::成员函数名;
指针 = &类名::成员函数名;
成员函数指针直接解引用 对象
(对象.*成员函数指针)(实参列表);
成员函数指针间接解引用 对象的指针
(对象的指针->*成员函数指针)(实参列表);
2. friend
在一个类里面 可以声明另外的类为该类的友元类
在友元类中可以访问该类对象的私有属性和私有函数
也可以声明一个全局函数为该类的友元函数 则在友元函数中也可以访问该对象的私有内容
友元的声明是单向性的
3.运算符重载
在对应的class中提供对应的 运算符函数 来实现功能
本质上是一个函数
返回值类型 operatorX(形参列表){
}
拷贝赋值: 重载=运算符
重载输入输出运算符:
只能以友元函数的形式重载
//重载输出运算符
friend ostream& operator<<(ostream& os,const 类名& 对象){
}
//重载输入运算符
friend istream& operator>>(istream& is,类名& 对象){
}
对运算符进行分类 按操作数的个:
1.单目运算符 +(正) -(负) *(取值) &(取地址) !(逻辑非) ~(按位取反) ++ --
#obj; ---> obj.operator#()
2.双目运算符
<< >> 两个操作数对象的类型不一样
cout << n; cout n
cin >> n;
o1 # o2 o1[o2] --> o1.operator(o2)
cout.operator<<(stu) ostream类型早已封装好了 不能给ostream类重载这样一个成员函数
<< 和 >> 运算符 只能以全局变量的形式进行重载
3.三目运算符 ?: 三目运算符不可以进行重载
以成员的方式重载运算符 第一个操作对象隐身为*this
单目运算符以成员方式重载 没有 参数
双目运算符以成员方式重载 只有一个参数
写个Complex类
实现 + - * / << >>
+ - 以友元方式重载
* / 以成员方式重载
- 重载负数运算符
~ 调换实部和虚部
++ -- 运算符重载
用哑元来区分前++--和后++--
类名& operator++(){}
const 类名 operator++(int){}
[] 一般来说都会实现一个常版本 和一个非常版本
() 重载()运算符的类的对象 称为 函数对象 因为这种类型的对象 可以像函数一样调用
C语言中用函数指针来实现回调
C++中用函数对象来实现回调
A类中有B作为参数的单参构造函数 B类型对象---> A类型的对象
class A{
public:
A(B& b){}
};
重载类型运算符:
class A{
operator B(){
return B类型对象;
}
}
A类型对象都可以自动转换成B类型的对象
注意: A 类型对象怎么可以自动转换为 B类型的对象? A->B
1. 在B类中添加A类型的单参构造
2. 在A类中重载B类型的类型运算符
new/delete new[]/delete[]
可以以全局函数的方式重载
也可以以类的静态方式重载
注意: == 和 != 成对出现
排序 查找 映射 需要重载 <
1.不能重载的运算符
. :: sizeof typeid .*
2.只能以友元方式重载
<< >>
3.只能以成员方式重载
= [] ()
4.不能创建新的运算符
x**y
5.运算符重载应该保持其原有的含义和运算规则