• C++ 核心编程


    学习网址:http://c.biancheng.net/cplus/

    学习笔记:https://blog.csdn.net/ClaireSy/article/details/108423047

    1. 内存的分区模型
    2. new 操作符
    3. 引用
    4. 函数提高
    5. 类与对象
    6. 对象的初始化与清理
    7. 深拷贝与浅拷贝
    8. 初始化列表
    9. 静态成员函数
    10. C++ 对象模型与 this 指针
    11. 友元
    12. 基本数据类型大小
    13. 运算符重载
    14. 继承
    15. 多态
    16. 文件操作

    1.内存的分区模型

    • 代码区   存放函数的二进制代码,由操作系统进行管理
    • 全局区
    • 栈区
    • 堆区 

    分区的意义:不同的区域拥有不同的生命周期,有更大的灵活编程

     程序运行前

    代码区

      存放cup 执行的二进制机器指向

      代码区是共享的,共享的目的是对于频执行的程序,只需要在内存中存一份即可

      代码区是只读的,防止程序意外修改

    全局区

      全局变量和静态变量

      常量区(全局常量、const 修饰的全局变量)

    程序运行后

    栈区

      编译器自动的分配与释放,存放函数形参局部变量

      (注意:不要返回局部变量的地址,第一次可以打印正确的局部变量返回,第二次就丢失

    堆区

      由程序员分配释放,如果程序员不释放,程序结束后由操作系统回收

      new  可以在堆区开辟内存

    2.new 操作符

      操作: new  数据类型

      释放: delete  变量  ,释放数组 delete[]  数组名

    3.引用

     语法: 数据类型  &别名 = 原名

        int a = 10;
        int &b = a;
        b = 20;
        cout << a << endl;

    引用必须初始化,不可更改

    使用引用作为方法参数,修改实参

    void swap_3(int &a, int &b) {
        int temp = a;
        a = b;
        b = temp;
    }

    不要返回局部变量的引用(第一次输出正常,第二次结果是错误的,编译器只保留一次)

    静态变量存储在全局区,全局区上的数据在程序结束后释放

    函数的调用可以作为左值

    #include<iostream>
    #include<string.h>
    using namespace std;
    
    int& test() {
        static int a = 10;
        return a;
    }
    
    int main() {
    
        int& ref = test();
    
        cout << "ref=" << ref << endl;
        cout << "ref=" << ref << endl;
    
        test() = 100;
    
        cout << "ref=" << ref << endl;
        cout << "ref=" << ref << endl;
        system("pause");
        return 0;
    }

    引用的本质:引用的本质在c++内部是一个指针常量

        int a = 10;
    
        // 等价于 int * const ref = &a;
        int& ref = a;
        
        //*ref = 20;
        ref = 20;
    
        cout << "a=" << a << endl;
        cout << "ref=" << ref << endl;

    常量引用:用来修饰形参,防止误操作(在方法形参中使用const 修饰引用,则引用无法被修改

        int a = 10;
    
        //等价于 int temp = 10;  const int& ref = temp;
        const int& ref = 10;

    4.函数提高

    默认参数

    函数的默认参数,如果有参数则使用传递的参数,没有则使用默认参数

    如果多个参数第一个出现默认参数,则之后的参数都需要有默认参数

    void add(int a= 100,int b= 200,int c= 300) {
        int sum = a + b + c;
        cout << sum << endl;
    }
    
    int main() {
    
        int a = 10;
        int b = 20;
        int c = 30;
        add(10);
    
        system("pause");
        return 0;
    }

    注意:函数的声明出现默认参数,则实现无法使用默认参数

    占位参数

    函数的形参列表可以有占位参数,用来做占位,调用函数时必须填补改参数

    void add(int a,int) {
        int sum = a;
        cout << sum << endl;
    }

    占位参数可以有默认参数,则不必传递该参数

    void add(int a,int = 20) {
    	int sum = a;
    	cout << sum << endl;
    }

    函数重载

    函数名相同,提高复用性

    • 同一个作用域
    • 函数名相同
    • 函数的参数类型不同、个数不同、顺序不同

    注意

    引用可以作为重载的条件(传递变量与常量不同)

    void test(int &a) {
        cout << "a" << endl;
    }
    
    void test(const int& a) {
        cout << "const a" << endl;
    }

    函数重载碰到默认参数(传递1个参数出现二义性,传递2个参数正常)

    void test(int a,int=10) {
        cout << "int a int" << endl;
    }
    
    void test(int a) {
        cout << "int a" << endl;
    }

    5.类与对象

    C++ 面向对象三大特性:封装、继承、多态

    访问权限 

     public  protected  private

    共有权限:类内可以访问,类外可以访问

    保护权限:类内可以访问,类外不可访问,子类可以访问父类的保护内容

    私有权限:类内可以访问,类外不可访问,子类不可访问

    struct 与 class 的区别

    struct 默认的权限是共有, class 默认的权限是私有

    struct C1
    {
        int age;
        void fun() {
            cout << "struct" << endl;
        }
    };
    
    class C2 {
        int age;
        void fun() {
            cout << "struct" << endl;
        }
    };
    
    int main() {
        C1 c;
        c.fun();
    
        C2 c2;
        //默认私有权限
        return 0;
    }

    6.对象的初始化与清理

    构造函数与析构函数,这两个函数会被编译器自动调用,完成初始化与清理工作。

    构造函数

    类型 (){}

    • 构造函数无返回值
    • 函数名与类目相同
    • 可以有参数,可以重载
    • 创建对象,会自动调用构造

    析构函数

    ~类名(){}

    • 不能有参数,不能发生重载
    • 析构函数在对象被销毁前调用
    class Person {
    public:
        Person() {
            cout << "构造函数" << endl;
        }
    
        ~Person(){
            cout << "析构函数" << endl;
        }
    };
    
    int main() {
        Person p;
        return 0;
    }

    注意:构造函数可以接收对象,实现拷贝构造

    创建对象的三种方式

        //1.隐式调用
        //Person p;
        //该调用被识别为函数声明
        //Person p();
    
        //2.显式调用
        //Person p = Person();
        //以下是创建匿名对象,执行完即释放
        //Person();
    
        //3.隐式转换法 等价与 Person p = Person(10);
        Person p = 10;

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

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

    规则如下

    如果用户定义了有参构造,则c++ 不提供无参构造,但是会提供拷贝构造

    如果用户定义了拷贝构造函数,c++ 不提供其他构造函数

    7.深拷贝与浅拷贝

    编译器提供的拷贝都是浅拷贝 ,会导致内存重复释放

    注意:如果属性是堆区开辟的内存,那么一定要实现深拷贝

    class Person {
    public:
        int age;
        int* hight;
    
        Person() {
            cout << "构造函数" << endl;
        }
    
        Person(int n) {
            cout << "构造函数" << endl;
        }
    
        Person(const Person &p) {
            age = p.age;
            hight = new int(*p.hight);
        }
    
        ~Person(){
            if (hight != NULL) {
                delete hight;
                hight = NULL;
            }
            cout << "析构函数" << endl;
        }
    };

    如果对象属性包含在堆创建的属性,那么析构进行回收,如果未实现深拷贝则会出现问题

    8.初始化列表

    class Person {
    public:
        int m_a;
        int m_b;
        int m_c;
    
        Person(int a,int b,int c):m_a(a),m_b(b),m_c(c){
    
        }
        Person(const Person& p) {
            m_a = p.m_a;
            m_b = p.m_b;
            m_c = p.m_c;
        }
    
        ~Person() {
        
        }
    
    };
    
    int main() {
        Person p(10, 20, 30);
        return 0;
    }

    初始化列表可以在构造函数外实现参数的初始化

    类对象作为成员: 当 A 类的对象作为 B 类的成员时,创建B的时候首先创建 A ,析构的顺序相反(注意:如果传参使用参数列表,则A 创建一个对象,如果用赋值,则创建了2个对象 ,包括一个无参)

    9.静态成员函数

    静态成员变量

    • 所有对象共享一份数据
    • 在编译阶段分配内存
    • 类内声明,类外访问

    静态成员函数

    • 所有对象共享一个函数
    • 静态成员函数只能访问静态成员变量
    class Person {
    public:
        string age;
        static int high;
    
        static void print() {
            high = 200;
            //cout << age << endl;
            cout << high << endl;
        }
    };
    //静态成员变量类外初始化
    int Person::high = 10;
    
    int main() {
        //方式一、对象访问静态成员函数
        Person p;
        p.print();
    
        //方式二、通过类名访问
        Person::print();
        return 0;
    }

     10.c++ 对象模型与 this 指针

    成员变量与成员函数分开存储

    空对象占用内存空间为 1 

    如果非空对象,则对象的大小与成员属性(非静态)相关

    class Person {
    public:
        int age;
        static string name;
        void say() {}
    };
    
    int main() {
        Person p;
        cout << sizeof(p) << endl;
        return 0;
    }

    如果是空对象则为1 ,否则为普通成员函数的内存大小

    注意:单独的double 占用8个字节,与其他字节组合,则占用8的倍数个字节

    this 指针概念

    •  指向被调用成员函数所属的对象
    •  空指针可以访问成员函数,如果成员函数用到this ,才会报错

    const 修饰的成员函数

    •   成员函数加 const 后称为常含数
    • 常含数不可修改属性
    • 成员属性声明时加 mutable 之后,在常含数中依然可以修改

    常对象

    • 声明对象前加const 称为常对象
    • 常对象只能调用常含数

    this 指针本质是指针常量: Person * const this;  指针不可修改,值可修改。 常含数加const 本质是: const Person * const this ,指针与值都不可修改

    成员变量加特殊后可以修改 : mutable 

    class Person {
    public:
        int m_A;
        mutable int m_B;
    
        void say() const{
            //m_A = 10; 不可修改
            m_B = 20;
        }
    };
    
    int main() {
        const Person p;
        //p.m_A = 20; 不可修改
        p.m_B = 20;
        p.say();
        return 0;
    }

    11.友元

    友元:目的是让全局函数、类、成员函数可以访问当前类的私有属性,关键词:friend

    全局函数作有缘

    类作友元

    成员函数作友元

    12.基本数据类型的大小

    基本数据类型的大小(window系统

    32位系统

     64位系统

    注意:Linux 系统下 long 类型是 8 字节

    VC++ 关于 sizeof(string) 为何28(x86)位 与 40 (x64) 个字节

    名称 X86 (字节数) X64(字节数)
    Allocator 4 8
    原始字符传 Data 位置 15+1  最多包含15个字符加一个结束符‘’ 15+1  最多包含15个字符加一个结束符‘’
    字符长度 Size 4 8
    当前容量 Capacity 4 8
    总计 28 40
    #include<iostream>
    using namespace std;
    
    /*
    1.空结构体/类的对象大小位 1B
    2.类有成员属性(单独)
        x64                        x86
        char  1b                  1b
        short 2b                  2b
        int  4b                    4b
        float 4b                  4b
        double 8b                 8b
        long long 8b              8b
        int * 8b                  4b
        string 40b                 28b
    3.组合类型
        x64                            x86
        char + char = 2                2
        char + short = 4              4
        char + int  = 8                8
        char + float = 8              8
        char + int * = 16             8
        char + string = 48            32
    
        int + string =    48           32
        int + double =    16           16
    
        string + string = 80        56
        string + double = 48        40
    
        float + int * = 16            8
    */
    
    class Person {
        float  age;
        int * name;
    };
    
    int main() {
        Person p;
        cout << sizeof(p) << endl;
        return 0;
    }

     13.运算符重载

    运算符重载:可以对对象之间的运算符操作

    + 号运算符重载,通用函数 operator

    #include<iostream>
    using namespace std;
    
    class Person {
    public:
        int m_A;
        int m_B;
    
        //成员函数重载
        Person operator+(Person &p) {
            Person temp;
            temp.m_A = this->m_A + p.m_A;
            temp.m_B = this->m_B + p.m_B;
            return temp;
        }
        
    };
    
    //全局函数重载
    Person& operator-(Person &p1,Person &p2 ) {
        Person temp;
        temp.m_A = p1.m_A - p2.m_A;
        temp.m_B = p1.m_B - p2.m_B;
        return temp;
    }
    
    int main() {
        Person p1;
        p1.m_A = 10;
        p1.m_B = 10;
        Person p2;
        p2.m_A = 20;
        p2.m_B = 20;
        Person p3 = p1 - p2;
        cout << p3.m_A << endl;
        cout << p3.m_B << endl;
        return 0;
    }

     << 重载,输出类

    #include<iostream>
    #include<string.h>
    using namespace std;
    
    class Person {
    public:
        int m_A;
        int m_B;
    };
    
    ostream& operator<<(ostream &cout,Person &p) {
        cout << "m_A=" << p.m_A << ",m_B=" << p.m_B;
        return cout;
    }
    
    int main() {
        Person p1;
        p1.m_A = 10;
        p1.m_B = 10;
        cout << p1<<endl;
        return 0;
    }

    递减运算符重载

    #include<iostream>
    #include<string.h>
    using namespace std;
    
    //实现自定义Int 类型,并实现 -- 重载
    class MyInt {
        friend ostream& operator<<(ostream& cout, MyInt m);
    private:
        int number;
    public:
        MyInt(){
            number = 0;
        }
    
        MyInt& operator--() {
            number--;
            return *this;
        }
    
        MyInt operator--(int) {
            MyInt temp = *this;
            number--;
            return temp;
        }
    };
    
    ostream& operator<<(ostream& cout, MyInt m) {
        cout << m.number;
        return cout;
    }
    
    int main() {
        MyInt m;
        //cout << --(--m) <<endl ;
        cout << (m--)-- << endl;
        cout << m << endl;
        return 0;
    }

    注意:

    前置递减和后置的重载通过占位符

    前置递返回的是引用,可以连续操作,后置递返回临时值,不能连续的操作

    赋值运算符重载

    编译器提供的赋值运算符实现的是浅拷贝,需要实现赋值运算符深拷贝运算符重载。

    关系运算符重载

    可以实现对象之间的等于与不等于比较

    函数调用运算符重载

    对象内部实现() 重载,可以通过对象调用。称为仿函数

    匿名函数对象,创建匿名对象调用了函数运算符重载

    14.继承

    语法

    class 类 : public 父类{

    }

    子类也叫做派生类,父类也叫做基本

    继承方式

    • 公共继承 与父类一致
    • 保护继承 父类共有变为保护
    • 私有继承  父类公共与保护变为私有

    继承中的对象模型

    父类中所有的属性都会被继承,私有成员属性是被隐藏的,无法访问

    //利用开发人员命令工具查看对象模型

    cl /d1  reportSingleClassLayout类名 文件名

     C++ 允许多继承

    多继承通过逗号分割

    不建议使用多继承,多继承可能会出现同名的变量、函数,需要加作用域

    菱形继承/钻石继承

    问题:当多个父类中包含重复的属性时,多继承浪费资源且毫无意义

    解决:虚继承 virtual ()

    父类继承公共类的时候使用虚继承,(vbptr  虚基类指针)

    vbptr 指向了 vbtable (虚基类表), 通过偏移量指向相同的数据

    15.多态

    静态多态: 函数重载,运算符重载属于静态多态

    动态多态: 派生类与虚函数实现运行时多态

    正常情况下,父类指向子类,指向的是父类的函数,因为是地址早绑定。要使用传入对象的方法,需要将父类的方法标记为 virtual ,即地址晚绑定 

    多态注意:子类要重写父类的虚函数(子类virtual 可以忽略)

    原理:

    父类指定virtual 函数后,内部多一个 vfpter 指针,指向虚函数表, vftable (该表存储虚函数地址)。

    当子类重写虚函数后,继承 vfptr ,同时虚函数表存储的函数地址修改为子类的虚函数地址

    纯虚函数 

    virtual 返回值类型 函数名(参数列表) = 0; 

    当类中有纯虚函数,该类为抽象类,特点是无法实例化,子类必须重写纯虚函数

    虚析构与纯虚析构

    解决问题:当子类对象中内存开辟到堆区,多态下父类指针无法释放子类对象,会造成内存泄漏

    将子类的析构函数用 virtual 修饰,改为虚析构,那么释放父类指针也会调用子类的析构

    另一种解决办法:使用纯虚析构 ,将父类的析构改为纯虚析构,需要类外实现

    #include<iostream>
    using std::cout;
    using std::endl;
    using std::cin;
    
    class Base {
    public:
        Base() {
            cout << "base 构造" << endl;
        }
    
        virtual void speak() {
            cout<<"Base speak"<<endl;
        }
    
        virtual ~Base() {
            cout << "base 析构" << endl;
        }
    };
    
    class Son : public Base {
    public:
        Son() {
            cout << "Son 构造" << endl;
        }
    
        void speak() {
            cout << "Son speak" << endl;
        }
    
         ~Son() {
            cout << "Son 析构" << endl;
        }
    };
    
    int main() {
        Base *base  = new Son;
        base->speak();
        delete base;
        return 0;
    }

    16.文件操作

    C++ 中文件操作需要包含头文件 <fstream>

    文件类型分为两中

    • 文本文件 以ASCII 码形式存储
    • 二进制文件 文件以文本的二进制形式存储,一般无法识别

    操作文件的三大类

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

    打开方式

    • ios::in 为读文件而打开文件
    • ios::out 为写文件而打开文件
    • ios::ate 初始位置:文件尾
    • ios::app 追加方式写文件
    • ios::trunc 如果文件存在先删除,再创建
    • ios::binary 二进制方式

    写文件案例

    #include<iostream>
    #include<fstream>
    using namespace std;
    
    int main() {
    
        ofstream ofs;
        ofs.open("text.txt",ios::out);
        ofs << "Hello,file ";
        ofs.flush();
        ofs.close();
        return 0;
    }

    读文件 ifstrean

    #include<iostream>
    #include<fstream>
    #include<string>
    using namespace std;
    
    int main() {
    
        ifstream ifs;
        ifs.open("text.txt",ios::in);
        if (!ifs.is_open()) {
            cout << "文件打开失败" << endl;
        }
        //第一种读取
        /*
        char buff[1024] = {0};
        while (ifs >> buff) {
            cout << buff << endl;
        }
        */
    
        //第二种方式
        /*
        char buff[1024] = {0};
        while (ifs.getline(buff, sizeof(buff))) {
            cout << buff << endl;
        }
        */
        
        //第三种
        /*
        string buff;
        while (getline(ifs, buff)) {
            cout << buff << endl;
        }
        */
    
        //第四种 ,EOF 代表文件尾部
        char ch;
        while ((ch = ifs.get())!=EOF) {
            cout << ch;
        }
    
        ifs.close();
        return 0;
    }
  • 相关阅读:
    android:background背景图片被拉伸问题
    面试积累(String和StringBuffer, StringBuilder的理解)
    面试积累(冒泡排序和选择排序)
    面试积累(java的内存分析)
    面试积累(java配置环境变量)
    异常积累(SQLException)
    【linux】fdisk磁盘分区
    【走马观花】十一月十八日通州雨
    【linux】CentOS查看硬件信息
    【linux】go安装及配置
  • 原文地址:https://www.cnblogs.com/baizhuang/p/13999189.html
Copyright © 2020-2023  润新知