在堆上申请数据与栈上申请数据的区别:
1.堆上的申请的数据只有在程序运行完才会被释放。
2.堆上的数据也可以由程序员用delete释放
3.栈上的数据在函数执行完就会被释放。
#include<iostream>
using namespace std;
int* creat()
{
int* p = new int(10);
/*在堆区开辟一块内存,存放整型数据10。
注意:new返回的是一块内存地址, 并且new
开辟的的内存空间不会杯自动回收,除非程
序运行结束或者程序员手动回收*/
return p;
}
int* creat1()
{
int p = 10;
return &p;
}
void test1()
{
int* p = creat();
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
cout << "堆上申请的数据" << endl;
delete p;
/*cout << *p << endl;*/
}
void test()
{
int* p = creat1();
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
cout << "栈上申请的数据" << endl;
}
int main()
{
test();
test1();
return 0;
}
输出结果:
10
1849460848
1849460848
栈上申请的数据
10
10
10
10
堆上申请的数据
如何在堆上申请一个数组
#include<iostream>
using namespace std;
void creart_arr()
{
int* p = new int[10];
for (int i = 0; i < 10; i++)
{
p[i] = i;
}
for (int i = 0; i < 10; i++)
{
cout << p[i] << endl;
}
delete[]p;//释放堆区申请的数组空间,其中[]代表释放的数组,p代码指向数组的空间
}
int main()
{
creart_arr();
return 0;
}
0
1
2
3
4
5
6
7
8
9
1.引用
引用的本质是给变量取别名,在C++内部实现的是一个指针常量
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
b = 20;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
return 0;
}
a=10
b=10
a=20
b=20
引用的注意事项:1.引用必须初始化
2.引用在初始化之后,不可以再发生改变
参数传递的方式与区别
1.值传递:形参没有修饰到实参
#include<iostream>
using namespace std;
void swap(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
cout << "*******swap func*******" << endl;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
}
int main()
{
int a = 10,b=20;
swap(a, b);
cout << "************* main func *************" << endl;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
return 0;
}
2.地址传递
#include<iostream>
using namespace std;
void swap(int* a, int* b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
cout << "*******swap func*******" << endl;
cout << "a=" << *a << endl;
cout << "b=" << *b << endl;
}
int main()
{
int a = 10,b=20;
swap(&a, &b);
cout << "************* main func *************" << endl;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
return 0;
}
3.引用传递
#include<iostream>
using namespace std;
void swap(int& a, int& b)
{
int temp;
temp = a;
a = b;
b = temp;
cout << "*******swap func*******" << endl;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
}
int main()
{
int a = 10,b=20;
swap(a, b);
cout << "************* main func *************" << endl;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
return 0;
}
总结:通过引用和地址传递的效果是一样的,但是引用传递的方式更加的简单
引用作为函数的返回值
1.不要返回局部变量的引用
test() 函数执行完,局部变量a的内存被释放,所以打印出来的a是一个随机值
#include<iostream>
using namespace std;
int& test()
{
int a = 10;
return a;
}
int main()
{
int& temp = test();
cout << "temp:" << temp << endl;
cout << "temp:" << temp << endl;
return 0;
}
2.引用作为函数的返回值,在函数调用可以作为函数的左值
temp是a的别名,test()返回值是a的引用,test()=100,相当于a=100;
#include<iostream>
using namespace std;
int& test()
{
static int a = 10;
return a;
}
int main()
{
int& temp = test();
cout << "temp:" << temp << endl;
test()=100;
cout << "temp:" << temp << endl;
return 0;
}
引用的本质
引用的本质:在C++内部实现的一个指针常量(指针的指向不可以改变,但是指向的内容可以改变,左数右址,以*为基准,const在 * 的左边,指向的内容不可以改变,const在 * 的右边,指针的指向不可以改变)
#include<iostream>
using namespace std;
//int* const ref=&a;
int test(int& a)
{
a = 20;
//*ref = a;
return a;
}
int main()
{
int a = 10;
test(a);
int& temp = a;
cout << "a=" << a << endl;
cout << "temp=" << temp << endl;
return 0;
}
常量引用
#include<iostream>
using namespace std;
int main()
{
//int& ref = 10;//引用本身需要一个合法的内存空间
const int& ref = 10;//编译器做了优化:int temp=10;const int& ref=temp;
return 0;
}
#include<iostream>
using namespace std;
void ShowValue(const int& a)//const修饰之后,可以防止值被修改
{
//a = 100;
cout << "a=" << a << endl;
}
int main()
{
int a = 10;
ShowValue(a);
return 0;
}
2.函数的提高
函数的默认参数
#include<iostream>
using namespace std;
int func(int a,int b,int c)
{
return a + b + c;
}
int main()
{
cout << func(10, 20, 30) << endl;
return 0;
}
#include<iostream>
using namespace std;
int func(int a,int b=20,int c=30)
{
return a + b + c;
}
int main()
{
cout << func(10, 30) << endl;
return 0;
}
函数提供了默认参数,调用函数时,如果提供参数,则使用提供的参数,否则使用默认的参数
注意事项:1.如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值。
2.如果函数声明已经右默认参数,函数实现就不要再有默认函数参数
#include<iostream>
using namespace std;
int func(int a, int b = 20, int c = 30);
int func(int a,int b=20,int c=30)
{
return a + b + c;
}
int main()
{
cout << func(10, 30) << endl;
return 0;
}
函数的占位参数
#include<iostream>
using namespace std;
void func(int a,int )
{
cout << "this is func" << endl;
}
int main()
{
func(10, 10);
return 0;
}
函数的占位参数也可以有默认值
#include<iostream>
using namespace std;
void func(int a,int =10)
{
cout << "this is func" << endl;
}
int main()
{
func(10);
return 0;
}
函数的重载
函数重载的条件:1.同一个作用域下
2.函数的名称相同
3.函数的类型不同或者个数不同或者顺序不同(参数列表不同)
#include<iostream>
using namespace std;
void func()
{
cout << "func()" << endl;
}
void func(int a)
{
cout << "func(int a)" << endl;
}
void func(int a,double b)
{
cout << "func(int a,double b)" << endl;
}
void func(double b,int a)
{
cout << "func(double b,int a)" << endl;
}
int main()
{
func();
func(10);
func(10, 3.14);
func(3.14, 10);
return 0;
}
函数的返回值不可以作为函数的重载条件
#include<iostream>
using namespace std;
void func()
{
cout << "func()" << endl;
}
int func()
{
cout << "int func()" << endl;
return 0;
}
int main()
{
func();
return 0;
}
函数重载的注意事项
1.引用作为函数重载的条件
#include<iostream>
using namespace std;
void func(int& a)//int& a=10;不合法!
{
cout << "func(int& a)" << endl;
}
void func(const int& a)
{
cout << "func(const int a)" << endl;
}
int main()
{
int a = 5;
func(a);
cout << "-----------------" << endl;
func(10);
return 0;
}
2.函数重载碰到默认参数
#include<iostream>
using namespace std;
void func(int a,int b=10)
{
cout << "func(int a,int b=10)" << endl;
}
void func(int a)
{
cout << "func(int a)" << endl;
}
int main()
{
func(10);
return 0;
}
如果有函数重载,就不要带默认参数,避免二义性。
#include<iostream>
using namespace std;
void func(int a,int b=10)
{
cout << "func(int a,int b=10)" << endl;
}
void func(int a)
{
cout << "func(int a)" << endl;
}
int main()
{
func(10,1);
return 0;
}
3.类和对象
C++面向对象的三大特性:封装、继承、多态
https://gitee.com/codezhengmeng_0/cpp.git
封装
封装的意义:属性和行为在一起而共同表现出的事物
#include<iostream>
using namespace std;
double const PI = 3.14;
//创建一个类
class Circle
{
//设置访问权限
public:
//设置属性
int circle_r;
//设置行为,一般类的行为都体现在函数上
double circle_zc()
{
return circle_r * PI * 2;
}
};
int main()
{
//实例化,通过类创建一个对象
Circle cir;
cir.circle_r = 10;
cout << "圆的周长:" << cir.circle_zc() << endl;
return 0;
}
#include<iostream>
using namespace std;
//创建一个类
class Student
{
//设置访问权限
public:
//设置属性
string Stu_name;
int Stu_ID;
//设置行为,一般类的行为都体现在函数上
void show_info()
{
cout << "姓名:" << Stu_name << endl << "学号:" << Stu_ID << endl;
}
};
int main()
{
//实例化,通过类创建一个对象
Student s1;
s1.Stu_name = "吴彦祖";
s1.Stu_ID = 1;
s1.show_info();
return 0;
}
除上述给属性赋值方式之外,也可以利用类型的行为为类的属性赋值。
#include<iostream>
using namespace std;
//创建一个类
class Student
{
//设置访问权限
public:
//设置属性
string Stu_name;
int Stu_ID;
//设置行为,一般类的行为都体现在函数上
void set_name(string name)
{
Stu_name = name;
}
void set_ID(int ID)
{
Stu_ID = ID;
}
void show_info()
{
cout << "姓名:" << Stu_name << endl << "学号:" << Stu_ID << endl;
}
};
int main()
{
//实例化,通过类创建一个对象
Student s1;
s1.set_name("陈冠希");
s1.set_ID(1);
s1.show_info();
return 0;
}
交流中,类中的属性和行为统称为成员。属性:成员属性、成员变量;行为:成员函数、成员方法
类在设计时,可以把属性和行为放在不同的权限下,加以控制
1.public:公共权限;类内可以访问,类外可以访问
2.protected:保护权限;类内可以访问,类外不可以访问
3.private:私有权限;类内可以访问,类外不可以访问(在继承中会体现与protected的区别)
#include<iostream>
using namespace std;
//创建一个类
class Person
{
//设置访问权限
public:
//设置属性
string Per_name;
protected:
string Per_car;
private:
int Per_ID;
public:
void func()
{
Per_name = "yanzu";
Per_car = "Porsche";
Per_ID = 1;
}
};
int main()
{
//实例化,通过类创建一个对象
Person s;
s.Per_name;
s.Per_car;
s.func();
s.Per_ID;
return 0;
}
修改函数的访问权限
#include<iostream>
using namespace std;
//创建一个类
class Person
{
//设置访问权限
public:
//设置属性
string Per_name;
protected:
string Per_car;
private:
int Per_ID;
private:
void func()
{
Per_name = "yanzu";
Per_car = "Porsche";
Per_ID = 1;
}
};
int main()
{
//实例化,通过类创建一个对象
Person s;
s.Per_name;
s.Per_car;
s.func();
s.Per_ID;
return 0;
}
struct和class区别
区别:主要体现在访问权限上。struct权限为公共,class默认权限为私有。
#include<iostream>
using namespace std;
//创建一个类
class P
{
int a;
};
int main()
{
//实例化,通过类创建一个对象
P s;
s.a = 10;
return 0;
}
#include<iostream>
using namespace std;
struct P
{
int a;
};
int main()
{
P s;
s.a = 10;
cout << s.a << endl;
return 0;
}
成员属性设置为私有
1.控制成员的读写权限
姓名:可读可写
年龄:只读
偶像:不可读
#include<iostream>
using namespace std;
class Person
{
public:
void set_name(string name)
{
m_name = name;
}
string get_name()
{
return m_name;
}
int get_age()
{
return age;
}
void set_idol(string idol)
{
m_idol = idol;
}
private:
string m_name;
int age=27;
string m_idol;
};
int main()
{
Person s1;
s1.set_name("a_zu");
cout << "name:" << s1.get_name() << endl;
cout << "age:" << s1.get_age() << endl;
s1.set_idol("赵雷");
return 0;
}
2.对于写检测数据的有效性
#include<iostream>
using namespace std;
class Person
{
public:
void set_age(int age)
{
if (age > 100 || age <= 0)
{
cout << "年龄超出规定范围!" << endl;//若年龄超出规定范围,输出默认值
return;
}
m_age = age;
}
int get_age()
{
return m_age;
}
private:
int m_age=27;
};
int main()
{
Person s1;
s1.set_age(200);
cout << s1.get_age() << endl;
return 0;
}
封装案例
1.案例1
要求:1.计算立方体体积和面积
2.分别用全局函数和成员函数判定两个立方体是否一样
#include<iostream>
using namespace std;
class Cube
{
public:
//利用类的行为设置类的属性
void set_m_L(int l)
{
m_L = l;
}
void set_m_W(int w)
{
m_W = w;
}
void set_m_H(int h)
{
m_H = h;
}
int get_m_L()
{
return m_L;
}
int get_m_W()
{
return m_W;
}
int get_m_H()
{
return m_H;
}
bool is_same_by_calss(Cube& c)
{
if (m_L == c.get_m_L() && m_W == c.get_m_W() && m_H == c.get_m_H())
{
return true;
}
return false;
}
int calculate_s()
{
return 2 * m_L * m_W + 2 * m_L * m_H + 2 * m_W * m_H;
}
int calculate_v()
{
return m_L * m_W * m_H;
}
private:
int m_L;
int m_W;
int m_H;
};
bool Cube_is_same(Cube& c1, Cube& c2)
{
if (c1.get_m_L() == c2.get_m_L() && c1.get_m_W() == c2.get_m_W() && c1.get_m_H() == c2.get_m_H())
{
return true;
}
return false;
}
int main()
{
Cube c1;
Cube c2;
c1.set_m_L(10);
c1.set_m_W(10);
c1.set_m_H(10);
cout << c1.get_m_L() << endl;
cout << c1.get_m_W() << endl;
cout << c1.get_m_H() << endl;
cout << "立方体面积:" << c1.calculate_s() << endl;
cout << "立方体体积:" << c1.calculate_v() << endl;
c2.set_m_L(10);
c2.set_m_W(10);
c2.set_m_H(10);
bool ret1= Cube_is_same(c1, c2);
if (ret1)
{
cout << "is same by Global function" << endl;
}
else
{
cout << "unlike by Global function" << endl;
}
bool ret2 = c2.is_same_by_calss(c1);
if (ret2)
{
cout << "is same by Class member function" << endl;
}
else
{
cout << "unlike by Class member function" << endl;
}
return 0;
}
2.案例2
要求:判断点与圆的关系(核心:在一个类中,可以让另一个类作为本类中的成员)
#include<iostream>
using namespace std;
class Point
{
public:
void set_m_X(int x)
{
m_X = x;
}
int get_m_X()
{
return m_X;
}
void set_m_Y(int y)
{
m_Y = y;
}
int get_m_Y()
{
return m_Y;
}
private:
int m_X;
int m_Y;
};
class Circle
{
public:
//利用类的行为设置类的属性
void set_m_R(int r)
{
m_R = r;
}
int get_m_R()
{
return m_R;
}
void set_p(Point& c)
{
p = c;
}
Point get_p()
{
return p;
}
private:
int m_R;
Point p;//其他类作为本类的成员
};
void local(Circle& c,Point& p)
{
int r = c.get_m_R();
int x = c.get_p().get_m_X();
int y = c.get_p().get_m_Y();
int p_x = p.get_m_X();
int p_y = p.get_m_Y();
int r_2 = r * r;
int d_2 = (x - p_x) * (x - p_x) + (y - p_y) * (y - p_y);
if (r_2 == d_2)
{
cout << "在圆上" << endl;
}
else if (r_2 < d_2) {
cout << "在圆外" << endl;
}
else {
cout << "在圆内" << endl;
}
}
int main()
{
Circle c;
Point p1,p2;
p1.set_m_X(0);
p1.set_m_Y(0);
c.set_m_R(10);
c.set_p(p1);
p2.set_m_X(9);
p2.set_m_Y(0);
local(c,p2);
return 0;
}
对象的初始化和清理
C++中的面向对象来源于生活,每个对象都会有初始设置以及对象销毁前的清理数据的设置
构造函数和析构函数
对象的初始化和清理是非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果位置
使用完一个对象,没有及时清理,也会造成一定的安全问题
C++利用构造函数和析构函数解决上述问题,这两个函数会被编译器自动调用,完成对象的初始化和清理的工作。对象的初始化和清理是编译器强制要求程序员要做的事情,因此如果我们不提供构造和析构,编译器会提供。(编译器提供的构造函数和析构函数是空实现)
构造函数:主要作用在于创建对象时为对象的属性赋值,构造函数由编译器自动调用,无需手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
1.构造函数:没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动构造函数,无需手动调用,而且只会调用一次
#include<iostream>
using namespace std;
// 1.构造函数:函数名与类型名相同
// 2.没有返回值,不写void
// 3.不需要手动调用,由程序自动调用,并且只调用一次
// 4.构造函数可以有参数,可以发生重载
class Person
{
public:
Person()
{
cout << "Person()的构造函数" << endl;
}
// 若不写构造函数,编译器默认构造函数为:
// Person()
// {
// }
};
void test01()
{
Person p;
}
int main()
{
test01();
return 0;
}
析构函数语法:~类名(){}
1.析构函数:没有返回值也不写void
2.函数名称与类名相同,在名称前加上符号~
3.析构函数不可以有参数,因此不可以发生重载
4.程序在对象销毁前,会自动调用析构,无需手动调用,并且只调用一次。
#include<iostream>
using namespace std;
// 1.析构函数:函数名与类型名相同,名称前加上~
// 2.没有返回值,不写void
// 3.不需要手动调用,由程序自动调用,并且只调用一次
// 4.构造函数不可以有参数,不能发生重载
class Person
{
public:
~Person()
{
cout << "Person()的析构函数" << endl;
}
// 若不写析构函数,编译器默认构造析构为:
// ~Person()
// {
// }
};
void test01()
{
Person p;
}
int main()
{
test01();
return 0;
}
构造函数的分类和调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
括号法
#include<iostream>
using namespace std;
class Person
{
public:
// 构造函数的分类:
// 1.按参数进行构造:有参构造和无参构造
// 2.按类型进行构造:拷贝构造
Person()
{
cout << "Pesson()的构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "Pesson()的有参构造函数" << endl;
}
Person(const Person &p)
{
// 将传入对象身上的所以属性传入到我的身上
age = p.age;
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
cout << "Person()的析构函数" << endl;
}
int age;
};
// 调用
void test01()
{
// 1.括号法
Person p1; //默认构造函数的调用
Person p2(10); //有参构造函数的调用
Person p3(p2); // 拷贝构造函数的调用
cout << "p2的年龄:" << p2.age << endl;
cout << "p3的年龄:" << p3.age << endl;
// 2.显示法
// 3.隐式转换法
}
int main()
{
test01();
return 0;
}
显式法
#include<iostream>
using namespace std;
class Person
{
public:
// 构造函数的分类:
// 1.按参数进行构造:有参构造和无参构造
// 2.按类型进行构造:拷贝构造
Person()
{
cout << "Pesson()的构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "Pesson()的有参构造函数" << endl;
}
Person(const Person &p)
{
// 将传入对象身上的所以属性传入到我的身上
age = p.age;
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
cout << "Person()的析构函数" << endl;
}
int age;
};
// 调用
void test01()
{
// 1.括号法
// Person p1; //默认构造函数的调用
// Person p2(10); //有参构造函数的调用
// Person p3(p2); // 拷贝构造函数的调用
// cout << "p2的年龄:" << p2.age << endl;
// cout << "p3的年龄:" << p3.age << endl;
// 2.显示法
Person p4;
Person p5 = Person(10); //有参构造
Person p6 = Person(p5); //拷贝构造
cout << "p2的年龄:" << p5.age << endl;
cout << "p3的年龄:" << p6.age << endl;
// 注意person(10)也称为匿名对象,调用完,person(10)会被立即释放
// 3.隐式转换法
}
int main()
{
test01();
return 0;
}
注意事项1:person(10)也称为匿名对象,调用完,person(10)会被立即释放
#include<iostream>
using namespace std;
class Person
{
public:
// 构造函数的分类:
// 1.按参数进行构造:有参构造和无参构造
// 2.按类型进行构造:拷贝构造
Person()
{
cout << "Pesson()的构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "Pesson()的有参构造函数" << endl;
}
Person(const Person &p)
{
// 将传入对象身上的所以属性传入到我的身上
age = p.age;
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
cout << "Person()的析构函数" << endl;
}
int age;
};
// 调用
void test01()
{
// 注意person(10)也称为匿名对象,调用完,person(10)会被立即释放
Person(10);
cout << "hello! yanzu" << endl;
// 3.隐式转换法
}
int main()
{
test01();
return 0;
}
注意事项2:不要用拷贝构造函数初始化匿名对象,编译器会认为:Person(p6)==Person p6
#include<iostream>
using namespace std;
class Person
{
public:
// 构造函数的分类:
// 1.按参数进行构造:有参构造和无参构造
// 2.按类型进行构造:拷贝构造
Person()
{
cout << "Pesson()的构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "Pesson()的有参构造函数" << endl;
}
Person(const Person &p)
{
// 将传入对象身上的所以属性传入到我的身上
age = p.age;
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
cout << "Person()的析构函数" << endl;
}
int age;
};
// 调用
void test01()
{
Person(p6);
}
int main()
{
test01();
return 0;
}
隐式转换法
#include<iostream>
using namespace std;
class Person
{
public:
// 构造函数的分类:
// 1.按参数进行构造:有参构造和无参构造
// 2.按类型进行构造:拷贝构造
Person()
{
cout << "Pesson()的构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "Pesson()的有参构造函数" << endl;
}
Person(const Person &p)
{
// 将传入对象身上的所以属性传入到我的身上
age = p.age;
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
cout << "Person()的析构函数" << endl;
}
int age;
};
// 调用
void test01()
{
// // 1.括号法
// // 2.显示法
// 3.隐式转换法
Person p7 = 10; //有参构造
Person p8 = p7; //拷贝构造
}
int main()
{
test01();
return 0;
}
注意事项:调用默认构造函数的时候,不要加(),因为编译器会认为这是函数的声明!
无输出,不是在构造对象
拷贝构造函数的调用时机
1.使用一个已经创建完毕的对象初始化一个新对象
#include<iostream>
using namespace std;
class Person
{
public:
// 构造函数的分类:
// 1.按参数进行构造:有参构造和无参构造
// 2.按类型进行构造:拷贝构造
Person()
{
cout << "Pesson()的构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "Pesson()的有参构造函数" << endl;
}
Person(const Person &p)
{
// 将传入对象身上的所以属性传入到我的身上
age = p.age;
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
cout << "Person()的析构函数" << endl;
}
int age;
};
// 调用
void test01()
{
Person p1(10);
Person p2(p1);
}
int main()
{
test01();
return 0;
}
2.值传递的方式给函数参数传值
#include<iostream>
using namespace std;
class Person
{
public:
// 构造函数的分类:
// 1.按参数进行构造:有参构造和无参构造
// 2.按类型进行构造:拷贝构造
Person()
{
cout << "Pesson()的构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "Pesson()的有参构造函数" << endl;
}
Person(const Person &p)
{
// 将传入对象身上的所以属性传入到我的身上
age = p.age;
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
cout << "Person()的析构函数" << endl;
}
int age;
};
void dowork(Person p)
{
}
void test02()
{
// 以值传递的方式给函数参数传值
Person p;
dowork(p);
}
int main()
{
test02();
return 0;
}
3.以值方式返回局部对象
#include<iostream>
using namespace std;
class Person
{
public:
// 构造函数的分类:
// 1.按参数进行构造:有参构造和无参构造
// 2.按类型进行构造:拷贝构造
Person()
{
cout << "Pesson()的构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "Pesson()的有参构造函数" << endl;
}
Person(const Person &p)
{
// 将传入对象身上的所以属性传入到我的身上
age = p.age;
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
cout << "Person()的析构函数" << endl;
}
int age;
};
void test01()
{
// 使用一个已经初始化完毕的对象创建一个新对象
Person p1(10);
Person p2(p1);
}
void dowork(Person p)
{
}
void test02()
{
// 以值传递的方式给函数参数传值
Person p;
dowork(p);
}
Person dowork2()
{
Person p1;
cout << &p1 << endl;
return p1;
}
void test03()
{
// 值得方式返回局部对象
Person p2 = dowork2();
cout << &p2 << endl;
}
int main()
{
test03();
return 0;
}
vscode结果如下,两地址一样,理论上不一样,应该是编译器优化的结果。
构造函数的调用规则
默认情况下,C++编译器至少给一个类添加3个函数
1.默认构造函数,实现为空
2.默认析构函数,实现为空
3.默认拷贝构造函数,对属性的值进行拷贝
调用规则如下:
1.如果写了有参构造函数,编译器不再提供默认无参构造函数,但依然提供拷贝构造函数
2.如果写了拷贝构造函数,编译器不再提供一般构造函数(即默认构造和有参构造)
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person的默认构造函数" << endl;
}
~Person()
{
cout << "Person()的默认析构函数" << endl;
}
Person(const Person& p)
{
m_age = p.m_age;
}
int m_age;
};
void test()
{
Person p;
p.m_age = 10;
Person p1(p);
cout << "p1的年龄为:" << p1.m_age << endl;
}
int main()
{
test();
return 0;
}
如果没有写拷贝构造函数,编译器会对属性的值进行拷贝。
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person的默认构造函数" << endl;
}
~Person()
{
cout << "Person()的默认析构函数" << endl;
}
// Person(const Person& p)
// {
// m_age = p.m_age;
// }
int m_age;
};
void test()
{
Person p;
p.m_age = 10;
Person p1(p);
cout << "p1的年龄为:" << p1.m_age << endl;
}
int main()
{
test();
return 0;
}
如果程序员提供有参构造函数,编译器不会再提供默认无参构造函数 ,但会提供拷贝构造函数。
#include<iostream>
using namespace std;
class Person
{
public:
// Person()
// {
// cout << "Person的默认构造函数" << endl;
// }
Person(int a)
{
cout << "Person的有参构造函数" << endl;
}
~Person()
{
cout << "Person()的默认析构函数" << endl;
}
Person(const Person& p)
{
m_age = p.m_age;
}
int m_age;
};
void test()
{
Person p;
}
int main()
{
test();
return 0;
}
提供的拷贝构造函数
#include<iostream>
using namespace std;
class Person
{
public:
// Person()
// {
// cout << "Person的默认构造函数" << endl;
// }
Person(int a)
{
m_age = a;
cout << "Person的有参构造函数" << endl;
}
~Person()
{
cout << "Person()的默认析构函数" << endl;
}
// Person(const Person& p)
// {
// m_age = p.m_age;
// }
int m_age;
};
void test()
{
Person p(10);
Person p1(p);
cout << "p1的年龄为:" << p1.m_age << endl;
}
int main()
{
test();
return 0;
}
如果程序员提供拷贝构造函数,编译器不再提高一般默认构造函数(默认无参构造和有参构造)
#include<iostream>
using namespace std;
class Person
{
public:
// Person()
// {
// cout << "Person的默认构造函数" << endl;
// }
// Person(int a)
// {
// m_age = a;
// cout << "Person的有参构造函数" << endl;
// }
Person(const Person& p)
{
m_age = p.m_age;
}
~Person()
{
cout << "Person()的默认析构函数" << endl;
}
int m_age;
};
void test()
{
Person p;
Person p1(10);
}
int main()
{
test();
return 0;
}
深拷贝与浅拷贝
浅拷贝:简单的赋值操作
深拷贝:在堆区重新申请空间,进行拷贝操作
这段代码是有问题的,在堆区申请的空间被重复释放,这是违法的,但是编译器可以正常编译运行(与编译器版本有关)
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person的默认构造函数" << endl;
}
Person(int a,int h)
{
m_age = a;
height = new int(h);
cout << "Person的有参构造函数" << endl;
}
// Person(const Person& p)
// {
// cout << "Person的拷贝构造函数" << endl;
// }
~Person()
{
if(!height)
{
delete height;
height = NULL;
}
cout << "Person的析构函数" << endl;
}
int m_age;
int* height;
};
void test()
{
Person p(27, 172);
Person p1(p);
cout << "p的年龄为:" << p.m_age << " p的身高为:" << *p.height << endl;
cout << "p1的年龄为:" << p1.m_age << " p1的身高为:" << *p1.height << endl;
}
int main()
{
test();
return 0;
}
浅拷贝:两指针指向同一块地址,导致堆区空间重复释放,是违法操作
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person的默认构造函数" << endl;
}
Person(int a,int h)
{
m_age = a;
height = new int(h);
cout << "Person的有参构造函数" << endl;
}
Person(const Person& p)
{
m_age = p.m_age;
height = new int(*p.height);
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
if(!height)
{
delete height;
height = NULL;
}
cout << "Person的析构函数" << endl;
}
int m_age;
int* height;
};
void test()
{
Person p(27, 172);
Person p1(p);
cout << "p的年龄为:" << p.m_age << " p的身高为:" << *p.height << endl;
cout << "p1的年龄为:" << p1.m_age << " p1的身高为:" << *p1.height << endl;
}
int main()
{
test();
return 0;
}
深拷贝:重新申请一块内存空间,解决堆区重复释放的问题
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person的默认构造函数" << endl;
}
Person(int a,int h)
{
m_age = a;
height = new int(h);
cout << "Person的有参构造函数" << endl;
}
Person(const Person& p)
{
m_age = p.m_age;
height = new int(*p.height);
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
if(!height)
{
delete height;
height = NULL;
}
cout << "Person的析构函数" << endl;
}
int m_age;
int* height;
};
void test()
{
Person p(27, 172);
Person p1(p);
cout << "p的年龄为:" << p.m_age << " p的身高为:" << *p.height << endl;
cout << "p1的年龄为:" << p1.m_age << " p1的身高为:" << *p1.height << endl;
}
int main()
{
test();
return 0;
}
总结:如果属性有在堆区开辟的,程序员要自己提供拷贝构造函数,防止浅拷贝带来的堆区内存重复释放的问题。
初始化列表
作用:C++提供初始化列表语法,用来初始化属性。
#include<iostream>
using namespace std;
class Person{
public :
// 传统的属性赋值
Person(int a,int b,int c)
{
m_a = a;
m_b = b;
m_c = c;
}
// 初始化列表写死
Person():mm_a(10),mm_b(20),mm_c(30)
{
}
// 初始化列表写活
Person(int a,int b,int c):mmm_a(a),mmm_b(b),mmm_c(c)
{
}
int m_a;
int m_b;
int m_c;
int mm_a;
int mm_b;
int mm_c;
int mmm_a;
int mmm_b;
int mmm_c;
};
int main()
{
// 传统有参函数赋值
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;
// 初始化列表写死
Person p;
cout << "mm_a:" << p.mm_a << endl;
cout << "mm_b:" << p.mm_b<<endl;
cout << "mm_c:" << p.mm_c<<endl;
// 初始化列表写活
Person p(10,20,30);
cout << "mmm_a:" << p.mmm_a<<endl;
cout << "mmm_b:" << p.mmm_b<<endl;
cout << "mmm_c:" << p.mmm_c<<endl;
return 0;
}
类对象作为类的成员
C++中,一个类的成员可以是另一个类的对象,我们称该成员为对象成员
一个类的成员变量是另一个类的构造对象的构造顺序?
先构造类成员变量的构造函数,再构造自己的构造函数
析构顺序?
按栈的方式(先进后出):先析构自己,再析构类的成员变量
#include<iostream>
using namespace std;
class Phone
{
public:
Phone(string phone)
{
mm_phone = phone;
cout << "Phone的构造函数调用" << endl;
}
~Phone()
{
cout << "Phone的析构函数调用" << endl;
}
string mm_phone;
};
class Person
{
public:
// 隐式转换:m_phone(phone)==Phone m_phone(phone)
Person(string name,string phone):m_name(name),m_phone(phone)
{
cout << "Person的构造函数调用" << endl;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
string m_name;
Phone m_phone;
};
void test()
{
Person p("guanxi chen","iphone 15 pro max");
cout << p.m_name << "手持 " << p.m_phone.mm_phone << endl;
}
int main()
{
test();
return 0;
}
静态成员
静态成员就是在成员变量和成员函数加上static,称为静态成员
静态成员分为:
静态成员变量:
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
#include<iostream>
using namespace std;
class Person
{
public:
static int m_A;
};
// 类内声明,类外初始化
int Person::m_A = 10;
void test()
{
// 所有成员变量共享同一份数据
Person p;
cout << "p.m_A= " << p.m_A << endl;
Person p1;
p1.m_A = 20;
cout << "p1.m_A= " << p1.m_A << endl;
cout << "p.m_A= " << p.m_A << endl;
}
void test2()
{
// 静态成员变量,不属于某一个对象上,所有对象共享同一份数据
// 因此静态成员变量的访问方式有两种
// 1.通过对象访问
Person p3;
cout << "p3.m_A:" << p3.m_A << endl;
// 2.通过类名访问
cout << "Person::m_A:" << Person::m_A << endl;
}
int main()
{
test();
test2();
return 0;
}
静态成员变量也是有访问权限的:私有成员,类外不可访问
class Person
{
public:
static int m_A;
private:
static int m_B;
};
// 类内声明,类外初始化
int Person::m_A = 10;
int Person::m_B = 20;
静态成员函数:
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
#include<iostream>
using namespace std;
class Person
{
public:
static void test()
{
m_A = 100;//静态成员函数可以访问静态成员变量
cout << "This is static void func" << endl;
}
static int m_A;
};
int Person::m_A = 10;//类内声明,类外初始化
void test1()
{
// 访问方式
// 1.通过对象访问
Person p1;
p1.test();
// 2.通过类型名访问
Person::test();
}
int main()
{
test1();
return 0;
}
静态成员函数不能访问非静态成员变量,因为非静态成员变量不是共享的,静态函数无法区分非静态变量到底属于哪一个对象上的
class Person
{
public:
static void test()
{
m_A = 100;//静态成员函数可以访问静态成员变量
m_B = 200;
cout << "This is static void func" << endl;
}
static int m_A;
int m_B;
};
静态函数也有访问权限
C++对象模型和this指针
成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类对象上
#include<iostream>
using namespace std;
class Person
{
int m_A; //非静态成员变量属于对象
static int mm_A; //静态成员变量,不属于对象上
void func(){} //非静态成员函数,不属于对象上
static void func1(){} //静态成员函数,不属于对象上
};
void test()
{
Person p;
cout << "size of p:" << sizeof(p) << endl;
}
int main()
{
test();
return 0;
}
C++中,即使是空对象,编译器也会分配一个字节的空间,为了区分空对象占内存的位置,每个空对象也应该有一个独一无二的内存地址。
#include<iostream>
using namespace std;
class Person
{
};
void test()
{
Person p;
cout << "size of p:" << sizeof(p) << endl;
}
int main()
{
test();
return 0;
}
this指针
this指针指向被调用的成员函数所属的对象。
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
当形参和成员变量同名时,可用this指针来区分
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
age = age;
}
int age;
};
void test()
{
Person p(10);
cout << "p的年龄为:" << p.age << endl;
}
int main()
{
test();
return 0;
}
如何解决?
1.避免成员变量和形参重名
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
m_age = age;
}
int m_age;
};
void test()
{
Person p(10);
cout << "p的年龄为:" << p.m_age << endl;
}
int main()
{
test();
return 0;
}
2.使用this指针
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
this->age = age;//this指针指向 被调用成员函数的所属对象,此处指向p
}
int age;
};
void test()
{
Person p(10);
cout << "p的年龄为:" << p.age << endl;
}
int main()
{
test();
return 0;
}
在类中的非静态成员函数中返回对象本身,可使用return *this
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
m_age = age;
}
void person_add(Person p)
{
this->m_age += p.m_age;
}
int m_age;
};
void test()
{
Person p1(10);
Person p2(10);
p1.person_add(p2);
cout << "p1的年龄:" << p1.m_age << endl;
}
int main()
{
test();
return 0;
}
上述只能调用一次,能否反复调用?当然是可以的,返回该对象本身便可以反复调用
如何修改?
1.首先将对象本身返回
2.用Person&来接收,若用Person来接收,相当于值传递,会创建一个新的对象,后面连续调的person_add()函数只有第一个作用在p1上,后面不做用在p1上
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
m_age = age;
}
Person& person_add(Person p)
{
this->m_age += p.m_age;
return *this;
}
int m_age;
};
void test()
{
Person p1(10);
Person p2(10);
p1.person_add(p2).person_add(p2).person_add(p2).person_add(p2);
cout << "p1的年龄:" << p1.m_age << endl;
}
int main()
{
test();
return 0;
}
若用Person接收
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
m_age = age;
}
Person person_add(Person p)
{
this->m_age += p.m_age;
return *this;
}
int m_age;
};
void test()
{
Person p1(10);
Person p2(10);
p1.person_add(p2).person_add(p2).person_add(p2).person_add(p2);
cout << "p1的年龄:" << p1.m_age << endl;
}
int main()
{
test();
return 0;
}
p1等于20的原因是,第一次调用,this指针指向p1,使p1+10,返回Person类型(会创造一个新的对象),再次调用person_add()函数,this指针指向了新的对象,所以结果为20
空指针访问成员函数
#include<iostream>
using namespace std;
class Person
{
public:
void show_classname()
{
cout << "this is Person class" << endl;
}
void show_age()
{
m_age = 27;
// cout << "the age is:" << this->m_age << endl;//注意m_age其实是 this->m_age;
cout << "the age is:" << m_age << endl;
}
int m_age;
};
void test()
{
Person *p = NULL;
p->show_classname();
p->show_age();
}
int main()
{
test();
return 0;
}
this为空,导致代码崩掉,可以判断一下,提高代码健壮性
#include<iostream>
using namespace std;
class Person
{
public:
void show_classname()
{
cout << "this is Person class" << endl;
}
void show_age()
{
if(NULL==this)
{
return;
}
m_age = 27;
// cout << "the age is:" << this->m_age << endl;//注意m_age其实是 this->m_age;
cout << "the age is:" << m_age << endl;
}
int m_age;
};
void test()
{
Person *p = NULL;
p->show_classname();
p->show_age();
}
int main()
{
test();
return 0;
}
const修饰成员函数
常函数:
成员函数后加const后称该函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改
#include<iostream>
using namespace std;
class Person
{
public:
// this的本质是一个指针常量,即this的指向不可以改变,
// 若要让this指向的值也不变,需在函数后加const,若
// 在属性前面加上mutable修饰,还是可以改变
void show_class()const
{
m_A = 100; // this->m_A=100;
m_B = 200;// this->m_B=200;
}
int m_A;
mutable int m_B;
};
int main()
{
return 0;
}
常对象:
声明对象前加const称该对象为常对象
常对象只能调用常函数
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
}
// this的本质是一个指针常量,即this的指向不可以改变,
// 若要让this指向的值也不变,需在函数后加const,若
// 在属性前面加上mutable修饰,还是可以改变
void show_class()const
{
// m_A = 100; // this->m_A=100;
m_B = 200;// this->m_B=200;
}
void test1()
{
m_A = 100;
}
int m_A;
mutable int m_B;
};
void test()
{
const Person p;//常对象
p.show_class();
p.test1();
}
int main()
{
test();
return 0;
}
友元
友元的目的是让一个函数或者一个类 访问另一个类中的私有成员
友元的关键字为friend
友元的三种实现
全局函数做友元
#include<iostream>
using namespace std;
class Building
{
// visit_gay()是全局函数的友元,可以访问类的私有成员
friend void visit_gay(Building &p);
public:
Building()
{
sittingroom = "客厅";
bedroom = "卧室";
}
public:
string sittingroom;
private:
string bedroom;
};
void test_friend()
{
Building p;
visit_gay(p);
}
void visit_gay(Building& p)
{
cout << "好基友正在访问:" << p.sittingroom << endl;
cout << "好基友正在访问:" << p.bedroom << endl;
}
int main()
{
test_friend();
return 0;
}
类做友元
#include<iostream>
using namespace std;
class Building
{
friend class Goodgay;
public:
Building();
public:
string sitting_room;
private:
string bed_room;
};
class Goodgay
{
public:
Goodgay();
private:
Building *p;
};
Building::Building()
{
sitting_room = "客厅";
bed_room = "卧室";
}
Goodgay::Goodgay()
{
p = new Building;
cout << "好基友正在访问:" << p->sitting_room << endl;
cout << "好基友正在访问:" << p->bed_room << endl;
delete p;
}
void test()
{
Goodgay gg;
}
int main()
{
test();
return 0;
}
成员函数做友元
#include<iostream>
using namespace std;
class Building;
class Good_gay
{
public:
Good_gay();
void vist();
private:
Building *p;
};
class Building
{
friend void Good_gay::vist();
public:
Building();
public:
string sitting_room;
private:
string bed_room;
};
Building::Building()
{
sitting_room="客厅";
bed_room = "卧室";
}
void Good_gay:: vist()
{
cout << "好基友正在访问:" << p->sitting_room << endl;
cout << "好基友正在访问:" << p->bed_room << endl;
}
Good_gay::Good_gay()
{
p = new Building;
}
void test()
{
Good_gay gg;
gg.vist();
}
int main()
{
test();
return 0;
}
当类成员函数做友元时,有时候会报这种类型的错误
解决方式一般有两种:(原因我也说不清楚,应该与C++编译器自上而下执行顺序有关)
1.将类定义的顺寻交换
2.将类内函数进行类外实现