C++学习笔记

在堆上申请数据与栈上申请数据的区别:

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.将类内函数进行类外实现

  • 9
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值