C++学习笔记

内存操作

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();//这个函数的声明就很明显
    
构造函数的分类
  • 按照参数分类

    1. 有参构造
    2. 无参构造
  • 按照类型分类

    1. 普通构造

    2. 拷贝构造

class Circle{
public:
    int r;//半径
    //普通构造函数
    Circle(int temp){
    	r = temp;
    }
    //拷贝构造函数
    Circle(const Person &p){
    	age = p.age
    }
    
}

构造函数的调用
  1. 括号法

    Circle c1;//无参构造函数调用
    Circle c2(10);//有参构造函数调用
    Circle c3(c2);//拷贝构造函数调用
    
  2. 显示法

    Circle p2 = Circle(10);
    Circle(10);//匿名对象
    

    **匿名对象特点:**当前行执行结束后,系统立即回收匿名对象

    **注意!!!**不要利用拷贝构造函数初始化匿名对象

    Circle p2 = Circle(10);
    Circle(p2);//编译器会认为 Circle(p2) 等价于 Person p2,即利用无参构造创建一个新的对象,导致和上一行代码重定义了
    
  3. 隐式转换法

    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 是对象本体

  1. 当形参和成员变量同名时,可用this指针来区分

  2. 在类和非静态成员函数中返回对象本身,可使用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(){
        
    }
}

多态实现条件

  1. 具有继承关系
  2. 子类重写了父类的虚函数

多态使用

父类的指针或者引用 执行对象为子类

多态的优点

  1. 组织结构清晰
  2. 可读性强

运算符重载

对已有的运算符重新定义,叫做运算符重载

重载方法,在类中利用成员函数

返回数据类型 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
}

加号重载

  1. 通过成员函数重载 ”+“ 号
  2. 通过全局函数重载 “+” 号

函数调用运算符重载

也被称为仿函数

纯虚函数和抽象类

纯虚函数: virtual 返回类型 函数名 (形参) = 0;

当一个类具有了纯虚函数,那么他就是抽象类

抽象类无法实例化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值