内存操作
new操作符
极度相似与C的 malloc
int main(){
int *i;
i = new int(10);//在堆区创建整型数据
}
注意事项
- 只要使用到了new操作符,一定要在一个地方写释放该内存空间的代码,包括对象的销毁时,要在析构函数中写
delete操作符
int main(){
int *i = new int(10);//在堆区创建整型数据
delete i;//释放了new出来的内存空间
i = NULL;//避免野指针
}
函数
默认参数
int func(int a, int b = 10){
return a+b
}
int main(){
sum = func(1,2);//sum = 3
sum = func(1);//sum = 11
}
注意事项:
- 如果函数形参的某个位置有了默认参数,那么从这个位置往后,从左到右都必须有默认值
- 函数的声明和实现,只能有一个有默认参数
占位参数
int func(int a, int ){
return a
}
函数重载
int func(int a){
return a
}
float func(float a){
return a
}
类
封装
基本形式:
class 类名{
访问权限:
属性
行为
}
class Circle{
public:
int r;//半径
const double pi =3.14
//计算周长
double calcZC(){
return 2*pi*r
}
}
int main(){
Circle c1;
c1.r = 10;
cout<<"圆的周长为:"<<c1.calcZC()<<endl;
}
访问权限
- public 公共权限 (类内可以访问,类外也可以,子类也可以)
- protected 保护权限 (类内可以访问,类外不可以,子类也可以)
- private 私有权限 (类内可以访问,类外不可以,子类不可以)
struct 和 class的区别
struct 的默认权限为公共
class 的默认权限为私有
构造函数
class Circle{
public:
int r;//半径
//构造函数
Circle(int temp){
r = temp;
}
}
注意事项
-
构造函数在对象创建时自动调用
-
构造函数必须在public作用域内
-
构造函数可以发生重载
-
调用无参 构造函数时,不要加(),因为编译器会认为这是一个函数的声明
//简单对比一下 Circle c1;//这是正确的调用默认构造函数 Circle c1();//这时编译器认为在声明一个叫 c1,返回值为Circle的函数 void c2();//这个函数的声明就很明显
构造函数的分类
-
按照参数分类
- 有参构造
- 无参构造
-
按照类型分类
-
普通构造
-
拷贝构造
-
class Circle{
public:
int r;//半径
//普通构造函数
Circle(int temp){
r = temp;
}
//拷贝构造函数
Circle(const Person &p){
age = p.age
}
}
构造函数的调用
-
括号法
Circle c1;//无参构造函数调用 Circle c2(10);//有参构造函数调用 Circle c3(c2);//拷贝构造函数调用
-
显示法
Circle p2 = Circle(10); Circle(10);//匿名对象
**匿名对象特点:**当前行执行结束后,系统立即回收匿名对象
**注意!!!**不要利用拷贝构造函数初始化匿名对象
Circle p2 = Circle(10); Circle(p2);//编译器会认为 Circle(p2) 等价于 Person p2,即利用无参构造创建一个新的对象,导致和上一行代码重定义了
-
隐式转换法
Circle c1 = 10;//等价于 Circle c1 = Circle(10)
注意事项
- 如果用户定义了有参构造函数, C++不再提供无参构造,但会提供拷贝构造
- 如果用户定义了拷贝构造,C++不再提供任何默认构造
析构函数
class Circle{
public:
int r;//半径
//析构函数 进行清理的操作
~Circle(){
}
}
注意事项
- 析构函数在对象销毁前会自动调用
- 构造函数必须在public作用域内
- 构造函数不可以发生重载
- 构造函数不可以有参数
深拷贝与浅拷贝
浅拷贝的问题
class Person{
public:
int age;
int *height;
Person(int temp_age, int temp_height){
age = temp;
height = new int(height)
}
~Person(){
if(height != NULL){
delete height;
height = NULL;
}
}
}
void func1(){
Person p1(10,180);
Person p2(p3);//这是浅拷贝
}
int main(){
func1();
//执行完func1函数后,程序会报异常,即堆区内存被重复释放
/*
解释:
根据先进后出,程序会先释放p4,那么p4.height所指向的内存空间被释放了
然后再释放p3,由于是浅拷贝,p3.height所指向的空间和p4.height原先指向是一致的,所以会导 致程序抛异常:p3.height内存空间重复释放
*/
}
注意事项
- 浅拷贝也就是直接调用了默认拷贝构造函数,是把指针的地址原封不动的拷贝
深拷贝的实现
class Person{
public:
int age;
int *height;
Person(int temp_age, int temp_height){
age = temp;
height = new int(height)
}
//自己实现拷贝构造函数 解决浅拷贝的问题
Person(const Person &p){
age = p.age;
//height = p.height; 编译器吗,默认实现的就是这行代码
height = new int(*p.height);
}
~Person(){
if(height != NULL){
delete height;
height = NULL;
}
}
}
void func1(){
Person p1(10,180);
Person p2(p3);//这是浅拷贝
}
int main(){
func1();
//执行完func1函数后,程序会报异常,即堆区内存被重复释放
/*
解释:
根据先进后出,程序会先释放p4,那么p4.height所指向的内存空间被释放了
然后再释放p3,由于是浅拷贝,p3.height所指向的空间和p4.height原先指向是一致的,所以会导 致程序抛异常:p3.height内存空间重复释放
*/
}
注意事项
- 如果属性又在堆区开辟的,一定要自己提供拷贝构造函数
初始化列表
class Person{
public:
int age;
int height;
//无参构造函数与初始化列表的结合,当调用无参构造函数时,初始化列表的属性也会被赋值
Person():age(10), height(180){
}
//上面构造函数的缺点是age、height的值写死了,用下面的有参构造改进
Person(int temp_age,int temp_height):age(temp_age), height(temp_height){
}
}
静态成员
就是在成员变量和成员函数前加上关键字 static,称为静态成员
-
静态成员变量
-
所有对象共享一份数据
-
在编译阶段分配内存
-
类内声明,类外初始化
-
可以通过类名进行访问
//类外初始化例子 class Person{ public: static int a; } int Person::a = 100;//类外初始化,不一定要在函数内 int main(){ //1.通过实例化对象访问 Person p1; cout<<p1.a<<endl; //2.通过类名进行访问 cout<<Person::a<<endl; }
-
静态成员变量也被访问权限所限制,但类外赋值不被限制
-
-
静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
- 静态成员函数也被访问权限所限制
C++对象模型
C++中,类的成员变量和成员函数分开存储
C++编译器会给每个空对象也分配一个字节空间(用sizeof函数看),但如果不是空对象,里面有一个以上的成员变量,那么对象大小就是成员变量大小之和
静态成员变量不属于类对象的大小方位
this指针
由C++对象模型可知,同一类的多个对象,对于类中的某个方法,是公用一块代码的,那么这一块代码要如何区分是哪个对象在调用自己?——this指针
this的用途
this是指向对象的指针,而*this 是对象本体
-
当形参和成员变量同名时,可用this指针来区分
-
在类和非静态成员函数中返回对象本身,可使用return *this
注意了,此用途可以实现链式编程思想
class Person{ public: int a; Person(int a){ this->a = a; } //这里要返回Person&,而不是Person,如果是值返回的话,会被编译器处理成创建一个新的Person对象 Person& aPlusn(int n){ this->a += n; return *this } } int main(){ Person p1(10); p1.aPlusn(1).aPlusn(1).aPlusn(1);//这就是链式编程,最终p1.a = 13 }
注意事项
- this指针是常量,不可以修改的
空指针调用成员函数
类的空指针是可以调用自身的成员函数的,但是在函数中是不能访问类中的属性
const修饰成员函数
常函数:
- 在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
- 常函数不能修改对象内属性
- 被mutable修饰的属性可以被修改
常对象:
- 在实例化对象前加const,变为常对象
- 常对象的属性不能被修改
- 被mutable修饰的属性是可以被修改
- 常对象只能调用常函数
class Person{
public:
int a;
mutable int b;
Person(int a){
this->a = a;
}
void sayHello() const{
b = 100;//被mutable修饰的属性是可以被修改
}
}
int main(){
Person p1(10);
p1.a = 0;
p1.b = 0;
const Person p2;
//p2.a = 0;//报错
p2.b = 0;
}
友元
在程序里,有些私有属性,想让类外的特殊的函数或类进行访问,就需要用到友元
关键字:friend
友元的三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
class House{
friend void goodGay(House *house);//全局函数做友元
friend class GoodGay;//类做友元
friend void BadGay::visit();//成员函数做友元
public:
House(){
SittingRoom = "客厅";
BedRoom = "卧室"
}
public:
string SittingRoom;
private:
string BedRoom;
}
class GoodGay{
public:
House *house
GoodGay(){
house = new House;
}
//类做友元
void visitRoom(){
cout << "参观" << house.BedRoom << endl;//如果在House类中没有用friend声明本类的话,这行会报错
}
}
class BadGay{
public:
House *house
BadGay(){
house = new House;
}
//成员函数做友元
void visitRoom(){
cout << "参观" << house.BedRoom << endl;//如果在House类中没有用friend声明本方法的话,这行会报错
}
}
//全局函数做友元
void goodGay(House *house){
cout << house.BedRoom << endl;//如果在House类中没有用friend声明本函数的话,这行会报错
}
继承
//让C++继承C
class C{
public:
void sayInt(){
cout<<"我有int类型数据"<<endl;
}
}
class CPP : public C{
}
int main(){
CPP newCpp;
newCpp.sayInt();
}
继承方式
语法:class 子类 : 继承方式 父类
继承方式的区别
- 公共继承:父类的公共权限内容到子类中还是公共权限,保护权限内容到子类中还是保护权限,父类的私有权限无法访问
- 保护继承:父类的公共权限内容到子类中变成保护权限,保护权限内容到子类中还是保护权限,父类的私有权限无法访问
- 私有继承:父类的私有权限无法访问,其他权限内容到子类中变为私有权限、
继承中的对象模型
父类中的私有属性也会被继承下去,只是被隐藏了,无法被访问,所以内存空间还是包含了父类的private
同名属性和同名方法
如果子类中定义了合父类同名的属性或方法,直接调用就是默认用的子类的,
用父类的使用起来也简单,加个作用域,比如
cout << 子类名.父类名::属性名 << endl;
子类名.父类名::方法名
多继承语法
C++运行一个子类继承多个父类
多态
地址早绑定
在编译阶段就确定了方法的所属地址(走父类还是子类)
**实现方式:**正常定义方法
地址晚绑定
在运行时才确定方法的所属地址(走父类还是子类)
实现方法:在父类的方法声明前一格加 virtual ,称为虚函数
class Animal{
public:
//虚函数
virtual void speak(){
}
}
多态实现条件
- 具有继承关系
- 子类重写了父类的虚函数
多态使用
父类的指针或者引用 执行对象为子类
多态的优点
- 组织结构清晰
- 可读性强
运算符重载
对已有的运算符重新定义,叫做运算符重载
重载方法,在类中利用成员函数
返回数据类型 operator运算符 (形参列表){
}
//例子
class A{
public:
int a;
int operator+ (b){
return this.a+b
}
}
int main(){
A m_a;
m_a.a = 1;
cout << m_a.a+1 << endl
}
加号重载
- 通过成员函数重载 ”+“ 号
- 通过全局函数重载 “+” 号
函数调用运算符重载
也被称为仿函数
纯虚函数和抽象类
纯虚函数: virtual 返回类型 函数名 (形参) = 0;
当一个类具有了纯虚函数,那么他就是抽象类
抽象类无法实例化