C++黑马程序员教程-学习笔记二

目录

二、核心编程

2.1 程序运行前

2.1.1 代码区

2.1.2 全局区

2.2 程序运行后

2.2.1 栈区

2.2.2 堆区

2.3 引用

2.4 函数高级

2.4.1 函数默认参数

2.4.2 函数的占位参数

2.4.3 函数重载

2.5 类和对象

2.5.1 类和定义和对象的构建

2.5.2 访问权限

2.5.3 封装

2.5.4 对象特性

2.5.4.1 构造函数

2.5.4.2 析构函数

2.5.4.3 拷贝函数

2.5.4.4 静态成员

2.5.4.5 对象存储空间

2.5.5 友元 friend

2.5.5.1 全局函数做友元

2.5.5.2 类做友元

2.5.5.3 成员函数做友元

2.5.6 运算符重载

2.5.7 继承

2.5.8 多态


二、核心编程

C++在执行时,将内存划分为四个区域

代码区:存放函数体二进制代码,由操作系统管理

全局区:存放全局变量和静态变量以及常量

栈区:由编译器自动分配释放,存放函数的参数值、局部变量

堆区:由程序员分配和释放,若程序员不释放则操作系统回收

意义:不同区域存放的数据,赋予不同的生命周期,更大的灵活编程

2.1 程序运行前

C++中在程序运行前分为代码区和全局区。

2.1.1 代码区

代码区存放CPU执行的机器指令。
代码区是共享的,对于频繁被执行的程序,只需要在内存中有一份代码即可。
代码区是只读的,防止程序意外的修改了他的指令。

2.1.2 全局区

全局区中存放全局变量(定义在主函数外的变量)、静态变量(static)、常量(字符串常量、const全局常量)。

全局区在程序结束后由操作系统释放

2.2 程序运行后

2.2.1 栈区

由编译器自动分配释放,存放函数的参数值(形参)、局部变量、局部常量(const)等。

注意:不要返回局部变量的地址(栈区数据在函数执行完后自动释放),栈区的数据由编译器开辟和释放

2.2.2 堆区

堆区由程序员分配释放;若程序员不释放,程序结束时由操作系统回收。

int* return_add()
{
    //利用new关键字,可以将数据开辟到堆区,new返回的是该数据类型的指针
    int* p = new int(10);    //初始化为整型数据10

    int* arr = new int[10];    //初始化10个元素的数组
    for (int i = 0; i < 10; i++)
        arr[i] = i;

    return p;
}

void main()
{
    int* p = return_add();
    delete p;         //使用delete手动释放内存
    delete[] arr;     //释放数组要加[]
}

2.3 引用

相当于起一个别名 

int a = 10;
int &b = a; //a、b指向同一段内存,同时修改

注意:引用必须要初始化,引用一旦初始化后就不可以更改。

形参采用引用传递,会修改实参的值:

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

void main()
{
    int a = 10;
    int b = 20;
    sawap(a, b);    //执行该函数后a=20,b=10
}

引用的本质是指针常量:

int &ref = a;
//本质上是int* const ref = &a;

2.4 函数高级

2.4.1 函数默认参数

int func (int a, int b = 10, int c = 20) //默认参数写在形参
{
    return a + b+ c;
}

void main()
{
    func(10);     //有默认参数可以不传实参
}

注意:1.如果某个形参有了默认参数,后续其它形参也都必须有默认值;2.函数声明有默认参数,函数实现就不能有默认参数。

2.4.2 函数的占位参数

int add(int a, int)
{
    return a+b;
}

void main()
{
    add(10, 10);
}

2.4.3 函数重载

函数重载的满足条件:1、同一作用域;2、函数名相同;3、函数参数类型不同、或个数不同、或顺序不同。

注意:返回值不能作为函数重载的条件;重载函数不要写默认参数,容易出现二义性。

2.5 类和对象

2.5.1 类和定义和对象的构建

class Student{
    //类中的属性和行为,统一称为成员
    //属性,又称为成员属性、成员变量
    public:    //共用权限
        string name;
        int id;
    
    //行为,又称为成员函数、成员方法
    void set_student(string nn, int ii;)
    {
        name = nn;
        id = ii;
    }
};

int main()
{
    Student ss;
    ss.set_student("张三", 1);
}

2.5.2 访问权限

三种访问权限:

1.公共权限 public                 成员 类内可以访问,类外可以访问

2.保护权限 protected           成员 类内可以访问,类外不可以访问,儿子可以访问父亲的保护内容

3.私有权限 private               成员 类内可以访问,类外不可以访问,儿子不可访问父亲的保护内容

class与struct的区别:class默认权限为private,struct默认权限为public。

2.5.3 封装

 成员属性私有化的好处:1、可以自己控制读写权限;2、对于写权限,可以检测数据有效性。

类和对象的分文件编写:

1.新建类的h头文件和cpp源文件;

2.在头文件中定义类,并将类中的行为(方法)仅保留声明部分;声明全局变量不赋值,加extern;

3.在源文件中添加对应头文件,补充声明函数的具体内容,并在函数前添加类名::表示其作用域。

2.5.4 对象特性

构造函数对对象进行初始化,析构函数进行清理,都会自动调用。

2.5.4.1 构造函数

1.没有返回值,不用写void

2.函数名与函数相同

3.构造函数可以有参数,可以发生重载

4.创建对象的时候,构造函数会自动调用,而且只调用一次

2.5.4.2 析构函数

1.没有返回值,不写void

2.函数名和类名相同,在名称前加~

3.析构函数不可以有参数,不可以重载

4.对象在销毁前,会自动调用析构函数,而且只会调用一次

2.5.4.3 拷贝函数

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间,进行拷贝操作

class Person()
{
    Person()
    {
        cout << "构造函数" << endl;
    }

    ~Person()
    {
        cout << "析构函数" << endl;
    }

    Person(const Person &p):        //拷贝函数
    {
        age = p.age;
    }
    
    int age;
};
2.5.4.4 静态成员

类内声明,类外需要初始化

1.静态成员变量

所有对象都共享同一块内存;

注意:static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配

可以有public和private不同作用域。

class Person
{
public:
    static int age;
};

int Person::age = 20;

2.静态成员函数

有public和private不同作用域。

静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数);普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数。

class Person
{
public:
    static void func()
    {
        cout << "static func." << endl;
    }
};

//两种方式调用静态成员函数
//1.通过对象
Person p1;
p1.func();

//2.通过类名
Person::func()
2.5.4.5 对象存储空间

只有非静态成员变量会占对象的存储空间,如果对象为空则占一个字节。

2.5.5 友元 friend

友元就是允许一个函数或者类访问另一个类中的私有成员。

2.5.5.1 全局函数做友元
class Person
{

    Person(int age0) //构造函数 初始化
    {
        age = age0;
    }

    friend ask_age(Person *p); //全局函数声明直接复制过来,前面加friend声明友元

private:
    int age;
};

int ask_age(Person *p)
{
    return p->age;
}

int main()
{
    Person pp(20);
    cout << "age: " << ask_age(& pp) << endl;
}
2.5.5.2 类做友元

类内可以访问另一个类的私有成员。

class A
{
private:
    int a;
};

class B
{
    friend class A;

public:
    void visit(A *x)
    {
        cout << "x.a: " << x->a << endl;
    }
};
2.5.5.3 成员函数做友元

类内的成员函数访问另一个类的私有成员。

class A
{

friend void B::visit(A *x);

private:
    int a;
};

class B
{

public:
    void visit(A *x)
    {
        cout << "x.a: " << x->a << endl;
    }
};

2.5.6 运算符重载

operator+

2.5.7 继承

class Base //父类或者基类
{};

class Special: public Base //子类或者派生类
{};

class Special: public Base1,  public Base2 //可以继承多个类,但不建议使用
{};

在构造和析构过程中,父类先构造,子类然后构造,析构的顺序则相反。

对于菱形继承可能出现的多个同名属性出现的不明确继承情况,在基类集成时可以使用虚继承。

class Base //父类或者基类
{
public:
    int a;
};

class subBase1: virtual public Base //虚继承用virtual
{};

class subBase2: virtual public Base //虚继承用virtual
{};

class grandson: public subBase1, public subBase2 //菱形继承
{};

2.5.8 多态

2.5.8.1 静态多态与动态多态、虚函数

静态多态包括函数重载和运算符重载,复用函数名称,在编译阶段就确定函数地址;

动态多态使用派生类和虚函数实现运行时多态,在运行阶段确定函数地址

举例如下:

class Animal
{
public:
    void speak(){
        cout << "Animal is speaking!"  << endl;
    }
};

class Cat: public Animal
{
public:
    void speak(){
        cout << "Cat is speaking!"  << endl;
    }
}

void doSpeak(Animal &animal)
{
    animal.speak();
}

void main()
{
    Cat cat;
    doSpeak(cat);   //输出“Animal is speaking!”
}

这是因为函数重载的地址早绑定,如果想要输出"Cat is speaking!"就需要通过虚函数来实现:

class Animal
{
public:
    virtual void speak(){
        cout << "Animal is speaking!"  << endl;
    }
};

总结:动态多态需要满足条件:1.有继承关系;2.子类要重写(函数名/参数/返回值完全相同)父类的虚函数。

实现方法:父类的指针或引用 子类对象(父类  对象名= new 子类();语句在堆内存中开辟了子类的对象,并把栈内存中的父类的引用指向了这个子类对象)。

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

class calculator
{
public:
    int num1;
    int num2;

    virtual int get_result()
    {
        return 0;
    }
};

class AddCal: public calculator
{
public:
    int get_result()
    {
        return num1 + num2;
    }
};

class MulCal: public calculator
{
public:
    int get_result()
    {
        return num1 * num2;
    }
};

void test()
{
    calculator *c = new MulCal;
    c->num1 = 10;
    c->num2 = 10;
    cout << c->get_result() << endl;
    delete c;
}

int main()
{
    test();
}

由于父类中的虚函数往往没有实际作用,往往将其写为纯虚函数。

只要有一个纯虚函数,这个类就被称为抽象类。

抽象类无法实例化对象,抽象类的子类必须要重写父类的纯虚函数,否则子类也无法实例化。

在子类中如果有堆区数据,为了避免内存泄漏(释放不干净),需要通过虚析构函数或者纯虚析构函数释放子类对象。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值