目录
二、核心编程
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();
}
由于父类中的虚函数往往没有实际作用,往往将其写为纯虚函数。
只要有一个纯虚函数,这个类就被称为抽象类。
抽象类无法实例化对象,抽象类的子类必须要重写父类的纯虚函数,否则子类也无法实例化。
在子类中如果有堆区数据,为了避免内存泄漏(释放不干净),需要通过虚析构函数或者纯虚析构函数释放子类对象。