黑马程序员课程学习笔记
内存分区
分四区:代码区、全局区、栈区、堆区
代码区
程序运行前就存在,存放二进制文件,共享的,只读
全局区
- 程序运行前就存在,存放全局变量,静态变量和常量(其中常量包括字符串常量、全局常量)
- 程序运行结束后,由操作系统释放
- 注意,局部变量加const修饰为局部常量,局部常量和局部变量都不在全局区
- 此外,string str=“abc”;str和"abc"地址不一样,str是局部变量,"abc"是字符串常量
#include <iostream>
using namespace std;
int g_c = 30;
int g_d = 40;
const int c_g_a = 40;
const int c_g_b = 40;
int main() {
int a = 10;
int b = 10;
cout << "局部变量a地址:" << (int)&a << endl;
cout << "局部变量b地址:" << (int)&b << endl << endl;
cout << "全局变量g_c地址" << (int)&g_c << endl;
cout << "全局变量g_d地址" << (int)&g_d << endl<<endl;
static int s_e = 10;
static int s_f = 10;
cout << "静态全局变量s_e地址" << (int)&s_e << endl ;
cout << "静态全局变量s_f地址" << (int)&s_f << endl << endl;
string str = "hello world" ;
cout << "字符串常量地址" << (int)&"hello world" << endl;
cout << "局部变量str地址" << (int)&str << endl << endl;
const int c_l_a = 1;
const int c_l_b = 1;
cout << "全局常量c_g_a地址" << (int)&c_g_a << endl;
cout << "全局常量c_g_b地址" << (int)&c_g_b << endl;
cout << "局部常量c_l_a地址" << (int)&c_l_a << endl;
cout << "局部常量c_l_b地址" << (int)&c_l_b << endl;
return 0;
}
从上述地址可以看到,圈出的三个地址很近,离其他的很远,这三个都不在全局区
栈区
- 存放局部变量和形参
- 栈区的数据由编译器管理开辟和释放,先进后出
- 不要返回局部变量的地址
#include <iostream>
using namespace std;
int* fun(int b) {
b = 100;
int a = 10;
return &a;
}
int main() {
int* p = fun(1);
cout << *p << endl;
cout << *p << endl;
return 0;
}
堆区 new
- 由程序员分配和释放,若程序员未释放,程序结束时,由系统回收
- 利用new在堆区开辟内存
- 利用delete释放堆区数据
- int * arr=new int[10];在堆区开辟一个长度为10的数组,new int(10),是开辟一个整型数据
- 开辟的数据用什么接收?new返回的是该数据类型的指针,所以要用指针来接收
- 利用delete[] 数组名释放堆区数组
#include <iostream>
using namespace std;
int*fun(){
int* a = new int(123);
return a;
}
void fun2() {
int *arr = new int[10];
for (int i = 0; i < 10; i++){
arr[i] = rand() % 50;
}
for (int i = 0; i <10; i++){
cout << arr[i] << " ";
}
delete[] arr;
}
int main() {
int* p = fun();
cout << "*p=" << *p << endl;
cout << "*p=" << *p << endl;
delete p;
fun2();
return 0;
}
引用
基本用法
- 作用:给变量起别名
- 定义:数据类型 &别名 = 原名;
- 注意:引用必须初始化,初始化之后不可再修改
#include <iostream>
using namespace std;
int main() {
int a = 10;
int c= 30;
cout << "a=" << a << endl;
int& b = a;
cout << "b=" << b << endl;
cout << "a=" << a << endl;
b = 20;
b=c;
cout << "b=" << b << endl;
cout << "a=" << a << endl;
return 0;
}
引用作为函数参数
- 三种函数传参方式:值传递,地址传递,引用
- 地址传递和引用均可以修饰实参,但是引用可能操作更简单一点
#include <iostream>
using namespace std;
void test01(int a, int b);
void test02(int* a, int* b);
void test03(int& a, int& b);
int main() {
int a = 10, b = 30;
cout << "交换前:" << endl;
cout << "a=" << a << ",b=" << b << endl << endl;;
test01(a, b);
cout << "a=" << a << ",b=" << b << endl;
a = 10;
b = 30;
test02(&a, &b);
cout << "a=" << a << ",b=" << b << endl;
a = 10;
b = 30;
test03(a, b);
cout << "a=" << a << ",b=" << b << endl;
return 0;
}
void test01(int a, int b) {
cout << "值传递:" << endl;
int temp = a;
a = b;
b = temp;
}
void test02(int* a, int* b) {
cout << "\n地址传递:" << endl;
int temp = *a;
*a = *b;
*b = temp;
}
void test03(int& aa, int& bb) {
cout << "\n引用:" << endl;
int temp = aa;
aa = bb;
bb = temp;
}
引用作为函数返回值
1.不要返回局部变量的引用 2.引用的函数调用可以作为左值
#include <iostream>
using namespace std;
int& test01();
int& test02();
int main() {
int r1 = test01();
cout << "r1=" << r1 << endl;
cout << "r1=" << r1 << endl;
int & r2 = test01();
cout << "r2=" << r2 << endl;
cout << "r2=" << r2 << endl;
int& r3 = test02();
cout << "r3=" << r3 << endl;
cout << "r3=" << r3 << endl;
cout << "r3=" << r3 << endl;
test02() = 100;
cout << "r3=" << r3 << endl;
cout << "r3=" << r3 << endl;
return 0;
}
int& test01() {
int a = 10;
return a;
}
int& test02() {
static int b = 20;
return b;
}
引用的本质
- 指针常量:*+const,指针的指向不可以修改
- 引用的本质是指针常量,故初始化之后就不能修改指向,但是值可以改。
- int &a=b;等价于int * const a = &b;
常量引用
在函数传参时,地址传递和引用的形参都能修饰实参,如果不想实参别修改,可以价格const,前面的指针常量就有这个用处,这里用const修饰引用也是一样的。
void test(const int&a){}
此外
const int& a = 10;
类和对象
对象那个的三大特性:封装、继承、多态
封装
将属性和行为写到一起,来表现事物 语法:class 类名{ 访问权限 属性 \ 行为};
类的案例1:一个圆类
设计一个圆类,求圆的周长和面积
#include <iostream>
using namespace std;
const double PI = 3.1415926;
class Circle{
public:
double m_r;
double calculateC() {
return 2 * PI * m_r;
}
double calculateS() {
return PI * m_r* m_r;
}
};
int main() {
Circle yuan;
cout << "请输入圆的半径" << endl;
cin >> yuan.m_r;
cout << "半径:" << yuan.m_r
<< ",周长:" << yuan.calculateC()
<< ",面积:" << yuan.calculateS()<< endl;
return 0;
}
类的案例2:学生类
设计一个学生类,包含姓名和学号,且能通过成员函数赋值
#include <iostream>
#include <string>
using namespace std;
class Student{
public:
string m_Name;
int m_ID;
void setValue() {
cout << "请输入学生姓名和学号" << endl;
cin >> m_Name>>m_ID;
}
void showValue() {
cout << "姓名:" << m_Name<<"\t学号:"<<m_ID<<endl;
}
void modifyValue(string name, int id) {
m_Name = name;
m_ID = id;
}
};
int main() {
Student s1;
s1.setValue();
s1.showValue();
s1.modifyValue("李四",2020225030);
s1.showValue();
return 0;
}
访问权限
访问权限共有三种:公有、保护、私有
类型 | 成员的访问权限 | 子类 |
---|
公有 | 类内可以访问,类外也能访问 | | 保护 | 类内可以访问,类外不可以访问 | 子类可以访问 | 私有 | 类内可以访问,类外不可以访问 | 子类不可以访问 |
#include <iostream>
using namespace std;
class Person {
public:
string m_Name;
protected:
string m_Car;
private:
int m_Password;
public:
void func() {
m_Name = "张三";
m_Car = "拖拉机";
m_Password = 123123;
}
};
int main() {
Person p1;
p1.m_Name = "李四";
p1.func();
return 0;
}
class 与strut区别
class默认访问权限是私有的,而class是公有的
#include<iostream>
using namespace std;
class C1 {
int m_A;
};
struct S1{
int m_B;
};
int main() {
C1 c1;
S1 s1;
s1.m_B = 1;
return 0;
}
成员属性设为私有
成员属性设为私有,类外通过公有成员函数对私有成员变量进行读写操作,优势如下:
- 可以自己控制读写权限
- 对于写权限,可以检测写入数据的有效性
#include <iostream>
using namespace std;
class Person{
string m_Name;
int m_Age;
public:
void setName(string name) {
m_Name = name;
}
void setAge(int age) {
if (age < 0 || age>120) {
age = 0;
cout << "输入失败" << endl;
return;
}
m_Age = age;
}
void showPerson() {
cout << "姓名:" << m_Name << "\t年龄:" << m_Age << endl;
}
string getName() {
return m_Name;
}
int getAge() {
return m_Age;
}
};
int main() {
Person p;
p.setAge(200);
p.setName("张三");
cout << p.getName() << "的年龄为" << p.getAge() << endl;
return 0;
}
案例:点类和圆类
设计一个点类和圆类,判断点和圆的关系。 要点:类的分文件编写
#pragma once
#include <iostream>
using namespace std;
class Point {
double m_X, m_Y;
public:
void setPoint(double x, double y);
double getPoint_X();
double getPoint_Y();
};
#include "Point.h"
void Point::setPoint(double x, double y) {
m_X = x;
m_Y = y;
}
double Point::getPoint_X() {
return m_X;
}
double Point::getPoint_Y() {
return m_Y;
}
接着是距离计算函数,用来计算两点之间的距离
#pragma once
#include "Point.h"
double getDistance(Point p1, Point p2);
#include "getDistance.h"
double getDistance(Point p1, Point p2) {
double x, y;
x = pow((p1.getPoint_X() - p2.getPoint_X()), 2);
y = pow((p1.getPoint_Y() - p2.getPoint_Y()), 2);
return sqrt(x + y);
}
#pragma once
#include <iostream>
#include "Point.h"
#include "getDistance.h"
using namespace std;
class Circle {
Point m_Pc;
double m_Ra;
public:
void setPc(Point p);
void setRadius(double r);
Point getPc();
double getRa();
void Point2Circle(Point p);
};
#include "circle.h"
void Circle::setPc(Point p) {
m_Pc.setPoint(p.getPoint_X(), p.getPoint_Y());
}
void Circle::setRadius(double r) {
m_Ra = r;
}
Point Circle::getPc() {
return m_Pc;
}
double Circle::getRa() {
return m_Ra;
}
void Circle::Point2Circle(Point p) {
int ref = getDistance(this->m_Pc, p);
cout << "点的坐标:(" << p.getPoint_X() << "," << p.getPoint_Y() << ")" << endl;
cout << "圆心坐标:(" << this->m_Pc.getPoint_X() << "," << this->m_Pc.getPoint_Y() << ")" << endl;
cout << "圆的半径:" << this->m_Ra << endl;
cout << "点到圆心的距离:" << ref << endl;
if (ref > this->m_Ra) {
cout << "点在圆外" << endl;
}
else if (ref == this->m_Ra) {
cout << "点在圆上" << endl;;
}
if (ref < this->m_Ra) {
cout << "点在圆内" << endl;;
}
}
#include <iostream>
#include "Circle.h"
using namespace std;
int main() {
Point p1,p2;
p1.setPoint(0, 0);
p2.setPoint(20,0);
Circle c1;
c1.setPc(p2);
c1.setRadius(20);
c1.Point2Circle(p1);
return 0;
}
对象的初始化和清理
- 构造函数:创建对象时为对象的成员属性赋值,构造函数有编译器自动调用,无须手动调用
- 析构函数:对象销毁前,系统自动调用,执行一些清理工作
构造函数 | 析构函数 |
---|
没有返回值,不写void | 没有返回值,不写void | 函数名与类名相同 | 函数名与类名不同 | 调用对象时由程序自动调用,且只调用一次 | 对象销毁时由程序自动调用,且只调用一次 | 可以有参数,可重载 | 不可以有参数,不能重载 |
构造函数 类名(){}
无参构造函数,如果是空实现,未对成员变量初始化,会有如下报错。 解决方式:初始化成员变量
- 按照有无参数分为无参构造和有参构造,其中无参构造又称为默认构造
- 按类型可分为普通构造和拷贝构造
- 三种调用方法,推荐括号法
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "Person无参构造函数调用" << endl;
}
Person(int a) {
this->m_Age = a;
cout << "Person有参构造函数调用" << endl;
}
Person(const Person &p) {
this->m_Age = p.m_Age;
cout << "Person拷贝构造函数调用" << endl;
}
~Person() {
cout << "Person析构函数调用" << endl;
}
int m_Age;
};
void test01() {
Person p7 = 10;
}
int main() {
test01();
system("pause");
return 0;
}
拷贝构造函数调用时机
拷贝函数调用时机
- 使用一个已经创造完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 值方式返回局部对象
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "Person默认构造函数" << endl;
}
Person(int age) {
this->m_Age = age;
cout << "Person有参构造函数" << endl;
}
Person(const Person &p) {
this->m_Age = p.m_Age;
cout << "Person拷贝构造函数" << endl;
}
~Person() {
cout << "Person析构造函数" << endl;
}
int m_Age;
};
void test01() {
Person p1(20);
Person p2(p1);
}
void doWork(Person p) {
}
void test02() {
Person p;
doWork(p);
}
Person dowork2() {
Person p1;
cout << (int)&p1 << endl;
return p1;
}
void test03() {
Person p = dowork2();
cout <<(int) &p << endl;
}
int main() {
test03();
system("pause");
return 0;
}
构造函数调用规则
- 默认情况下,编译器提供默认构造函数(无参,函数体为空)、默认析构函数(无参,函数体为空)和默认拷贝构造函数。
- 无参构造<有参构造<拷贝构造,如果用户定义了有参构造,则c++不提供无参构造,但还会提供拷贝构造;如果用户定义了拷贝构造,则编译器不提供构造函数。如果编译器不提供,自己又不去定义,就会报错。
#include <iostream>
using namespace std;
class Person {
public:
int m_Age;
Person(int age) {
this->m_Age = age;
cout << "Person有参构造函数调用" << endl;
}
~Person() {
cout << "Person默认析构函数调用" << endl;
}
};
void test01() {
Person p;
}
int main() {
test01();
system("pause");
return 0;
}
深拷贝与浅拷贝
- 浅拷贝:编译器提供的那种简单的等号赋值操作,存在堆区内存重复释放的缺点
- 深拷贝:在堆区重新申请一块内存空间,进行拷贝操作
因此,如果有属性在读取开辟,要自己提供深拷贝,防止堆区内存重复释放
#include <iostream>
using namespace std;
class Person {
public:
int m_Age;
int *m_Height;
Person() {
cout << "Person默认构造函数调用" << endl;
}
Person(int age,int height) {
this->m_Age = age;
this->m_Height = new int(height);
cout << "Person有参构造函数调用" << endl;
}
Person(const Person& p) {
this->m_Age = p.m_Age;
this->m_Height = new int(*p.m_Height);
}
~Person() {
if (m_Height!=NULL){
delete m_Height;
m_Height = NULL;
}
cout << "Person默认析构函数调用" << endl;
}
};
void test01() {
Person p1(15,160);
cout << "p1年龄:" << p1.m_Age << ",p1的身高:" << *p1.m_Height << endl;
Person p2(p1);
cout << "p2年龄:" << p2.m_Age << ",p2的身高:" << *p2.m_Height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
初始化列表
就是给属性赋初值的一种方法,个人感觉传统的有参构造更好用
#include <iostream>
using namespace std;
class Person {
public:
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
int m_A;
int m_B;
int m_C;
};
void test01() {
Person p(10,20,30);
cout << "m_A:" << p.m_A << endl;
cout << "m_B:" << p.m_B << endl;
cout << "m_C:" << p.m_C << endl;
}
void test02() {
Person p(10, 20, 30);
cout << "m_A:" << p.m_A << endl;
cout << "m_B:" << p.m_B << endl;
cout << "m_C:" << p.m_C << endl;
}
int main() {
test02();
system("pause");
return 0;
}
类作为成员对象,类中有类
结论:如果A类中由B类成员,则会先构造B,再构造A,析构时则相反 这里两个程序,主要展示了初始化成员列表以及构造函数调用法则。
#include <iostream>
#include <string>
using namespace std;
class Phone {
public:
Phone() {
cout << "Phone默认构造" << endl;
}
Phone(string brand) {
cout << "phone有参构造" << endl;
m_Brand = brand;
}
~Phone() {
cout << "Phone析构函数调用" << endl;
}
string m_Brand;
};
class Person {
public:
~Person() {
cout << "Person析构函数调用" << endl;
}
Person(string name, string brand):m_Name(name), m_Phone(brand){
cout << "Person参数列表初始化" << endl;
}
string m_Name;
Phone m_Phone;
};
void test01() {
Person p("张三", "诺基亚");
cout << p.m_Name << "拿着" << p.m_Phone.m_Brand << endl;
}
int main() {
test01();
system("pause");
return 0;
}
- 不用初始化成员列表也能写,只用到了Phone的默认构造
#include <iostream>
#include <string>
using namespace std;
class Phone {
public:
Phone() {
cout << "Phone默认构造" << endl;
}
Phone(string brand) {
cout << "phone有参构造" << endl;
m_Brand = brand;
}
~Phone() {
cout << "Phone析构函数调用" << endl;
}
string m_Brand;
};
class Person {
public:
Person(string name, string brand){
cout << "Person有参构造函数调用" << endl;
m_Phone = Phone(brand);
m_Name = name;
}
~Person() {
cout << "Person析构函数调用" << endl;
}
string m_Name;
Phone m_Phone;
};
void test01() {
Person p("张三", "水果");
cout << p.m_Name << "拿着" << p.m_Phone.m_Brand << endl;
}
int main() {
test01();
system("pause");
return 0;
}
静态成员,static
包括静态成员变量和静态成员函数
静态成员变量
- 所有对象共享一份数据
- 在编译阶段分配内存,全局区
- 类内声明,类外初始化
- 依然受到访问权限约束
#include <iostream>
using namespace std;
class Person {
public:
static int m_A;
};
int Person:: m_A=100;
void test01() {
Person p;
cout << "p.m_A=:" << p.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p.m_A=:" << p.m_A << endl;
cout << "m.A=" << Person::m_A << endl;
}
int main() {
test01();
system("pause");
return 0;
}
静态成员函数
成员函数前缀statice
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
- 也受到访问权限限制
#include <iostream>
using namespace std;
class Person {
public:
static void fun() {
m_A = 100;
cout << "static void fun()调用" << endl;
}
static int m_A;
int m_B;
};
int Person::m_A = 100;
void test01() {
Person p;
p.fun();
Person::fun();
}
int main() {
test01();
system("pause");
return 0;
}
成员变量和成员函数分开存储
- 空的类,就是{}里面啥也不写,实例化的对象占空间大小为1
- 实例化对象所占空间大小只和非静态成员变量有关,本例占8,是因为两个int
#include <iostream>
using namespace std;
class Person {
public:
int m_A = 1;
int m_b = 1;
static int m_B;
void func1() {}
static void func2() {}
};
int Person::m_B = 1;
void test01() {
Person p;
cout << "p占用内存空间:" << sizeof(p) << endl;
}
int main() {
test01();
system("pause");
return 0;
}
this指针
谁调用就指向谁
- 当形参变量和成员变量重名时,用this加以区分,最好还是做好命名规范,别给自己找麻烦
- 在类的非静态成员函数中返回对象本身,可用return *this,且函数返回值类型是类名&,也就是引用的方式
- 如果2中没有引用的方式返回,则发生了拷贝,返回的不是自身
#include <iostream>
using namespace std;
class Person {
public:
Person(int age) {
this->age = age;
}
Person & PersonAddAge(Person& p) {
this->age += p.age;
return *this;
}
int age;
};
void test01() {
Person p1(1);
cout << "p1年龄:" << p1.age << endl;
Person p2(1);
cout << "p2年龄:" << p2.age << endl;
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2年龄:" << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
空指针访问成员函数
如果定义了一个类的空指针,用空指针调用成员函数可能出现报错。为了防止成员崩溃,应防止传入空指针。if (this == NULL) return;//防止传入的是空指针
#include <iostream>
using namespace std;
class Person {
public:
void showClassName() {
cout << "Person类" << endl;
}
void showPersonAge() {
if (this == NULL) return;
cout << "age:" << this->m_Age << endl;
}
int m_Age;
};
void test01() {
Person* p = NULL;
p->showPersonAge();
p->showClassName();
}
int main() {
test01();
system("pause");
return 0;
}
const修饰成员函数,常函数
- 常函数 函数名() const {}
- 常函数中不能修改成员变量的值
- 如果要改值,在定义成员变量时,前缀关键字mutable
- 实例化对象时前缀const,常对象,只能调用常函数
- 同样的,常对象只能修改mutable修饰的成员变量
#include <iostream>
using namespace std;
class Person {
public:
void showPerson()const{
this->m_B = 200;
}
void fun() {}
int m_A;
mutable int m_B;
};
void test01() {
const Person p;
p.m_B = 20;
p.showPerson();
Person p2;
p2.fun();
p2.m_A = 100;
p2.showPerson();
}
int main() {
test01();
system("pause");
return 0;
}
友元
类里面的访问权限中,private作用下的成员变量是类外访问不到的。但是,如果是友元就可以给他访问权限,有以下两种情况:
全局函数作友元
你有一个建筑类,里面有客厅和卧室,普通客人只能进客厅,不能访问卧室。但是你可以让好朋友进去,好朋友全局函数就是你建筑类的友元。
- 在类里面声明全局函数,且前缀friend,不用加public作用域
#include <iostream>
using namespace std;
class Building;
class Goodgay {
public:
Goodgay();
~Goodgay();
void visit();
Building* m_building;
};
class Building {
friend class Goodgay;
friend void Goodgay::visit();
Building();
~Building();
public:
string m_SettingRoom;
private:
string m_BedRoom;
};
Goodgay::Goodgay() {
cout << "Goodgay构造函数" << endl;
m_building = new Building;
}
Goodgay::~Goodgay() {
cout << "Goodgay析构函数" << endl;
if (m_building != NULL) {
delete m_building;
m_building = NULL;
}
}
void Goodgay::visit() {
cout << "好基友类正在访问" << m_building->m_BedRoom << endl;
cout << "好基友类正在访问" << m_building->m_SettingRoom << endl;
}
Building::Building() {
cout << "Building构造函数" << endl;
m_BedRoom = "卧室";
m_SettingRoom = "客厅";
}
Building::~Building() {
cout << "Building析构函数" << endl;
}
void test01() {
Goodgay gg;
gg.visit();
}
int main() {
test01();
system("pause");
return 0;
}
- 根据运行结果显示,先构造了goodgay,再构造了building;释放时也是先释放的goodgay,再释放的building
- 栈的数据是先进后出的,这里开辟了堆区数据,所以产生了差异
类和成员函数作友元
- 让一个类可以访问另一个类中的私有属性
- 让一个类中的单个成员函数可以访问另一个类中私有属性
- 成员函数的特殊写法:类内声明,类外加作用域实现。
- 特别注意这种嵌套类,声明定义时候的先后顺序
#include <iostream>
using namespace std;
class Building;
class Goodgay {
public:
Goodgay();
void visit();
Building* m_building;
};
class Building {
friend class Goodgay;
friend void Goodgay::visit();
Building();
public:
string m_SettingRoom;
private:
string m_BedRoom;
};
Goodgay::Goodgay() {
m_building = new Building;
}
void Goodgay::visit() {
cout << "好基友类正在访问" << m_building->m_BedRoom << endl;
cout << "好基友类正在访问" << m_building->m_SettingRoom << endl;
}
Building::Building() {
m_BedRoom = "卧室";
m_SettingRoom = "客厅";
}
void test01() {
Goodgay gg;
gg.visit();
}
int main() {
test01();
system("pause");
return 0;
}
运算符重载
重新定义运算符,进行自定义的数据类型的运算
加号运算符重载
比如两个类相加,普通的"+"是无法实现的。可行的做法是,在类里面写一个成员函数,传入一个类,实现类间相加。现在编译器给这个成员函数取了一个名字,叫operator+,当然,而也可以通过全局函数实现加号运算符重载。
#include <iostream>
using namespace std;
class Person {
public:
Person() {
}
Person(int a,int b) {
m_A = a;
m_B = 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;
}
int m_A;
int m_B;
};
void test01() {
Person p1(10, 20);
Person p2(30, 40);
Person p4 = p1 + p2;
cout << "p4.m_A=" << p4.m_A << endl;
cout << "p4.m_B=" << p4.m_B << endl;
}
int main() {
test01();
system("pause");
return 0;
}
左移运算符重载
- 左移运算符重载只能利用全局函数实现
- cout,右击定义,属于ostream,标准的输出流类
#include <iostream>
using namespace std;
class Person {
public:
int m_A;
int m_B;
};
ostream& operator<<(ostream &cout,Person &p){
cout << "p.m_A=" << p.m_A <<"\tp.m_B="<<p.m_B<<endl;
return cout;
}
void test01() {
Person p;
p.m_A = 1;
p.m_B = 2;
cout << p << endl;
}
int main() {
test01();
system("pause");
return 0;
}
递增运算符重载
#include <iostream>
using namespace std;
class MyInt {
public:
MyInt() {
m_Int = 0;
}
MyInt& operator++() {
this->m_Int++;
return *this;
}
MyInt operator++(int) {
MyInt temp = *this;
this->m_Int++;
return temp;
}
int m_Int;
};
ostream& operator<<(ostream& cout, MyInt num) {
cout << num.m_Int << endl;
return cout;
}
void test01() {
MyInt p;
p.m_Int = 0;
cout << p;
cout << ++p;
cout << p;
}
void test02() {
MyInt myint;
cout << myint;
cout << (myint++);
cout << myint;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
赋值运算符重载
堆区的数据手动开辟,手动释放。编译器提供的直接等号赋值操作,是浅拷贝,对于堆区数据利用浅拷贝,会出现堆区内存重复释放的问题,因此要用深拷贝,重新开辟一块空间,不再指向同一块内存。
- 两块堆区数据,再赋值前,先将被复制的那一块进行了释放操作。
- 连等操作要返回自身
#include <iostream>
using namespace std;
class Person {
public:
Person(int age) {
m_Age = new int(age);
}
Person& operator=(Person &p) {
if (m_Age!=NULL){
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
return *this;
}
~Person() {
if (m_Age != NULL) {
delete m_Age;
m_Age = NULL;
}
}
int* m_Age;
};
void test01() {
Person p1(18);
cout << "p1的年龄:" << *p1.m_Age << endl;
Person p2(20);
cout << "p2的年龄:" << *p2.m_Age << endl;
p1 = p2;
cout << "p1的年龄:" << *p1.m_Age << endl;
cout << "p1的年龄:" << *p1.m_Age << endl;
Person p3(30);
p1 = p2 = p3;
cout << "p1的年龄:" << *p1.m_Age << endl;
cout << "p2的年龄:" << *p2.m_Age << endl;
cout << "p3的年龄:" << *p3.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
函数调用运算符重载 仿函数
对()重载,使用起来像是函数调用,故名仿函数,仿函数使用灵活,可以通过对象调用,也可以通过类名加括号调用,这种调用方式也被称为匿名对象。
#include <iostream>
#include <string>
using namespace std;
class myAdd{
public:
void operator()(int num1, int num2) {
cout << num1 + num2 << endl;
}
};
class myCout {
public:
void operator()(string str) {
cout << str << endl;
}
};
void test01() {
myAdd myadd;
myadd(100, 100);
myAdd()(20, 20);
}
void test02() {
myCout mycout;
mycout("hello world!");
}
int main() {
test02();
system("pause");
return 0;
}
继承
继承的作用是可以省去很多重复代码
基本语法
- class 子类:继承方式 父类
- 子类也叫派生类,父类也叫基类
- 先构造父类,再构造子类;析构时顺序相反
下面有个案例,小白和小黑是兄弟,每天一起吃饭睡觉。但是小白喜欢唱歌,小黑喜欢跳舞。吃饭睡觉是两人共有的,作为父类。每个人有自己的喜好,独立完成,作为子类
#include <iostream>
using namespace std;
class Base {
public:
Base() {
cout << "父类构造" << endl;
}
~Base() {
cout << "父类析构" << endl;
}
void eat() {
cout << "吃饭" << endl;
}
void sleep() {
cout << "睡觉" << endl;
}
};
class Bai :public Base {
public:
Bai() {
cout << "子类构造" << endl;
}
~Bai() {
cout << "子类析构" << endl;
}
void sing() {
cout << "唱歌" << endl;
}
};
class Hei :public Base {
public:
void dance() {
cout << "跳舞" << endl;
}
};
void test01() {
Bai bai;
bai.eat();
bai.sing();
bai.sleep();
}
int main () {
test01();
system("pause");
return 0;
}
继承方式
如下表所示:横向为父类中属性的访问权限,纵向为子类的继承方式,表格中为子类继承后属性的访问权限。
继承方式 \父类 | 公有 | 保护 | 私有 |
---|
公有 | 公有 | 保护 | 无权访问 | 保护 | 保护 | 保护 | 无权访问 | 私有 | 私有 | 私有 | 无权访问 |
父类中的元素,有三种访问权限:公有、保护、私有 子类的继承方式也有三种:公有、保护、私有
- 父类中的私有内容,无论那种继承方式,子类都访问不到
- 公有继承,父类 公有和保护 子类 仍保持原访问权限
- 保护继承,父类 共有和保护 子类 全变成保护
- 私有继承、父类 公有和保护 子类 全变成私有
#include <iostream>
using namespace std;
class A {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class B1 :public A {
void fun() {
m_A = 100;
m_B = 100;
}
};
class B2 :protected A {
void fun() {
m_A = 100;
m_B = 100;
}
};
class B3 :private A {
void fun() {
m_A = 100;
m_B = 100;
}
};
class C :public B3 {
void fun() {
}
};
void test01() {
B1 b1;
b1.m_A = 100;
B2 b2;
B3 b3;
}
int main() {
system("pause");
return 0;
}
继承中的对象模型
- 父类中所有非静态成员都被子类继承下来,
- 父类中的私有属性,子类访问不到,但是也被继承了下来
- 利用vs自带工具查看类
#include <iostream>
using namespace std;
class A {
int m_A;
public:
int m_B;
protected:
int m_C;
};
class B :public A {
public:
int m_D;
};
void test01() {
B b;
cout << "b占用和内存:" << sizeof(b) << endl;
}
int main() {
test01();
system("pause");
return 0;
}
工具截图,类B占16字节,父类是A,继承了m_A、m_B、m_C,定义了m_D
同名成员
如果子类和父类中有同名成员,包括变量和函数
- 子类对象可以直接访问子类中同名成员
- 子类对象加父类作用域可以访问到父类同名成员
同名静态成员
至于非静态成员,既可以通过对象调用,也可以类名的方式访问。在通过类名的方式访问的时候,子类名::父类名::同名静态成员
多继承
一个子类可以继承多个父类,但是可能引起重名问题,需通过作用域加以区分。不推荐使用这种方式。
class 子类:继承方式 父类1,继承方式 父类2···{};
菱形继承与虚继承
一个父类派生两个子类,又有一个(孙子)类同时继承了这两个子类,就是菱形继承,也叫钻石继承。
- 如果两个子类有同名成员,可以通过作用域区分,但是也造成了无意义的资源浪费
- 利用虚继承可以解决1的问题,在继承方式和父类名之间加关键字virtual
#include <iostream>
using namespace std;
class Animal{
public:
int m_Age;
};
class Sheep :public virtual Animal {};
class Tuo :public virtual Animal {};
class SheepTuo :public Sheep, public Tuo {};
void test01() {
SheepTuo st;
st.m_Age = 6;
cout << "st.m_Age:" << st.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
不用虚继承
虚基类指针,通过指针指向数据
多态
多态是c++面向对象三大特性之一。 多态分为两类:
- 静态多态:函数重载和运算符重载,
- 动态多态:派生类和虚函数实现运行时多态
两类多态区别: - 静态多态:函数地址早绑定,编译阶段确定函数地址
- 动态多态:函数地址晚绑定,运行阶段确定函数地址
动态多态
动态多态条件:
- 有继承关系
- 子类重写父类虚函数,重写不是重载,重写是函数返回值类型 函数名 参数列表完全相同
一个案例: 父类是动物类,猫狗是子类,有同名函数。有一个函数,父类指针指向子类对象,传入猫狗,就运行猫狗的函数,而不是父类中的函数
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Cat:public Animal{
public:
void speak() {
cout << "猫在说话" << endl;
}
};
class Dog :public Animal {
public:
void speak() {
cout << "狗在说话" << endl;
}
};
void doSpeak(Animal &animal) {
animal.speak();
}
void test01() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
案例 多态计算机
普通的计算器写法和多态计算器写法 多态调用:父类指针或者引用指向子类对象
#include <iostream>
using namespace std;
class Calculator {
public:
int getResult(char oper) {
switch (oper)
{
case '+':
return m_Num1 + m_Num2;
break;
case '-':
return m_Num1 - m_Num2;
break;
case '*':
return m_Num1 * m_Num2;
break;
case '/':
return m_Num1 / m_Num2;
break;
default:
break;
}
}
int m_Num1;
int m_Num2;
char oper;
};
class Base_JiSuanQi {
public:
virtual int getResult() {
return 0;
}
int m_Num1;
int m_Num2;
};
class son_Add :public Base_JiSuanQi {
public:
int getResult() {
return m_Num1+m_Num2;
}
};
class son_Sub :public Base_JiSuanQi {
public:
int getResult() {
return m_Num1 - m_Num2;
}
};
class son_Mul :public Base_JiSuanQi {
public:
int getResult() {
return m_Num1 * m_Num2;
}
};
class son_Div :public Base_JiSuanQi {
public:
int getResult() {
return m_Num1 / m_Num2;
}
};
void test01() {
cout << "**计算器普通写法**" << endl;
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult('+') << endl;
cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getResult('-') << endl;
cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult('*') << endl;
cout << c.m_Num1 << "/" << c.m_Num2 << "=" << c.getResult('/') << endl;
}
void test02() {
cout << "**多态计算器**" << endl;
Base_JiSuanQi* js = new son_Add;
js->m_Num1 = 10;
js->m_Num2 = 10;
cout << js->m_Num1 << "+" << js->m_Num2 << "=" << js->getResult() << endl;
delete js;
js = new son_Sub;
js->m_Num1 = 10;
js->m_Num2 = 10;
cout << js->m_Num1 << "-" << js->m_Num2 << "=" << js->getResult() << endl;
delete js;
js = new son_Mul;
js->m_Num1 = 10;
js->m_Num2 = 10;
cout << js->m_Num1 << "*" << js->m_Num2 << "=" << js->getResult() << endl;
delete js;
js = new son_Div;
js->m_Num1 = 10;
js->m_Num2 = 10;
cout << js->m_Num1 << "/" << js->m_Num2 << "=" << js->getResult() << endl;
delete js;
}
int main() {
test02();
system("pause");
return 0;
}
纯虚函数和多态类 虚析构与纯虚析构
因为父类中的虚函数实现毫无意义,基本用不到,主要调用的是子类中的内容。因此可以将虚函数写为纯虚函数。 纯虚函数语法:
virtual 返回值类型 函数名 (参数列表)= 0;
当函数中有了纯虚函数,这个类也叫抽象类 抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
虚析构与纯虚析构 虚析构稍微方便一点
- 如果子类有数据开辟到堆区,父类的指针无法释放子类对象,解决办法是在父类析构函数前缀virtual。
- 纯虚析构在父类中声明之后,类外要实现。且纯虚析构,类也是抽象类
- 二者只能有一个
#include <iostream>
using namespace std;
#include <string>
class baseAnimal {
public:
baseAnimal() {
cout << "baseAnimal构造函数" << endl;
}
virtual ~baseAnimal() = 0;
virtual void speak() = 0;
};
baseAnimal::~baseAnimal() {
cout << "baseAnimal纯虚析构函数" << endl;
}
class Cat :public baseAnimal {
public:
Cat(string name) {
cout << "Cat构造函数" << endl;
m_Name = new string(name);
}
~Cat() {
cout << "Cat析构函数" << endl;
if (m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
void speak() {
cout <<*m_Name<< "猫在说话" << endl;
}
string* m_Name;
};
class Dog :public baseAnimal {
public:
void speak() {
cout << "狗在说话" << endl;
}
};
void doSpeak(baseAnimal* animal) {
animal->speak();
delete animal;
}
void test01() {
doSpeak(new Cat("Tom"));
}
int main() {
test01();
system("pause");
return 0;
}
案例 制作饮品 父类指针或引用指向子类对象
主要展示了纯虚函数,以及父类指针或者引用指向子类对象的具体实现方式
#include <iostream>
using namespace std;
class baseDrink {
public:
virtual void boilWater() = 0;
virtual void brew() = 0;
virtual void pour() = 0;
virtual void add() = 0;
void makeDrink() {
boilWater();
brew();
pour();
add();
}
};
class Tea:public baseDrink {
public:
void boilWater() {
cout << "第一步:烧水" << endl;
}
void brew() {
cout << "第二步:冲泡茶叶" << endl;
}
void pour() {
cout << "第三步:倒入杯中" << endl;
}
void add() {
cout << "第四步:加点枸杞" << endl;
}
};
class Coffee :public baseDrink {
public:
void boilWater() {
cout << "第一步:烧水" << endl;
}
void brew() {
cout << "第二步:冲泡咖啡" << endl;
}
void pour() {
cout << "第三步:倒入杯中" << endl;
}
void add() {
cout << "第四步:加点牛奶糖" << endl;
}
};
void doMakeDrink(baseDrink* bd) {
bd->makeDrink();
delete bd;
}
void doMakeDrink(baseDrink& bd) {
bd.makeDrink();
}
void test01() {
doMakeDrink(new Tea);
cout << "----------------" << endl;
doMakeDrink(new Coffee);
cout << "----------------" << endl;
Tea tea;
doMakeDrink(tea);
cout << "----------------" << endl;
Coffee coffee;
doMakeDrink(coffee);
cout << "----------------" << endl;
}
int main() {
test01();
system("pause");
return 0;
}
案例 组装电脑
#include <iostream>
#include <string>
using namespace std;
class CPU {
public:
virtual void calculate() = 0;
};
class VideoCard{
public:
virtual void display() = 0;
};
class Memory {
public:
virtual void storage() = 0;
};
class Computer {
public:
Computer(CPU* cpu,VideoCard* vc,Memory* mem) {
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
void doWork() {
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
CPU* m_cpu;
VideoCard* m_vc;
Memory* m_mem;
~Computer() {
if (m_cpu!=NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
};
class InterCpu :public CPU {
public:
void calculate() {
cout << "InterCpu正在计算" << endl;
}
};
class InterVideoCard :public VideoCard {
public:
void display() {
cout << "Inter显卡正在显示" << endl;
}
};
class InterMemory :public Memory {
public:
void storage() {
cout << "Inter内存正在存储" << endl;
}
};
class LenovoCpu :public CPU {
public:
void calculate() {
cout << "LenovoCpu正在计算" << endl;
}
};
class LenovoVideoCard :public VideoCard {
public:
void display() {
cout << "Lenovo显卡正在显示" << endl;
}
};
class LenovoMemory :public Memory {
public:
void storage() {
cout << "Lenovo内存正在存储" << endl;
}
};
void test01() {
CPU* intercpu = new InterCpu;
VideoCard* intercard = new InterVideoCard;
Memory* intermem = new InterMemory;
Computer* pc1 = new Computer(intercpu, intercard, intermem);
pc1->doWork();
delete pc1;
cout << "--------------------" << endl;
Computer* pc2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);
pc2->doWork();
delete pc2;
cout << "--------------------" << endl;
Computer* pc3 = new Computer(new InterCpu, new LenovoVideoCard, new LenovoMemory);
pc3->doWork();
delete pc3;
}
int main() {
test01();
system("pause");
return 0;
}
|