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;
}
原文地址:https://www.cnblogs.com/bytecodebuffer/p/13999189.html