c核心编程
C++核心编程
1 内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理。
- 全局区:存放全局变量、静态变量以及常量。
- 栈区:由编译器自动分配释放,存放函数的参数值、局部变量等。
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
内存四区意义:
不同区域存放的数据,被赋予不同的生命周期,这给我们带来了更大的编程灵活性。
1.1 程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
(1) 代码区:
内容:存放CPU执行的机器指令。
特点:
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
- 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令。
(2) 全局区:
内容:全局变量和静态变量存放在此。
特点:
- 全局区还包含了常量区,字符串常量和其他常量也存放在此。
- 该区域的数据在程序结束后由操作系统释放。
示例:
#include <iostream>
#include <string>
using namespace std;
// 全局变量
int g_a = 10;
int g_b = 10;
// 静态全局变量
static int s_g_a = 10;
static int s_g_b = 10;
// 字符串全局常量
const char* g_s1 = "abcd";
const char* g_s2 = "abcd";
// const 修饰的全局常量
const int g_c_a = 10;
const int g_c_b = 10;
int main() {
cout << "程序运行前:" << endl;
cout << endl;
// 局部变量
int a = 10;
int b = 10;
// 局部变量的地址
cout << "局部变量a的地址为:" << &a << endl;
cout << "局部变量b的地址为:" << &b << endl;
// 全局变量的地址
cout << "全局变量g_a的地址为:" << &g_a << endl;
cout << "全局变量g_b的地址为:" << &g_b << endl;
cout << endl;
// 静态局部变量
static int s_a = 10;
static int s_b = 10;
// 静态局部变量的地址
cout << "静态局部变量s_a的地址为:" << &s_a << endl;
cout << "静态局部变量s_b的地址为:" << &s_b << endl;
// 静态全局变量的地址
cout << "静态全局变量s_g_a的地址为:" << &s_g_a << endl;
cout << "静态全局变量s_g_b的地址为:" << &s_g_b << endl;
cout << endl;
// 常量
// 字符串常量
const char* s1 = "abcd";
const char* s2 = "abcd";
cout << "字符串常量s1的地址为:" << (void*)s1 << endl;
cout << "字符串常量s2的地址为:" << (void*)s2 << endl;
// const修饰的局部常量
const int c_a = 10;
const int c_b = 10;
cout << "局部常量c_a的地址为:" << &c_a << endl;
cout << "局部常量c_b的地址为:" << &c_b << endl;
cout << endl;
// 全局常量地址
cout << "全局常量g_c_a的地址为:" << &g_c_a << endl;
cout << "全局常量g_c_b的地址为:" << &g_c_b << endl;
return 0;
}//const 修饰的局部常量
const int c_a=10;
const int c_b=10;
//字符串局部常量的地址
cout<<"字符串局部常量s1的地址为:"<<&s1<<endl;
cout<<"字符串局部常量s2的地址为:"<<&s2<<endl;
//字符串全局常量的地址
cout<<"字符串全局常量g_s1的地址为:"<<&g_s1<<endl;
cout<<"字符串全局常量g_s2的地址为:"<<&g_s2<<endl;
//const 修饰的局部常量
cout<<"const 修饰的局部常量c_a的地址为:"<<&c_a<<endl;
cout<<"const 修饰的局部常量c_b的地址为:"<<&c_b<<endl;
//const 修饰的全局常量
cout<<"const 修饰的全局常量g_c_a的地址为:"<<&g_c_a<<endl;
cout<<"const 修饰的全局常量g_c_b的地址为:"<<&g_c_b<<endl;
cout<<endl;
cout<<"有全局修饰的在全局区"<<endl;
cout<<"其他的不在全局区"<<endl;
return 0;
}总结 :
C++中在程序运行前分为全局区 和代码区- 代码区特点是共享和只读
- 全局区中存放全局变量、静态变量、常量
- 常量区中存放 const 修饰的全局常量和字符串常量
1.2 程序运行后
在程序编译后,生成了exe可执行程序,执行该程序后 分为两个区域
(1) 栈区:
- 由编译器自动分配释放, 存放函数的参数值,局部变量等
注意事项 :不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
(2) 堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在
C++中主要利用new在堆区开辟内存
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int* test_01(){
int a=10; //局部变量存储在栈区
return &a; //不要返回局部变量的地址
}
int* test_02(){
int* m=new int(10); //利用new将数据开辟到堆区
return m;
}
int main(){
cout<<"栈区数据由编译器自动分配释放, 存放函数的参数值,局部变量等"<<endl;
//调用函数test_01
int* p1=test_01();
cout<<endl;
//输出
cout<<"第一次输出,编译器对局部变量做一次保留,暂时不释放: "<<*p1<<endl;
cout<<"第二次输出,编译器不再保留栈区的数据,直接释放:"<<*p1<<endl;
cout<<"不要返回局部变量的地址!!!"<<endl;
cout<<endl;
//调用函数test_02
int* p2=test_02();
//输出
cout<<"输出存放在堆区的数据,编译器不释放,由程序员手动释放: "<<*p2<<endl;
cout<<"输出存放在堆区的数据,编译器不释放,由程序员手动释放: "<<*p2<<endl;
cout<<"输出存放在堆区的数据,编译器不释放,由程序员手动释放: "<<*p2<<endl;
cout<<endl;
//释放堆中开辟的数据
delete p2;
cout<<"程序员手动释放后: "<<*p2<<endl;
return 0;
}总结:
堆区数据由程序员管理开辟和释放
堆区数据利用new关键字进行开辟内存
1.3 new操作符
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
示例1:开辟数据
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int* test_01(){
int* a=new int(10); //堆区开辟数据
return a;
}
int main(){
int *p=test_01();
cout<<*p<<endl;
cout<<*p<<endl;
//利用delete释放堆区数据
delete p;
cout<<*p<<endl; //已释放,输出垃圾值
return 0;
}示例2:开辟数组
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int* test_01(){
int* a=new int[10]; //堆区中开辟数组
return a;
}
int main(){
int *p=test_01();
for(int i=0;i<10;i++) p[i]=i+1; //赋值
for(int i=0;i<10;i++) cout<<p[i]<<" "; //输出
cout<<endl;
//未释放前输出p[0]
cout<<*p<<endl;
//利用delete释放堆区数据
delete[] p;
//已释放,输出垃圾值
cout<<*p<<endl;
return 0;
}2 引用及其使用
2.1 引用的基本使用
作用: 给变量起别名
语法: 数据类型 &别名 = 原名
示例:
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main(){
int a = 10;
int &b = a; // 创建a的别名为b,必须初始化
cout << "a = " << a << endl;
cout << "b = " << b << endl; // b的值同a
// 修改a的值
a = 20;
cout << "a = " << a << endl;
cout << "b = " << b << endl; // b的值也发生改变
// 修改b的值
b = 10;
cout << "a = " << a << endl; // a的值也发生改变
cout << "b = " << b << endl;
return 0;
}2.2 引用注意事项
引用必须初始化
引用在初始化后,不可以改变
示例:
#include <iostream>
#include <string>
#include <algorithm>
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;
return 0;
}2.3 引用做函数参数
作用: 函数传参时,可以利用引用的技术让形参修饰实参
优点: 可以简化指针修改实参
示例:
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
void swap(int &a, int &b){
int t = a;
a = b;
b = t;
}
int main(){
int a = 10;
int b = 20;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << endl;
swap(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单。
2.4 引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
示例:
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int& test() {
static int a = 10; // 静态局部变量,生命周期长于函数
return a;
}
int main() {
int &ref = test();
cout << "ref = " << ref << endl;
test() = 20; // 函数调用作为左值
cout << "ref = " << ref << endl;
return 0;
}#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int &test_01(){
int a=10; //局部变量
return a;
}
int &test_02(){
static int a=10; //全局区变量
return a;
}
int main(){
int &ans_01=test_01();
cout<<"ans_01 = "<<ans_01<<endl; //第一次输出正确是因为编译器做了保留
cout<<"ans_01 = "<<ans_01<<endl; //再次输出已经被释放,输出垃圾值
cout<<"不要返回局部变量的引用!!!"<<endl;
cout<<endl;
int &ans_02=test_02();
cout<<"ans_02 = "<<ans_02<<endl;
cout<<"ans_02 = "<<ans_02<<endl;
cout<<"ans_02 = "<<ans_02<<endl;
test_02()=20; //函数调用作为左值
cout<<"ans_02 = "<<ans_02<<endl;
return 0;
}2.5 引用的本质
本质:引用的本质在C++内部实现是一个指针常量。
讲解示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
//发现是引用,转换为 int* const ref = &a;
void test_01(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;
test_01(a);
cout<<"a = "<<a<<endl;
cout<<"ref = "<<ref<<endl;
return 0;
}结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了。
2.6 常量引用
作用: 常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加 const 修饰形参,防止形参改变实参
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int test_01(const int &a){
int b=a+10;
// a=b; //报错 a被const修饰 不可修改
return b;
}
int main(){
int a=10;
int b=test_01(a);
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
return 0;
}3 函数提高
3.1 函数默认参数
在 C++ 中,函数的形参列表中的形参是可以有默认值的。
语法:返回值类型 函数名(参数=默认值){}
示例:
//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int add(int a,int b=10){
return a+b;
}
int main(){
int a=20,b=30;
cout<<add(a,b)<<endl;
cout<<add(a)<<endl; //未传入参数b,默认b=10
return 0;
}3.2 函数占位参数
C++ 中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置。
语法: 返回值类型 函数名(数据类型){}
在现阶段函数的占位参数存在意义不大,但是后面的课程中会用到该技术。
示例:
//函数占位参数,占位参数也可以有默认参数
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int add(int a,int){
return a;
}
int main(){
int a=20,b=30;
cout<<add(a,b)<<endl; //占位参数必须填补
return 0;
}3.3 函数重载
3.3.1 函数重载概述
作用: 函数名可以相同,提高复用性。
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数 类型不同 或者 个数不同 或者 顺序不同
注意: 函数的返回值不可以作为函数重载的条件。
示例:
//函数重载需要函数都在同一个作用域下
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int add(){
cout << "add() 的调用: " << 0 << endl;
}
int add(int a){
cout << "add(int a) 的调用:" << a << endl;
}
int add(double a){
cout << "add(double a) 的调用:" << a << endl;
}
int add(int a, int b){
cout << "add(int a, int b) 的调用:" << a << "+" << b << "=" << a + b << endl;
}
int add(int a, double b){
cout << "add(int a, double b) 的调用:" << a << "+" << b << "=" << a + b << endl;
}
int add(double a, int b){
cout << "add(double a, int b) 的调用:" << a << "+" << b << "=" << a + b << endl;
}
int add(double a, double b){
cout << "add(double a, double b) 的调用:" << a << "+" << b << "=" << a + b << endl;
}
int main(){
add();
add(1);
add(1.11);
add(1, 2);
add(1, 2.22);
add(1.11, 2);
add(1.11, 2.22);
return 0;
}3.3.2 函数重载注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
示例:
//1、引用作为重载条件
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
void func(int &a){
cout << "func(int &a) 的调用:" << a << endl;
}
void func(const int &a){
cout << "func(const int &a) 的调用:" << a << endl;
}
int main(){
int a = 10;
func(a); //调用无const
func(20); //调用有const
return 0;
}
//函数重载碰到函数默认参数
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
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;
}4 类和对象
C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物皆为对象,对象有其属性和行为
例如:
人可以作为对象,属性有姓名、年龄、身高、体重...,行为有走、跑、跳、吃饭、唱歌...
车也可以作为对象,属性有轮胎、方向盘、车灯...,行为有载人、放音乐、放空调...
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
4.1 封装
4.1.1 封装的意义
封装是C++面向对象三大特性之一
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法: class 类名{ 访问权限: 属性 / 行为 };
示例1: 设计一个圆类,求圆的周长
示例代码:
//1、封装的意义
//将属性和行为作为一个整体,用来表现生活中的事物
//封装一个圆类,求圆的周长
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const double PI=3.1415926;
class circle{
public: //访问权限 公共的权限
//属性
int r; //半径
//行为
double calculate(){ //计算圆的周长
return r*r*PI;
}
};
int main(){
//通过圆类,创建圆的对象
circle c1; // c1就是一个具体的圆
c1.r=10; //给圆对象的半径 进行赋值操作
cout<<"c1的周长为: "<<c1.calculate()<<endl;
return 0;
}封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
- public 公共权限
- protected 保护权限
- private 私有权限
示例:
//三种权限
//公共权限 public 类内可以访问 类外可以访问
//保护权限 protected 类内可以访问 类外不可以访问
//私有权限 private 类内可以访问 类外不可以访问
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class person{
public: //访问权限 公共的权限
//姓名 公共权限
string name;
protected:
//钱 保护权限
int money;
private:
//年龄 私有权限
int year;
public:
void make(){ //初始化
name="lys";
money=100000000;
year=20;
}
};
int main(){
//通过person类,创建对象p1
person p1;
p1.make(); //初始化p1
cout<<p1.name; //public 类外可以访问
//p1.money=0; // protected 类外不可更改,不可访问
//cout<<p1.money;
//p1.year=100; //private 类外不可更改,不可访问
//cout<<p1.year;
return 0;
}4.1.2 struct和class区别
在C++中 struct和class唯一的区别 就在于 默认的访问权限不同
区别:
- struct 默认权限为公共
- class 默认权限为私有
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
struct point_1{
int x,y; //默认是公共权限
};
class point_2{
int x,y; //默认是私有权限
};
int main(){
point_1 p1;
point_2 p2;
p1.x=10;
p1.y=20;
//p2.x=10;
//p2.y=20; //错误,访问权限是私有
return 0;
}4.2 对象的初始化和清理
4.2.1 构造函数和析构函数
对象的初始化和清理 也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知。
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。
解决方法:
C++利用了构造函数 和析构函数 解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。
编译器提供的构造函数和析构函数是空实现。
- 构造函数 :主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 析构函数 :主要作用在于对象销毁前 系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void。
- 函数名称与类名相同。
- 构造函数可以有参数,因此可以发生重载。
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次。
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写 void
- 函数名称与类名相同,在名称前加上符号
~ - 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
- 不能设为私有
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
// 构造函数
point(){
cout<<"point的构造函数调用"<<endl;
}
// 析构函数
~point(){
cout<<"point的析构函数调用"<<endl;
}
};
int main(){
point p1;
return 0;
}4.2.2 构造函数的分类及调用方式
两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
示例:
// 1、构造函数分类:
// 按照参数分类分为 有参和无参构造 无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x, y;
point(){
cout<<"无参构造函数调用"<<endl;
}
point(int a, int b){
x=a;
y=b;
cout<<"有参构造函数调用"<<endl;
}
point(const point &p){
x=p.x;
y=p.y;
cout<<"拷贝构造函数调用"<<endl;
}
};
int main(){
// 括号法,常用
point p1; // 调用无参构造函数
point p2(1,2); // 调用有参构造函数
point p3(p2); // 调用拷贝构造函数
// 注意:不可写成 point p1(),否则编译器会认为是函数声明而不是构造
point p4=point(); // 调用无参构造函数
point p5=point(3,4); // 调用有参构造函数
point p6=point(p5); // 调用拷贝构造函数
// 隐式转换法
point p7; // 调用无参构造函数
point p8={5,6}; // 调用有参构造函数
point p9=p8; // 调用拷贝构造函数
return 0;
}4.2.3 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x,y;
point(){
cout << "默认构造函数调用" << endl;
}
point(int a,int b){
x = a;
y = b;
cout << "有参构造函数调用" << endl;
}
point(const point &p){
x = p.x;
y = p.y;
cout << "拷贝构造函数调用" << endl;
}
};
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01(){
point p1(1,2);
point p2(p1);
}
//2. 值传递的方式给函数参数传值
void make(point &p){
p.x = 1;
p.y = 2;
}
void test02(){
point p3;
make(p3);
cout << p3.x << " " << p3.y;
}
//3. 以值方式返回局部对象
int show_x(point p){
return p.x;
}
void test03(){
point p4(2,3);
cout << show_x(p4);
}
int main(){
test01();
test02();
test03();
return 0;
}4.2.4 构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x,y;
point(){
cout << "无参构造函数" << endl;
}
point(int a, int b) {
x = a;
y = b;
cout << "有参构造函数" << endl;
}
point(const point &p) {
x = p.x;
y = p.y;
cout << "拷贝构造函数" << endl;
}
~point() {
cout << "析构函数" << endl;
}
};
void test01() {
point p1(1, 2);
// 如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
point p2(p1);
printf("p2 = (%d,%d)\n", p2.x, p2.y);
}
void test02() {
// 如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
// point p3; // 此时如果用户自己没有提供默认构造,会出错
point p4(3, 4); // 用户提供的有参
point p5(p4); // 此时如果用户没有提供拷贝构造,编译器会提供
// 如果用户提供拷贝构造,编译器不会提供其他构造函数
// point p6; // 此时如果用户自己没有提供默认构造,会出错
// point p7(5, 6); // 此时如果用户自己没有提供有参,会出错
point p8(p7); // 用户自己提供拷贝构造
}
int main() {
test01();
test02();
return 0;
}4.2.5 深拷贝与浅拷贝
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point {
public:
int x, y;
int *z;
point(int a, int b, int h) {
x = a;
y = b;
z = new int(h);
}
// 用户未提供拷贝构造函数,执行浅拷贝
~point() {
if (z != NULL) {
delete z;
z = NULL;
}
}
};
void test01() {
point p1(1, 2, 3);
point p2(p1); // 用户未提供拷贝构造函数,执行浅拷贝
}
int main() {
test01();
return 0;
}
/*程序会崩掉,原因是在用户没有提供拷贝构造函数的前提下,
调用拷贝构造函数是编译器提供的默认拷贝构造函数,对h的地址进行拷贝,实现的是浅拷贝
在执行析构函数时,会造成h的内存重复释放的非法操作*/解决方案:
// 1. 用户自己提供拷贝构造函数
point(const point &p) {
x = p.x;
y = p.y;
z = new int(*p.z); // 深拷贝
}#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x,y;
int *z;
point(int a,int b,int h){
x=a;
y=b;
z=new int(h);
}
point(const point &p){
x=p.x;
y=p.y;
// z=p.z; //浅拷贝执行的操作
z=new int(*p.z); //深拷贝执行的操作
}
~point(){
if(z!=NULL){
delete z;
z=NULL;
}
}
};
void test01(){
point p1(1,2,3);
point p2(p1); //用户提供了拷贝函数,执行深拷贝
}
int main(){
test01();
return 0;
}总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
4.2.6 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)... {}
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x,y,z;
//传统方式初始化
// point(int a,int b,int h){
// x=a;
// y=b;
// z=h;
// }
//初始化列表方式初始化
point(int a,int b,int h):x(a),y(b),z(h) {}
};
void test01(){
point p1(1,2,3);
cout<<p1.x<<" "<<p1.y<<" "<<p1.z<<endl;
}
int main(){
test01();
return 0;
}4.2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
例如:
class A{
};
class B{
A a;
};B类中有对象A作为成员,A为对象成员当创建B对象时,A与B的构造和析构的顺序
示例:
//构造的顺序是:先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class A{
public:
A(){
cout << "A的构造函数调用" << endl;
}
~A(){
cout << "A的析构函数调用" << endl;
}
};
class B{
public:
A a;
B(){
cout << "B的构造函数调用" << endl;
}
~B(){
cout << "B的析构函数调用" << endl;
}
};
int main(){
B b2;
return 0;
}4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。
静态成员分为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
示例1:静态成员变量
//静态成员变量特点:
//1 在编译阶段分配内存
//2 类内声明,类外初始化
//3 所有对象共享同一份数据
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
static int x;
private:
static int y; //静态成员变量也是有访问权限的
};
int point::x = 10;
int point::y = 20;
int main(){
point p1;
p1.x = 20;
cout << p1.x << endl; //1、通过对象访问
point p2;
cout << p2.x << endl; //共享同一份数据
cout << point::x << endl; //2、通过类名访问
//cout << p2.y << endl; //私有权限访问不到
return 0;
}示例2:静态成员函数
//静态成员函数特点:
//1 程序共享一个函数
//2 静态成员函数只能访问静态成员变量
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
static int x;
int y;
static void show_pub(){
cout<<x<<endl;
//cout<<y<<endl; //错误,不可以访问非静态成员变量
}
private:
static int z;
//静态成员函数也是有访问权限的
static void show_pri(){
cout<<z<<endl;
}
};
int point::x=10;
int point::z=30;
int main(){
point p1;
p1.y=20;
//1、通过对象
p1.show_pub();
//2、通过类名
point::show_pub();
//p1.show_pri(); //私有权限访问不到
return 0;
}4.3 C++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储。
只有非静态成员变量 才属于类的对象上。
示例
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point1{
int x; //非静态成员变量占对象空间
};
class point2{
int x; //非静态成员变量占对象空间
static int y; //静态成员变量不占对象空间
void fx(){ //函数也不占对象空间,所有函数共享一个函数实例
}
static void fy(){ //静态成员函数也不占对象空间
}
};
int main(){
cout<<sizeof(point1)<<endl;
cout<<sizeof(point2)<<endl;
}注意:C++编译器会给空类的对象分配一个字节,用于区分其存储空间。
4.3.2 this指针概念
C++中成员变量和成员函数是分开存储的,每一个非静态成员函数只会产生一份函数实例,也就是说多个同类型的对象会共用一块代码。
那么问题是:这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。
概念
- this指针是隐含在每一个非静态成员函数内的一种指针
- this指针不需要定义,直接使用即可
用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用
return *this
- 在类的非静态成员函数中返回对象本身,可使用
示例
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x,y;
//1、当形参和成员变量同名时,可用this指针来区分
point(int x, int y) {
this->x = x;
this->y = y;
}
point& add(point p) {
this->x += p.x;
this->y += p.y;
//返回对象本身
return *this;
}
};
void test01() {
point p1(1, 2);
cout << p1.x << " " << p1.y << endl;
}
void test02() {
point p2(1, 1);
point p3(0, 0);
p3.add(p2).add(p2).add(p2);
cout << p2.x << " " << p2.y << endl;
}
int main() {
test01();
test02();
return 0;
}4.3.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针。
如果用到this指针,需要加以判断保证代码的健壮性。
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point {
public:
int x, y;
point(int a, int b) : x(a), y(b) {}
void show_1() {
cout << "YES" << endl;
}
void show_2() {
cout << x << " " << y << endl; //默认使用了this指针
// cout << this->x << this->y << endl; //与上一行等价
}
};
int main() {
point *p1 = NULL;
p1->show_1(); //空指针,可以调用成员函数
//p1->show_2(); //但是如果成员函数中用到了this指针,就不可以了
return 0;
}4.3.4 const修饰成员函数
常函数:
- 成员函数后加
const后我们称为这个函数为常函数- 常函数内不可以修改成员属性
- 成员属性声明时加关键字
mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加
const称该对象为常对象- 常对象只能调用常函数
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point {
public:
int x;
mutable int y; //可修改 可变的
point() {
x = 10;
y = 10;
}
void show_1() const {
//const Type* const pointer;
//this = NULL; //不能修改指针的指向 Person* const this;
//this->mA = 100; //但是this指针指向的对象的数据是可以修改的
//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
this->y = 20;
cout << x << " " << y << endl;
}
void show_2() {
cout << x << " " << y << endl;
}
// void show_no() const {
// this->x = 20;
// }
private:
int y;
};
int main() {
point p1;
p1.show_1(); //非常对象可以调用const函数
const point p2; //常量对象
// p2.x = 20; //常对象不能修改成员变量的值,但是可以访问
p2.y = 100; //但是常对象可以修改mutable修饰成员变量
//常对象访问成员函数
p2.show_1();
// p2.show_2(); //常对象只能调用const函数
return 0;
}4.4 友元
生活中你的家有客厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去。
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
- 友元的目的就是让一个函数或者类访问另一个类中私有成员
- 友元的关键字为:
friend
友元的三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.4.1 全局函数做友元
示例
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point {
friend void visit(point &p); //声明友元函数
public:
int x;
point() {
x = 10;
y = 10;
}
private:
int y;
};
void visit(point &p) {
cout << p.x << endl << p.y << endl;
}
int main() {
point p1;
visit(p1);
}4.4.2 类做友元
示例
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
friend class show; //声明友元类
public:
int x;
point(){
x=10;
y=10;
}
private:
int y;
};
class show{
public:
point p1;
void visit(){
cout<<p1.x<<" "<<p1.y<<endl; //可以访问类point里的私有y
}
};
int main(){
show s1;
s1.visit();
return 0;
}4.4.3 成员函数做友元
示例
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point;
class show
{
public:
show();
void visit(); //让visit函数作为point的好朋友,可以访问point中私有内容
private:
point *p;
};
class point
{
//告诉编译器 show类中的visit成员函数 是point的好朋友,可以访问私有内容
friend void show::visit();
public:
point();
public:
int x;
private:
int y;
};
point::point()
{
this->x=10;
this->y=10;
}
show::show()
{
p = new point;
}
void show::visit()
{
cout << "好基友正在访问" << p->x << endl;
cout << "好基友正在访问" << p->y << endl;
}
void test01()
{
show s;
s.visit();
}
int main(){
test01();
return 0;
}4.5 运算符重载
运算符重载概念 :利用operator对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
本质 :
- 提供一个
operator 运算符()函数,使得A operator 运算符(B)的形式可以化简为A 运算符 B的形式
4.5.1 加号/减号运算符重载
作用 :实现两个自定义数据类型相加的运算
示例1
//成员函数实现 + / -号运算符重载
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x,y;
point(){}
point(int a,int b):x(a),y(b) {}
point operator+(const point p){ //成员函数实现 + 号运算符重载
point t;
t.x=this->x+p.x;
t.y=this->y+p.y;
return t;
}
point operator-(const point p){ //成员函数实现 - 号运算符重载
point t;
t.x=this->x-p.x;
t.y=this->y-p.y;
return t;
}
};
int main(){
point p1(1,1);
point p2(2,2);
point p3=p2+p1;
cout<<p3.x<<" "<<p3.y<<endl;
p3=p2-p1;
cout<<p3.x<<" "<<p3.y<<endl;
return 0;
}示例2
//全局函数实现 + / -号运算符重载
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x,y;
point(){}
point(int a,int b):x(a),y(b){}
};
point operator+(const point &p1,const point &p2){ //全局函数实现 + 号运算符重载
point t;
t.x=p1.x+p2.x;
t.y=p1.y+p2.y;
return t;
}
point operator-(const point &p1,const point &p2){ //全局函数实现 - 号运算符重载
point t;
t.x=p1.x-p2.x;
t.y=p1.y-p2.y;
return t;
}
int main(){
point p1(1,1);
point p2(2,2);
point p3=p1+p2;
cout<<p3.x<<" "<<p3.y<<endl;
p3=p2-p1;
cout<<p3.x<<" "<<p3.y<<endl;
return 0;
}总结
- 对于内置的数据类型的表达式的运算符是不可能改变的
- 不要滥用运算符重载
4.5.2 左移运算符重载
作用:可以输出自定义数据类型
注意:一般使用全局函数实现
示例
// 全局函数实现输出运算符重载
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x, y;
point(){}
point(int a, int b): x(a), y(b){}
};
// ostream对象只能有一个
ostream& operator<<(ostream &cout, point &p){
cout << p.x << " " << p.y << endl;
return cout;
}
int main(){
point p1(1, 1);
cout << p1 << "链式输出" << endl;
return 0;
}4.5.3 递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
示例
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x, y;
point(){}
point(int a, int b): x(a), y(b){}
// 前置递增
point& operator++(){
this->x++;
this->y++;
return *this;
}
// 后置递增
point operator++(int){ // int用于占位
point temp = *this; // 先记录当前值
this->x++;
this->y++;
return temp; // 返回递增前的值
}
};
int main(){
point p1(1, 1);
p1++;
++p1;
cout << p1.x << " " << p1.y << endl;
return 0;
}4.5.4 赋值运算符重载
C++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符
operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int *x, *y; // 开辟到堆区
point(){
x = new int(0);
y = new int(0);
}
point(int a, int b) {
x = new int(a); // 将数据开辟到堆区
y = new int(b);
}
// 重载赋值运算符
point& operator=(const point &p){
// 先判断是否有属性在堆区,如果有,先释放干净,否则造成内存泄漏
if(this->x != NULL || this->y != NULL){
delete this->x;
this->x = NULL;
delete this->y;
this->y = NULL;
}
// 深拷贝
this->x = new int(*p.x);
this->y = new int(*p.y);
return *this;
}
~point(){
if(x != NULL){
delete x;
x = NULL;
}
if(y != NULL){
delete y;
y = NULL;
}
}
};
int main(){
point p1;
*p1.x = 10;
*p1.y = 20;
point p2;
p2 = p1; // 调用赋值运算符重载
cout << "p2.x = " << *p2.x << " p2.y = " << *p2.y << endl;
return 0;
}#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int *x, *y; // 开辟到堆区
point(){
x = new int(0);
y = new int(0);
}
point(int a, int b) {
x = new int(a); // 将数据开辟到堆区
y = new int(b);
}
// 重载赋值运算符
point& operator=(point& p) {
if (this->x != NULL) {
delete this->x;
this->x = NULL;
}
if (this->y != NULL) {
delete this->y;
this->y = NULL;
}
// this->x = p.x; // 编译器提供的代码是浅拷贝
this->x = new int(*p.x); // 提供深拷贝,解决浅拷贝的问题
this->y = new int(*p.y);
return *this;
}
~point() {
if (this->x != NULL) {
delete this->x;
this->x = NULL;
}
if (this->y != NULL) {
delete this->y;
this->y = NULL;
}
}
};
int main() {
point p1(1, 1);
point p2(2, 2);
p1 = p2;
cout << *p1.x << " " << *p1.y << endl;
return 0;
}4.5.5 关系运算符重载
作用: 重载关系运算符,可以让两个自定义类型对象进行对比操作。
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point {
public:
int x, y;
point(int a, int b) : x(a), y(b) {}
bool operator==(const point& p) {
if (this->x == p.x && this->y == p.y) return 1;
else return 0;
}
};
int main() {
point p1(1, 1);
point p2(2, 2);
if (p1 == p2) cout << "YES" << endl;
else cout << "NO" << endl;
return 0;
}4.5.6 函数调用运算符重载
特点
- 函数调用运算符
()也可以重载。- 由于重载后使用的方式非常像函数的调用,因此称为仿函数。
- 仿函数没有固定写法,非常灵活。
示例:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class print {
public:
void operator()(auto s) { // 重载的 () 操作符,也称为仿函数
cout << s << endl;
}
};
class add {
public:
int operator()(int a, int b) {
return a + b;
}
};
void test01() {
print p;
p("lys is dog");
}
void test02() {
add a;
cout << a(1, 2) << endl;
}
int main() {
test01();
test02();
return 0;
}4.6 继承
继承是面向对象三大特性之一。
我们发现,定义这些类时,下级的成员除了拥有上级的共性,还有自己的特性。这个时候我们就可以考虑利用继承的技术,减少重复代码。
定义和概念
继承是类的重要特性。A类继承B类,我们称B类为“基类”,A类为“派生类”。A类继承了B类之后,A类就具有了B类的部分成员,具体得到了哪些成员,这由两个方面决定:
- 继承方式
- 基类成员访问权限
4.6.1 继承的基本语法
基本语法:class A : public B
A类称为派生类。B类称为基类。
示例:对于一个人来说,有姓名、年龄、性别这些基本特征,而像是职位之类的特征则是因人而异的特征。在创建“人”的类的时候,我们可以通过继承的技术,减少对基本特征的定义等操作的代码。
普通实现:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
// 学生类
class student {
public:
string name, sex;
int year;
student(string n, int x, string s) : name(n), year(x), sex(s) {}
void show_name() {
cout << "名字:" << name << endl;
}
void show_year() {
cout << "年龄:" << year << endl;
}
void show_sex() {
if (sex == "boy") {
cout << "性别:男" << endl;
} else {
cout << "性别:女" << endl;
}
}
void show_position() {
cout << "是一个学生" << endl;
}
};
//家长类
class parent {
public:
string name, sex;
int year;
parent(string n, int x, string s) : name(n), year(x), sex(s) {}
void show_name() {
cout << "名字:" << name << endl;
}
void show_year() {
cout << "年龄:" << year << endl;
}
void show_sex() {
if (sex == "boy") {
cout << "性别:男" << endl;
} else {
cout << "性别:女" << endl;
}
}
void show_position() {
cout << "是一名家长" << endl;
}
};
//教师类
class teacher {
public:
string name, sex;
int year;
teacher(string n, int x, string s) : name(n), year(x), sex(s) {}
void show_name() {
cout << "名字:" << name << endl;
}
void show_year() {
cout << "年龄:" << year << endl;
}
void show_sex() {
if (sex == "boy") {
cout << "性别:男" << endl;
} else {
cout << "性别:女" << endl;
}
}
void show_position() {
cout << "是一位老师" << endl;
}
};
int main() {
//学生对象
student s1("lys", 20, "boy");
s1.show_name();
s1.show_year();
s1.show_position();
cout << endl;
//家长对象
parent p1("mama", 40, "girl");
p1.show_name();
p1.show_year();
p1.show_position();
cout << endl;
//教师对象
teacher t1("yxc", 30, "boy");
t1.show_name();
t1.show_year();
t1.show_position();
cout << endl;
return 0;
}#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
// 用于继承的基类
class person {
public:
string name, sex;
int year;
person(string n, int x, string s) : name(n), year(x), sex(s) {}
void show_name() {
cout << "名字:" << name << endl;
}
void show_year() {
cout << "年龄:" << year << endl;
}
void show_sex() {
if (sex == "boy") {
cout << "性别:男" << endl;
} else {
cout << "性别:女" << endl;
}
}
};
// 学生类
class student : public person {
public:
student(string n, int x, string s) : person(n, x, s) {}
void show_position() {
cout << "是一个学生" << endl; // 因人而异的特征
}
};
// 家长类
class parent : public person {
public:
parent(string n, int x, string s) : person(n, x, s) {}
void show_position() {
cout << "是一名家长" << endl;
}
};
// 教师类
class teacher : public person {
public:
teacher(string n, int x, string s) : person(n, x, s) {}
void show_position() {
cout << "是一位老师" << endl;
}
};
int main() {
// 学生对象
student s1("lys", 20, "boy");
s1.show_name();
s1.show_year();
s1.show_position();
cout << endl;
// 家长对象
parent p1("mama", 40, "girl");
p1.show_name();
p1.show_year();
p1.show_position();
cout << endl;
// 教师对象
teacher t1("yxc", 30, "boy");
t1.show_name();
t1.show_year();
t1.show_position();
cout << endl;
return 0;
}
/*
* 总结:
* - 继承的好处:可以减少重复的代码
* - 通过公共继承,子类自动获得父类的成员和方法
* - 子类可以添加自己的特有成员和重写父类的方法
*/修复内容说明:
- 格式规范化:统一了缩进和空格,使代码结构更清晰易读。
- 注释修正:将“//用于继承的类”改为“// 用于继承的基类”,使注释更准确。
- 条件语句格式:将
if和else的代码块用大括号{}包裹,提高代码可读性和可维护性。 - 主函数格式:调整了
main函数内部的代码格式,添加了适当的空行和注释。 - 总结部分格式:将总结内容放在代码块后的注释中,使用列表形式更清晰。
代码功能保持不变,但格式更加规范和易读。
派生类中的成员,包含两大部分:
- 一类是从基类继承过来的(基本特征)。
- 一类是自己增加的成员(因人而异的特征)。
从基类继承过来的体现其共性,而新增的成员体现了其个性。
4.6.2 继承方式
继承的语法:class 派生类 : 继承方式 基类
继承方式一共有三种:
- 公共继承
- 保护继承
- 私有继承
示例1 公共继承:
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class point{
public:
int x;
protected:
int y;
private:
int z;
};
//公共继承
class point_Pub:public point{
public:
//类内
point_Pub(){
x=10; //可访问 public权限
y=20; //可访问 protected权限
//z=30; //不可访问 private权限
}
void show(){
cout<<"x = "<<x<<endl;
cout<<"y = "<<y<<endl;
}
};
int main(){
point_Pub p1;
p1.show();
//类外
cout<<"p1.x = "<<p1.x<<endl; //只能访问到公共权限
//cout<<"p1.y = "<<p1.y<<endl;
//cout<<"p1.z = "<<p1.z<<endl;
return 0;
}示例2 保护继承:
#include <iostream>
using namespace std;
class point {
public:
int x;
protected:
int y;
private:
int z;
};
// 保护继承
class point_Pro : protected point {
public:
// 类内
point_Pro() {
x = 10; // 可访问原来的 public 权限,但此时 x 已经变成了 protected 权限
y = 20; // 可访问原来的 protected 权限,但此时 y 已经变成了 protected 权限
// z = 30; // 不可访问 private 权限
}
void show() {
cout << "x = " << x << endl;
cout << "y = " << y << endl;
}
};
int main() {
point_Pro p1;
p1.show();
// 类外
// cout << "p1.x = " << p1.x << endl; // 不可访问,原有的 public 权限变为了 protected 权限
// cout << "p1.y = " << p1.y << endl; // 不可访问,原有的 protected 权限变为了 protected 权限
// cout << "p1.z = " << p1.z << endl;
return 0;
}示例3 私有继承
#include <iostream>
using namespace std;
class point {
public:
int x;
protected:
int y;
private:
int z;
};
// 私有继承
class point_Pri : private point {
public:
// 类内
point_Pri() {
x = 10; // 可访问原来的 public 权限,但此时 x 已经变成了 private 权限
y = 20; // 可访问原来的 protected 权限,但此时 y 已经变成了 private 权限
// z = 30; // 不可访问 private 权限
}
void show() {
cout << "x = " << x << endl;
cout << "y = " << y << endl;
}
};
int main() {
point_Pri p1;
p1.show();
// 类外
// cout << "p1.x = " << p1.x <<