C++学习1

前言

C++学习笔记

一、内存分区模型

C++程序在执行时,将内存大方向划分为4个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值、局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

内存四区的意义:
不同区域存放的数据,赋予不同的生命周期,给予更大的灵活性。
C++中在程序运行前分为全局区和代码区。
在这里插入图片描述
常量分为:字符串常量和const修饰的变量
在这里插入图片描述

栈区

由编译器自动分配释放,存放函数的参数值,局部变量等
注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
栈区的数据在函数执行完后自动释放

// 指针本质也是局部变量,放在栈上,指针保存的数据是放在堆区
int *p = new int(10); 
堆区

由程序员分配释放,若程序员不释放,程序结束时由操作系统回收。在C++中主要利用new在堆区开辟内存,想要释放该内存空间使用delete关键字

delete p;
new的基本语法
int * func(){
	// new 返回是 该数据类型的指针
	int *p = new int(10);
	return p;
}
// new 关键字开辟数组 
int * arr = new int[10];
// 释放数组 
delete [] arr;

二、引用

引用的本质就是给变量取别名
数据类型 &别名 = 变量名;
注意:

  1. 引用必须要初始化

    int &a; //错误
    

2.引用一旦初始化就不能更改

int a = 10;
int &b = a;
int c = 20;
b = c; // 这是赋值操作,不是更改引用
引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参

// 值传递 形参不会修饰实参
void swap01(int a, int b){
    int temp = a;
    a = b;
    b = temp;
}
// 地址传递 形参会修饰实参
void swap02(int * a, int * b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 引用传递 形参会修饰实参
void swap03(int &a, int &b){
    int temp = a;
    a = b;
    b = temp;
}
int main(){
    int a = 10;
    int b = 20;
    swap01(a, b);
    cout << a << endl; // 10
    cout << b << endl; // 20
    swap02(&a, &b);
    cout << a << endl; // 20
    cout << b << endl; // 10
    swap03(a, b);
    cout << a << endl; // 10
    cout << b << endl; // 20

}

引用传递能修改实参的原因是因为引用的本质实参的别名,即还是将实参传递到交换函数里面了,而值传递则是重新复制了一份实参。

引用做函数返回值

作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数作为左值

int& test(){
    static int a = 10; // 静态变量 存放在全局区
    return a;
}
int main(){
    int& b = test();
    cout << b << endl; // 10
    cout << b << endl; // 10
    test() = 100; 
    cout << b << endl; // 100
    cout << b << endl; // 100
}

test()返回的是a 变量, 即a=100; 而b是a的别名。

引用的本质

引用的本质在C++内部实现就是一个指针常量!

int a=10;
int &ref = a;
ref = 20; // 内部自动帮我们转换:*ref=20;

由于引用是指针常量,故一旦初始化就不能修改!

常量引用

作用:常量引用主要用来修饰形参,防止误操作。
在函数形参列表中,可以加const修饰形参,防止形参改变实参。

int a=10;
// int &ref = 10; // 报错,引用必须引一块合法的内存空间
const int &ref = 20; // 可以,编译器将代码进行了修改 int temp =20; const int &ref = temp;
void show(const int & val){
}

三、函数重载

作用:函数名可以相同,提高复用性
函数重载满足条件:

  1. 同一个作用域下
  2. 函数名称相同
  3. 函数参数类型不同个数不同顺序不同

注意:函数的返回值不作为重载的条件

四、类和对象

访问权限:public, protected, private

  • public: 成员类内可以访问,类外不可以访问
  • protected:类内可以访问,类外不可以访问,子类可以访问父类中的protected属性
  • private:类内可以访问,类外不可以访问

struct和class的区别:默认的访问权限不同

  • struct默认权限为公共
  • class默认权限为私有

对象的初始化和清理

构造函数和析构函数

  • 构造函数:在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用
  • 析构函数:在对象销毁前系统自动调用,执行一些清理工作。

编译器提供的构造函数和析构函数是空实现。

/*
	构造函数:
		类名(){} // 可以有参数,即可以重载
	析构函数:
		~类名(){} // 不可以有参数
*/
构造函数分类及调用

分类方式:

  • 按参数分 :有参构造和无参构造
  • 按类型分:普通构造和拷贝构造

调用方式:

  • 括号法
  • 显示法
  • 隐式转换法
class Person{
    public:
        Person(){
            cout << "无参构造" << endl;
        }
        Person(int a){
            cout << "有参构造" << endl;
        }
        Person(const Person &p){ // 拷贝构造函数
            cout << "拷贝构造函数" << endl;
            age = p.age;
        }
        ~Person(){
            cout << "析构函数" << endl;
        }
    private:
        int age;

};
int main(){
    // 调用 
    // 括号法
    Person p; // 默认构造
    // Person p(); 编译器会认为是函数的声明 void test();
    Person p1(10); // 有参构造
    Person p2(p); // 拷贝构造函数
    // 显示法
    Person p3 = Person(10); // 有参构造
    Person p4 = Person(p3); // 拷贝构造
    Person(10); // 匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
    /*
        注意:不要利用拷贝函数 初始化匿名对象 编译器会认为 Person(p3) === Person p3;
    */
    // 隐式转换法
    Person p5 = 10; // 等价于 Person p5 = Person(10);
    Person p6 =p5 ;// 调用拷贝构造
}
拷贝构造函数的调用时机
  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数传值
  • 以值方式返回局部对象
class Person{
     public:
        Person(){
            cout << "无参构造" << endl;
        }
        Person(int a){
            age = a;
            cout << "有参构造" << endl;
        }
        Person(const Person &p){ // 拷贝构造函数
            cout << "拷贝构造函数" << endl;
            age = p.age;
        }
        ~Person(){
            cout << "析构函数" << endl;
        }
    private:
        int age;
};

// 值传递的方式给函数传值
void doWork(Person p){

}
void test(){
    Person p;
    doWork(p);
}

// 值方式返回局部对象
Person doWork02(){
    Person p; // 值方式返回会拷贝一份p的副本返回
    cout << (long)&p << endl; // 6160232364
    return p;
}
void test02(){
    Person p = doWork02();
    cout << (long)&p << endl; //6160232364 
}
int main(){
    //test();
    test02();
}

按老师的讲解应该会调用拷贝函数,但是我的没有调用,两份地址也一样!

构造函数的调用规则

默认情况下,C++编译器至少给一个类添加3个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行拷贝

构造函数调用规则如下

  • 如果用户定义有参构造函数,C++不在提供默认构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
注意:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

初始化列表

作用:初始化属性
语法:构造函数():属性1(值1),属性2(值2)…{}

class Person{
    public:
        Person(int age):age(age){

        }

    private:
        int age;
};
对象成员属性

当其他类对象作为本类成员,构造时先构造类对象,再构造自身,析构的顺序与构造相反

静态成员属性

静态成员就是在成员变量和成员函数前加上static,称为静态成员。
静态成员分为:

  • 静态成员变量:
    1、所有对象共享同一份数据
    2、在编译阶段分配内存 // 全局区
    3、类内声明,类外初始化
  • 静态成员函数
    1、所有对象共享一个函数
    2、静态成员函数只能访问静态成员变量
//静态成员变量
class Person{
    public:
        /*
            1、所有对象都共享同一份数据
            2、编译阶段就分配内存
            3、类内声明,类外初始化操作
        */
       static int m_A;
};
int Person::m_A = 100; // 通过类名或对象访问
C++对象模型和this指针
成员变量和成员函数分开存储

在C++中,类中的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上,可以通过sizeof测试!
空对象占1个字节大小

class Person{}
this指针

this指针是一个指针常量
this指针指向被调用的成员函数所属的对象
this指针式隐含每一个非静态成员函数内的一种指针,不需要定义
this指针用途:

  • 当行参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可用return *this
class Person{
    public:
        int age;
        Person(int age){
            this->age = age;
        }
        Person addAge(int age){ // 返回一个新的对象
            this->age += age;
            return *this;
        }
        Person& addAgeA(int age){ // 返回同一个对象
            this->age += age;
            return *this;
        }
};

int main(){
    int age = 10;
    Person p(10);
    // p.addAge(age).addAge(age);
    // cout << p.age << endl; // 20
    p.addAgeA(age).addAgeA(age);
    cout << p.age << endl; // 30
}
空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针,如果用到this指针,需要加以判断保证代码的健壮性。

if(this == NULL){
	return;
}
const修饰成员函数

常函数:

  • 成员函数后加const后我们称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改
class Pserson{
    public:
        int age;
        mutable int a;
        // 在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
        void show() const{ 
            // this->age = 10; 报错
            this->a = 10;
        }
};

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
const Person p; // 常对象
p.a = 100; // 可以修改
友元

友元的关键为firend,目的是让一个函数或者类访问另一个类中的私有成员
友元的三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元
class Buliding{
	friend void test(Building *building);
	private:
		int age;
	
};
void test(Building *building){
	cout << building->age << endl;
}

运算符重载

定义:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

加号运算符重载
class Person{
    public:
        int a;
        int b;
        // 成员函数重载,也可以使用全局函数重载
        Person operator+ (Person &p){ 
            Person temp;
            temp.a = this->a + p.a;
            temp.b = this->b + p.b;
            return temp;
        }
};
int main(){
    Person p1;
    p1.a = 10;
    p1.b = 20;
    Person p2;
    p2.a = 10;
    p2.b = 20;
    Person p3 = p1 + p2;
    cout<< p3.a << p3.b<< endl;
}

注意:对于内置的数据类型的表达式的运算符是不能改变的

<<运算符重载
class Person{
    public:
        int a;
        int b;
        Person operator+ (Person &p){
            Person temp;
            temp.a = this->a + p.a;
            temp.b = this->b + p.b;
            return temp;
        }
};

ostream& operator<<(ostream &cout, Person &a){
    cout << "a=" << a.a << "b=" << a.b;
    return cout;
}
int main(){
    Person p1;
    p1.a = 10;
    p1.b = 20;
    Person p2;
    p2.a = 10;
    p2.b = 20;
    Person p3 = p1 + p2;
    cout<< p3.a << p3.b<< endl;
    cout << p3 << endl;
}
++运算符重载
class MyInteger{
    friend void test();
    private:
        int num;
    public:
        MyInteger& operator++(){
            num++;
            return *this; // 返回引用
        }
        MyInteger operator++(int ){ // int代表占位参数,用于区分前置和后置递增
            MyInteger temp = *this;
            num++;
            return temp; // 返回值 局部变量
        }
        
};
void test(){
    MyInteger number;
    number.num = 1;
    cout << ++number.num << endl;
     cout << number.num++ << endl;
}
int main(){
    test();
}

函数调用运算符重载
  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为防函数
  • 防函数没有固定写法,非常灵活

继承

语法:class 子类:继承方式 父类

class Father{};
class Son: public Father{};

继承方式

  • 公共继承
  • 保护继承
  • 私有继承
    在这里插入图片描述
    注意:父类中所有非静态成员属性都会被子类继承,父类中私有成员属性是被编译器隐藏了,因此是访问不到,但是确实被继承了!
class Base{};
class Son: public Base{};

在这里插入图片描述

继承同名成员处理方式
  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
  • 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名的成员函数,需要加作用域访问
class Base{
    public:
        Base(){
            m_A = 100;
        }
        int m_A;
        void show(){
            cout << "base show()" << endl;
        }
};
/*
如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名的成员函数
*/
class Son: public Base{
    public:
        Son(){
            m_A = 200;
        }
        void show(){
            cout << "son show()" << endl;
        }
        int m_A;
};
void test(){
    Son s;
    cout << s.m_A << endl; // 200
    cout << s.Base::m_A << endl; // 100
    s.show();
    s.Base::show();
}
int main(){
    test();
}
多继承语法

class 子类:继承方式 父类1, 继承方式 父类2…

多态

多态分为两类:

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态
    静态多态和动态多态区别:
  • 静态多态的函数地址早绑定-编译阶段确定函数地址
  • 动态多态的函数地址晚绑定-运行阶段确定函数地址

动态多态满足条件:

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

动态多态的使用:
父类的指针或引用指向子类对象
在这里插入图片描述
由于类中成员变量和成员函数分开存储,虚函数存储在类内部,存在一个vfptr(虚函数指针)指向虚函数表,虚函数表中存放类中的虚函数的地址。当

Animal & animal = cat;
//等价于
cat.speak();
纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
纯虚函数语法:
virtual 返回值类型 函数名(参数列表)= 0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类的特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构的区别:

  • 如果时纯虚析构,该类属于抽象类,无法实例化
    虚析构语法:
    virtual ~类名(){}
    纯虚析构语法:
    virtual ~类名() = 0;

虚析构实现

class Animal{
    public:
        virtual void speak() = 0;
        Animal(){
            cout << "animal 的构造函数" << endl;
        }
        virtual ~Animal(){
            cout << "animal 的析构函数" << endl;
        }
};

class Cat: public Animal{
     public: 
        Cat(string name){
            cout << "cat 的构造函数" << endl;
            this->name = new string(name);
        }
        virtual void speak(){
            cout << *name << "hello" << endl;
        }

        ~Cat(){
            if(name != NULL){
                cout << "cat的析构函数" << endl;
                delete name;
                name = NULL;
            }
        }
        string * name;
};
void test(){
    Animal * animal = new Cat("Tom");
    animal->speak();
    delete animal;
}
int main(){
    test();
}

纯虚析构实现:

class Animal{
    public:
        virtual void speak() = 0;
        Animal(){
            cout << "animal 的构造函数" << endl;
        }
        // virtual ~Animal(){
        //     cout << "animal 的析构函数" << endl;
        // }
        /*
		纯虚析构 需要申明也需要实现;
		有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
		*/
        virtual ~Animal() = 0;
};
Animal::~Animal(){
    cout << "animal 的纯虚析构函数" << endl;
}

class Cat: public Animal{
     public: 
        Cat(string name){
            cout << "cat 的构造函数" << endl;
            this->name = new string(name);
        }
        virtual void speak(){
            cout << *name << "hello" << endl;
        }

        ~Cat(){
            if(name != NULL){
                cout << "cat的析构函数" << endl;
                delete name;
                name = NULL;
            }
        }
        string * name;
};
void test(){
    Animal * animal = new Cat("Tom");
    animal->speak();
    delete animal;
}
int main(){
    test();
}

文件操作

C++中对文件操作需要包含头文件 < fstream>
文件类型分为两种:

  • 文本文件:文件以文本的ASCII码形式存储在计算机中
  • 二进制文件:文件以文本的二进制形式存储在计算机中

操作文件的三大类:

  • ofstream: 写操作
  • ifstream: 读操作
  • fstream: 读写操作

在这里插入图片描述
在这里插入图片描述
读文件
在这里插入图片描述

#include <iostream>
#include <fstream>
using namespace std;

void test(){
    ofstream ofs;
    ofs.open("test.txt", ios::out);
    ofs << "hello world"<<endl;
    ofs << "hello python"<<endl;
    ofs.close();
}
void read(){
    ifstream ifs;
    ifs.open("test.txt", ios::in);
    if(!ifs.is_open()){
        cout << "文件打开失败" << endl;
        return;
    }
    // 读数据 4种方式
    // 1
    // char buf[1024] = {0};
    // while(ifs >> buf){
    //     cout << buf << endl;
    // }

    // 2
    // char buf[1024];
    // while(ifs.getline(buf, sizeof(buf))){
    //     cout << buf << endl;
    // }

    // 3
    // string buf;
    // while(getline(ifs,buf)){
    //      cout << buf << endl;
    // }

    // 4 
    char c;
    while( (c = ifs.get()) != EOF){ // EOF end of file
        cout << c;
    }
    ifs.close();
}
int main(){
   read();
}

在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;
#include <fstream>

class Person{
    public:
        int age;
        string name;
        Person(int age, string name){
            this->age = age;
            this->name = name;
        }
};
void test(){
    ofstream ofs("person.txt", ios::out | ios::binary);
    Person p(19, "zhangsan");
    ofs.write((const char * )&p, sizeof(p));
    ofs.close();
}
int main(){
    test();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值