第二天 C++核心编程(1)

1.内存分区模型

C++程序在执行时,将内存大方向划分为4个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束的时候由操作系统回收

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

1.1 程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域

代码区:

存放 CPU 执行的机器指令

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

代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区:

全局变量和静态变量存放在此

全局区还包含了常量区,字符串常量和其他常量也存放在此

该区域的数据在程序结束后由操作系统释放

#include<iostream>
using namespace std;
//全局变量
int g_a = 10;
int g_b = 10;
//全局常量
const int c_g_a = 10;
const int c_g_b = 10;
int main()
{
//局部变量
int a = 10;
int b = 10;
//打印地址
cout << "局部变量a的地址是" << (int)&a << endl;
cout << "局部变量b的地址是" << (int)&b << endl;
cout << "全局变量g_a的地址是" << (int)&g_a << endl;
cout << "全局变量g_b的地址是" << (int)&g_b << endl;
//静态变量
static int s_a = 10;
static int s_b = 10;
cout << "静态变量s_a地址为: " << (int)&s_a << endl;
cout << "静态变量s_b地址为: " << (int)&s_b << endl;

cout << "字符串常量地址为: " << (int)&"hello world" << endl;
cout << "字符串常量地址为: " << (int)&"hello world1" << endl;

cout << "全局常量c_g_a地址为: " << (int)&c_g_a << endl;
cout << "全局常量c_g_b地址为: " << (int)&c_g_b << endl;
const int c_l_a = 10;
const int c_l_b = 10;
cout << "局部常量c_l_a地址为: " << (int)&c_l_a << endl;
cout << "局部常量c_l_b地址为: " << (int)&c_l_b << endl;
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_数据

局部变量,局部常量存放在栈区;全局变量和静态变量存放在全局区;常量存在常量区

总结:

  • C++中在程序运行前分为全局区和代码区
  • 代码区特点是共享和只读
  • 全局区中存放全局变量、静态变量、常量
  • 常量区中存放 const修饰的全局常量 和 字符串常量

1.2 程序运行后

栈区:

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

注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

#include<iostream>
using namespace std;
int* func1()
{
int a = 10;
return &a;
}
int main()
{
int* a = func1();
cout << *a << endl;
cout << *a << endl;
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_ios_02

第一次是10是因为编译器为我们保留了a的数据,但是第二次的时候a位置的数据就变成了随机数

堆区:

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

在C++中主要利用new在堆区开辟内存

#include<iostream>
using namespace std;
int* fun()
{
int* a = new int(10);
return a;
}
int main()
{
int* aa = fun();
cout << *aa << endl;
cout << *aa << endl;
cout << *aa << endl;
cout << *aa << endl;
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_#include_03

总结:

堆区数据由程序员管理开辟和释放

堆区数据利用new关键字进行开辟内存

1.3 new操作符

C++中利用==new==操作符在堆区开辟数据

堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete 

利用new创建的数据,会返回该数据对应的类型的指针

示例1: 基本语法

#include<iostream>
using namespace std;
int* fun()
{
int* a = new int(10);
return a;
}
int main()
{
int* aa = fun();
cout << *aa << endl;
cout << *aa << endl;
cout << *aa << endl;
//通过delete来释放堆区上开辟的空间
delete aa;
cout << *aa << endl;
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_数据_04

最后一次的cout并没有正常执行,因为此时aa没有权限去访问释放的空间

示例2:开辟数组

#include<iostream>
using namespace std;
int main()
{
int* p = new int[10];
for (int i = 0; i < 10; i++)
{
p[i] = i + 100;
}
for (int i = 0; i < 10; i++)
{
cout << p[i] << endl;
}
//释放数组的时候delete后面要加[];
delete[] p;
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_数据_05

2.引用

2.1 引用的基本使用

作用:给变量起别名

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

#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
b = 100;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_#include_06

2.2 引用注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变
#include<iostream>
using namespace std;
int main() {

int a = 10;
int b = 20;
//int &c; //错误,引用必须初始化
int &c = a; //一旦初始化后,就不可以更改
c = b; //这是赋值操作,不是更改引用

cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;

system("pause");

return 0;
}

2.3 引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参

优点:可以简化指针修改实参

#include<iostream>
using namespace std;
//1. 值传递
void myswap01(int a,int b)
{
int temp=0;
temp = a;
a = b;
b = temp;
}
//2. 地址传递
void myswap02(int* a,int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
//3. 引用传递
void myswap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
myswap01(a, b);
cout << "a:" << a << "b:" << b << endl;
myswap02(&a, &b);
cout << "a:" << a << "b:" << b << endl;
myswap03(a, b);
cout << "a:" << a << "b:" << b << endl;
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_数据_07

第一次并没有交换a和b的值,第二次和第三次交换了a和b的值

总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单

2.4 引用做函数的返回值

作用:引用是可以作为函数的返回值存在的

注意:不要返回局部变量引用

用法:函数调用作为左值

#include<iostream>
using namespace std;
// 返回局部变量的引用
int& test01()
{
int a = 10;
return a;
}
//返回静态变量的引用
int& test02()
{
static int a = 20;
return a;
}
int main()
{
int& ret = test01();
cout << "ret=" << ret << endl;
cout << "ret=" << ret << endl;
int& ret02 = test02();
cout << "ret02=" << ret02 << endl;
cout << "ret02=" << ret02 << endl;
cout << "ret02=" << ret02 << endl;
test02() = 1000;
cout << "ret02=" << ret02 << endl;
cout << "ret02=" << ret02 << endl;
cout << "ret02=" << ret02 << endl;
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_#include_08

通过static延长了test02中变量a的生命周期

2.5 引用的本质

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

#include<iostream>
using namespace std;
//发现是引用,转换为 int* const ref = &a;
void func(int& ref){
ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
int a = 10;

//自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
int& ref = a;
ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;

cout << "a:" << a << endl;
cout << "ref:" << ref << endl;

func(a);
return 0;
}

结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了

2.6 常量引用

作用:常量引用主要用来修饰形参,防止误操作

在函数形参列表中,可以加const修饰形参,防止形参改变实参

#include<iostream>
using namespace std;
//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
//v += 10;
cout << v << endl;
}

int main() {

//int& ref = 10; 引用本身需要一个合法的内存空间,因此这行错误
//加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;
const int& ref = 10;

//ref = 100; //加入const后不可以修改变量
cout << ref << endl;

//函数中利用常量引用防止误操作修改实参
int a = 10;
showValue(a);

system("pause");

return 0;
}

从零开始学编程---C++篇(2)_#include_09

3.函数提高

3.1 函数默认参数

在C++中,函数的形参列表中的形参是可以有默认值的。

语法:​​返回值类型 函数名 (参数= 默认值){}​

1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值

2. 如果函数声明有默认值,函数实现的时候就不能有默认参数

#include<iostream>
using namespace std;
int func(int a = 10, int b = 20, int c = 30)
{
return a + b + c;
}
int func2(int a = 10, int b = 20);
int func2(int a, int b)
{
return a + b;
}
int main()
{
int ret = func();
cout << "ret=" << ret << endl;
ret = func(30, 30, 30);
cout << "ret=" << ret << endl;
int ret02 = func2();
cout << "ret02=" << ret02 << endl;
system("pause");
return 0;
}

3.2 函数占位参数

C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

语法:​返回值类型 函数名 (数据类型){}​

#include<iostream>
using namespace std;
//函数占位参数 ,占位参数也可以有默认参数
void func(int a, int) {
cout << "this is func" << endl;
}

int main() {

func(10,10); //占位参数必须填补

system("pause");

return 0;
}

3.3 函数重载

3.3.1 函数重载概述

作用:函数名可以相同,提高复用性

函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同 或者 个数不同 或者 顺序不同

注意: 函数的返回值不可以作为函数重载的条件

#include<iostream>
using namespace std;
void func()
{
cout << "调用func()函数" << endl;
}
void func(int a)
{
cout << "调用func(int a)函数" << endl;
}
void func(double b)
{
cout << "调用func(double b)函数" << endl;
}
void func(int a,double b)
{
cout << "调用func(int a,double b)函数" << endl;
}
void func(double a,int b)
{
cout << "调用func(double a,int b)函数" << endl;
}void func(int a,int b)
{
cout << "调用func(int a,int b)函数" << endl;
}
int main()
{
func();
func(20);
func(3.14);
func(5, 3.12);
func(3.25, 6);
func(5, 30);

system("pause");
return 0;
}

从零开始学编程---C++篇(2)_数据_10

3.3.2函数重载注意事项
  • 引用作为重载条件
  • 函数重载碰到函数默认参数
#include<iostream>
using namespace std;
void func(int& a)
{
cout << "调用func(int& a)" << endl;
}
void func(const int& a)
{
cout << "调用func(const int& a)" << endl;
}
void func2(int a)
{
cout << "调用func(int a)" << endl;
}void func2(int a,int b=10)
{
cout << "调用func(int a,int b=10)" << endl;
}
int main()
{
int a = 0;
func(a);
func(10);
func2(10, 10);
func2(10);//编译器无法区分是哪一个函数
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_ios_11

屏蔽24行代码后

从零开始学编程---C++篇(2)_数据_12

4.类和对象

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

C++认为万事万物都皆为对象,对象上有其属性和行为

具有相同性质的 == 对象 == ,我们可以抽象称为 == 类 == ,人属于人类,车属于车类

4.1 封装

4.1.1 封装的意义

封装是C++面向对象三大特性之一

封装的意义:

  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制

封装意义一:

在设计类的时候,属性和行为写在一起,表现事物

语法:​class 类名{ 访问权限: 属性 / 行为 };​

示例1:设计一个圆类,求圆的周长

#include<iostream>
using namespace std;
const double PI=3.14;
class Circle
{
public:
Circle();
~Circle();
double caculatezc()
{
return 2 * PI * m_r;
}

int m_r;
};

Circle::Circle()
{
}

Circle::~Circle()
{
}
int main()
{
Circle c1;
c1.m_r = 10;
double ret = c1.caculatezc();
cout << "ret=" << ret << endl;
system("pause");
return 0;
}


从零开始学编程---C++篇(2)_数据_13

示例2: 设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号

#include<iostream>
#include<string>
using namespace std;
class student
{
public:
student();
~student();
void SetName(string name)
{
m_name = name;
}
void SetId(string id)
{
m_id = id;
}
void ShowStudent()
{
cout << "姓名是" << m_name << "学号是" << m_id << endl;
}

public:
string m_name;
string m_id;

};

student::student()
{
}

student::~student()
{
}
int main()
{
student stu;
stu.SetName("zhangsan");
stu.SetId("250");
stu.ShowStudent();

system("pause");
return 0;
}

从零开始学编程---C++篇(2)_#include_14

封装意义二:

类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有三种:

  1. public 公共权限
  2. protected 保护权限
  3. private 私有权限

三种权限

公共权限  public     类内可以访问  类外可以访问

保护权限  protected  类内可以访问  类外不可以访问

私有权限  private    类内可以访问  类外不可以访问

#include<iostream>
#include<string>
using namespace std;
class person
{
public:
person();
~person();
//姓名,公共权限
public:
string m_name;
//汽车,保护权限
protected:
string m_car;
//银行卡密码,隐私权限
private:
string m_password;
};

person::person()
{
}

person::~person()
{
}
int main()
{
person p;
p.m_name = "zhangsan";
/*p.m_car;
p.m_password;*/// 这两个成员变量访问不到
system("pause");
return 0;
}
4.4.2 struct和class的区别

在C++中 struct和class唯一的区别就在于 默认的访问权限不同

区别:

  • struct 默认权限为公共
  • class 默认权限为私有
#include<iostream>
using namespace std;
class C1
{
int m_A; //默认是私有权限
};

struct C2
{
int m_A; //默认是公共权限
};

int main() {

C1 c1;
c1.m_A = 10; //错误,访问权限是私有

C2 c2;
c2.m_A = 10; //正确,访问权限是公共

system("pause");

return 0;
}
4.1.3 成员属性设置为私有

优点1:将所有成员属性设置为私有,可以自己控制读写权限

优点2:对于写权限,我们可以检测数据的有效性

#include<iostream>
using namespace std;
//定义一个人类,有名字,年龄,情人
class person
{
public:
person();
~person();
//姓名可读可写
//设置姓名
void SetName(string name)
{
m_name = name;
}
//读取姓名
void ShowName()
{
cout << "姓名是" << m_name << endl;
}
//设置年龄
void Setage(int a)
{
if (a<0||a>150)
{
cout << "输入错误" << endl;
return;
}
m_age = a;
}
//展示年龄
void Showage()
{
cout << "年龄是" << m_age << endl;
}
//设置情人
void SetLover(string lo)
{
m_lover = lo;
}

private:
string m_name;
int m_age;
string m_lover;
};

person::person()
{
}

person::~person()
{
}
int main()
{
person p;
p.SetName("zhangsan");
p.ShowName();
p.Setage(26);
p.Showage();
p.SetLover("xiaofang");
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_数据_15

练习案例:设计立方体类

设计立方体类(Cube)

求出立方体的面积和体积

分别用全局函数和成员函数判断两个立方体是否相等。

#include<iostream>
using namespace std;
class Cube
{
public:
Cube();
~Cube();
//设置立方体的三个参数
void SetCube(int a, int b, int c)
{
m_l = a;
m_w = b;
m_h = c;
}
//求立方体的表面积
int mianji()
{
return 2*(m_l * m_w + m_l * m_h + m_w * m_h);
}
//求立方体的体积
int tiji()
{
return m_h * m_l * m_w;
}
bool IsSame(Cube cu)
{
if (m_l == cu.m_l && m_h == cu.m_h && m_w == cu.m_w)
{
return true;
}
else
return false;
}
private:
int m_l;
int m_w;
int m_h;
};

Cube::Cube()
{
}

Cube::~Cube()
{
}
int main()
{
Cube c;
c.SetCube(1,2,3);
int ret=c.mianji();
int ret2 = c.tiji();
cout << "立方体的面积是" << ret << endl;
cout << "立方体的体积是" << ret2 << endl;
Cube cu;
cu.SetCube(1, 2, 4);
bool flag=c.IsSame(cu);
if (flag)
{
cout << "这两个立方体相等" << endl;
}
else
{
cout << "这两个立方体不相等" << endl;
}
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_数据_16

4.2 对象的初始化和清理

  • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
  • C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。
4.2.1 构造函数和析构函数

对象的初始化和清理也是两个非常重要的安全问题

一个对象或者变量没有初始状态,对其使用后果是未知

同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题

c++利用了构造函数析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。

对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供

编译器提供的构造函数和析构函数是空实现。

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

构造函数语法:​类名(){}​

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法:​~类名(){}​

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号 ~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include<iostream>
using namespace std;
class Person
{
public:
Person();
~Person();

private:

};

Person::Person()
{
cout << "调用构造函数Person()" << endl;
}

Person::~Person()
{
cout << "调用析构函数~Person()" << endl;
}
void test()
{
Person p1;
}
int main()
{
test();
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_ios_17

4.2.2 构造函数的分类及调用

两种分类方式:

按参数分为: 有参构造和无参构造

按类型分为: 普通构造和拷贝构造

三种调用方式:

括号法

显示法

隐式转换法

#include<iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person();
//有参构造函数
Person(int a);
//拷贝构造函数
Person(const Person& a);
//析构函数
~Person();

private:
int m_age;
};

Person::Person()
{
cout << "调用无参构造函数Person()" << endl;
}
Person:: Person(int a)
{
m_age = a;
cout << "调用有参构造函数Person(int a)" << endl;
}
Person::Person(const Person& p)
{
m_age = p.m_age;
cout << "调用拷贝构造函数Person(const Person& p)" << endl;
}
Person::~Person()
{
cout << "调用析构函数~Person()" << endl;
}
void test01()
{
/*Person p;*/
Person p(10);
Person p2(p);
}
void test02()
{

}
int main()
{
test01();

system("pause");
return 0;
}

从零开始学编程---C++篇(2)_#include_18

#include<iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person();
//有参构造函数
Person(int a);
//拷贝构造函数
Person(const Person& a);
//析构函数
~Person();

private:
int m_age;
};

Person::Person()
{
cout << "调用无参构造函数Person()" << endl;
}
Person:: Person(int a)
{
m_age = a;
cout << "调用有参构造函数Person(int a)" << endl;
}
Person::Person(const Person& p)
{
m_age = p.m_age;
cout << "调用拷贝构造函数Person(const Person& p)" << endl;
}
Person::~Person()
{
cout << "调用析构函数~Person()" << endl;
}
//void test01()
//{
// /*Person p;*/
// Person p(10);
// Person p2(p);
//}
//调用构造函数的方法
void test02()
{
//1.括号法
Person p1;//无参构造时后面不能加(),编译器无法识别是构造函数还是其他函数
Person p2(12);
Person p3(p1);
//2.显式法
Person p4 = Person(10);
Person p5 = Person(p4);
Person p6 = Person ();
//3.隐式转换法
Person p7 = 10;
Person p8 = p7;
//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
//Person p5(p4);
}
int main()
{
test02();

system("pause");
return 0;
}

从零开始学编程---C++篇(2)_ios_19

4.2.3 拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象
#include<iostream>
using namespace std;
class Person
{
public:
Person();
Person(int a);
Person(const Person& p);
~Person();

private:
int age;
};

Person::Person()
{
cout << "调用无参构造函数Person()" << endl;
}
Person::Person(int a)
{
age = a;
cout << "调用有参构造函数Person(int a)" << endl;
}
Person:: Person(const Person& p)
{
age = p.age;
cout << "调用拷贝函数Person(const Person& p)" << endl;
}
Person::~Person()
{
cout << "调用析构函数Person()" << endl;
}
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p(100);
Person p1 = p;
Person p2(p);
//Person newman3;
//newman3 = man; //不是调用拷贝构造函数,赋值操作
}
//2.值传递的方式给函数传值
//相当于Person p4=p3
void dowork(Person p4)
{

}
void test02()
{
Person p3;
dowork(p3);
}
//3. 以值方式返回局部对象
Person dowork02()
{
Person p5;
cout << (int*)&p5 << endl;
return p5;
}
void test03()
{
Person p6 = dowork02();
cout << (int*)&p6 << endl;
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_ios_20

4.2.4 构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数

1.默认构造函数(无参,函数体为空)

2.默认析构函数(无参,函数体为空)

3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
#include<iostream>
using namespace std;
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};

void test01()
{
Person p1(18);
//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
Person p2(p1);

cout << "p2的年龄为: " << p2.age << endl;
}

void test02()
{
//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
Person p1; //此时如果用户自己没有提供默认构造,会出错
Person p2(10); //用户提供的有参
Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供

//如果用户提供拷贝构造,编译器不会提供其他构造函数
Person p4; //此时如果用户自己没有提供默认构造,会出错
Person p5(10); //此时如果用户自己没有提供有参,会出错
Person p6(p5); //用户自己提供拷贝构造
}

int main() {

test01();

system("pause");

return 0;
}
4.2.5 深拷贝与浅拷贝

深浅拷贝是面试经典问题,也是常见的一个坑

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

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

#include<iostream>
using namespace std;
class Person
{
public:
Person();
//有参构造函数
Person(int age,int height);
//拷贝构造函数
Person(const Person& p);

~Person();

public:
int m_age;
int* m_height;
};

Person::Person()
{
cout << "调用无参构造函数Person()" << endl;
}
Person::Person(int age, int height)
{
cout << "调用有参构造函数Person(int age, int height)" << endl;
m_age = age;
m_height = new int(height);
}
Person::Person(const Person& p)
{
cout << "调用拷贝构造函数Person(const Person& p)" << endl;
m_age = p.m_age;
m_height = p.m_height;//编译器默认的拷贝构造函数的代码
}
Person::~Person()
{
cout << "调用析构函数~Person()" << endl;
if (m_height!=NULL)
{
delete m_height;
}
}
void test01()
{
Person p1(10, 180);
Person p2(p1);
cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;

cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}
int main()
{
test01();
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_ios_21

这是因为有参构造函数和拷贝构造函数中都有一个指针指向同一块堆区开辟的内存“new int(*p.m_height)”,为了,在创建pi1和p2的时候会调用两次析构函数,那么这块内存就会释放两次,第二次就是非法访问内存,解决方法是第二次用原有数据重新开辟一块堆区上的内存

//原代码中的29-34行即拷贝构造函数改为
Person::Person(const Person& p)
{
cout<<"调用拷贝构造函数Person(const Person& p)"<<endl;
m_age=p.m_age;
m_height=new int(*p.m_height);
}

总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

4.2.6 初始化列表

作用:

C++提供了初始化列表语法,用来初始化属性

语法:​构造函数():属性1(值1),属性2(值2)... {}​

#include<iostream>
using namespace std;
class Person
{
public:
Person();
~Person();
Person(int a, int b, int c) :m_a(a), m_b(b), m_c(c)
{

}
void print()
{
cout << "m_a=" << m_a << endl;
cout << "m_b=" << m_b << endl;
cout << "m_c=" << m_c << endl;
}
private:
int m_a;
int m_b;
int m_c;
};

Person::Person()
{
}

Person::~Person()
{
}
int main()
{
Person p(1, 2, 3);
p.print();
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_#include_22

4.2.7 类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员

class A {}
class B
{
A a;
}

B类中有对象A作为成员,A为对象成员

那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?

#include<iostream>
#include<string>
using namespace std;
class Phone
{
public:
Phone();
~Phone();

public:
string m_phonename;
};

Phone::Phone()
{
cout << "调用无参构造函数Phone()" << endl;
}

Phone::~Phone()
{
cout << "调用析构函数~Phone()" << endl;
}
class Person
{
public:
Person();
~Person();

public:
string m_name;
Phone m_phone;
};

Person::Person()
{
cout << "调用无参构造函数Person()" << endl;
}

Person::~Person()
{
cout << "调用析构函数Person()" << endl;
}
void test01()
{
Person p;
}
int main()
{
test01();
system("pause");
return 0;
}

从零开始学编程---C++篇(2)_数据_23

当类中成员是其他类对象时,我们称该成员为 对象成员

构造的顺序是 :先调用对象成员的构造,再调用本类构造

析构顺序与构造相反

4.2.8 静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

静态成员分为:

  • 静态成员变量
  • 所有对象共享同一份数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化
  • 静态成员函数
  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量

示例1:静态成员变量

#include<iostream>
using namespace std;
class Person
{
public:
Person();
~Person();
static int m_a;
private:
static int m_b;
};
int Person::m_a = 10;
int Person::m_b = 10;
Person::Person()
{
}

Person::~Person()
{
}
void test01()
{
//通过对象访问静态成员变量
Person p1;
p1.m_a = 100;
cout << "p1.m_a=" << p1.m_a << endl;
Person p2;
p2.m_a = 200;
cout << "p1.m_a=" << p1.m_a << endl;
cout << "p2.m_a=" << p2.m_a << endl;
//通过类访问静态成员变量
cout << "m_a=" << Person::m_a << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
//静态成员变量特点:
//1 在编译阶段分配内存
//2 类内声明,类外初始化
//3 所有对象共享同一份数据

从零开始学编程---C++篇(2)_数据_24

示例2:静态成员函数

#include<iostream>
using namespace std;
class Person
{

public:

//静态成员函数特点:
//1 程序共享一个函数
//2 静态成员函数只能访问静态成员变量

static void func()
{
cout << "func调用" << endl;
m_A = 100;
//m_B = 100; //错误,不可以访问非静态成员变量
}

static int m_A; //静态成员变量
int m_B; //
private:

//静态成员函数也是有访问权限的
static void func2()
{
cout << "func2调用" << endl;
}
};
int Person::m_A = 10;


void test01()
{
//静态成员变量两种访问方式

//1、通过对象
Person p1;
p1.func();

//2、通过类名
Person::func();


//Person::func2(); //私有权限访问不到
}

int main() {

test01();

system("pause");

return 0;
}


未完待续......