C++学习笔记(9)
学习是一件任重而道远的事情,与其焦虑不如动手起来,借助平台记录自己学习笔记,希望和大家多多交流,今天又是努力成为程序媛的一天!
17.类和对象
C++面向对象的三大特性为:封装、继承、多态
C++认为万物皆为对象,对象上有属性和行为
具有相同性质的对象,我们抽象称为类。
17.1 封装
17.1.1 封装的意义
封装是C++面向对象三大特性之一
封装的意义:
- 将属性和行为作为一个整体,表现万物万物即对象
- 将属性和行为加以权限控制
封装意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{ 访问权限:属性 / 行为 };
例1:设计一个圆类,求圆的周长
#include<iostream>
using namespace std;
//圆周率
const double& PI = 3.14;//定义常量
//设计一个圆类,求圆的周长
//圆求周长的公式 : 2 * pi *半径
class Circle {
//1.访问权限
//公共权限
public://这里是冒号
//2.属性
//半径
int r;
//3.行为
//获取圆的周长
//利用一个函数
double get_circle_ZC(){
return 2 * PI * r;
}
};
int main() {
//通过圆类 创建圆的对象
//cl就是一个具体的圆
Circle cl;
cl.r = 10;//给圆对象半径赋值
cout << "圆的周长为:" << cl.get_circle_ZC() << endl;
system("pause");
return 0;
}
运行结果:
例2:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
#include<iostream>
using namespace std;
#include<string>
//创建一个学生类
class Student {
//学生属性有学号和姓名
//1.权限,访问权限
public:
//2.属性
string s_name;//姓名
long long ID;//学号
//3.行为
//给学生姓名和学号赋值
//显示学生的姓名和学号
void getin(string name,long long id) {
s_name = name;
ID = id;//通过传进来的实参赋值给属性
cout << "学生姓名:" << s_name << "\t "
<< " 学号:" << ID << endl;
}
};
int main() {
//创建一个学生对象
Student s;
Student s1;
//给属性赋值
string name;
cout << "请输入学生姓名:" << endl;
cin >> name;
s.s_name = name;
long long id;
cout << "请输入学生学号:" << endl;
cin >> id;
s.ID = id;
s.getin(name,id);
//通过行为可以给类的属性赋值
s.getin("张三", 12);//通过实参传给形参,并给属性赋值,打印出来
system("pause");
return 0;
}
注意:
- 类中的属性和行为 我们统一称为 成员
- 属性 又称为 成员属性/成员变量
- 行为 又称为 成员函数/成员方法
封装意义二:
在设计类的时候,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
1.public 公共权限 类内可以访问 类外可以访问
2.protected 保护权限 类内可以访问 类外不可以访问
3.private 私有权限 类内可以访问 类外不可以访问
#include<iostream>
using namespace std;
#include<string>
//访问权限有三种:
//1.public 公共权限 类内可以访问 类外可以访问
//2.protected 保护权限 类内可以访问 类外不可以访问 可继承子类
//3.private 私有权限 类内可以访问 类外不可以访问 不可继承子类
class Person {
public:
string m_name;
protected:
int age;
private:
string password;
public://权限可以修改
void func() {
m_name = "Peter";
age = 20;//可以访问 因为是类内
password = "123456";
}
};
//类内都可以访问 类外主程序调用时看设置的权限
int main() {
//创建实例化对象
Person p;
//访问类内对象
p.m_name = "Walter";
//p.age = 25;//无法修改,因为这是类内的对象 权限为protected;
p.func();//因为设定的是public权限 所以可以访问类内这个函数
system("pause");
return 0;
}
17.1.2 struct和class区别
在C++中 struct和class唯一的区别就是默认的访问权限不同
区别:
- struct默认权限为公共
- class 默认权限为私有
#include<iostream>
using namespace std;
//创建类
class C1 {
int m_A;
};
//创建结构体
struct C2 {
int m_a;
};
int main() {
//struct和class区别
//struct 默认权限是 公共public
//class 默认权限是 私有private
//创建类实例化对象
C1 c1;
//创建结构体变量
C2 c2;
//访问
//c1.m_A = 100;//类访问权限默认是私有 不可访问
c2.m_a = 100;//结构体默认访问权限是 公共 可访问
system("pause");
return 0;
}
17.1.3 成员属性设置为私有
优点:
1.将所有成员属性设置为私有,可以自己控制读写权限
2.对于写权限,我们可以检测数据的有效性
#include<iostream>
using namespace std;
#include<string>
class Person {
//类默认权限设置为私有
//通过不同权限来访问其属性
public://想设置哪个属性什么权限,就给它设置对外接口
//设置姓名
void setName(string name) {
m_name = name;//将外面传进来的name赋值给成员属性m_name
}
//获取姓名
string showName() {
return m_name;
}
//设置年龄
void setage(int m_age) {
if (m_age < 0 || m_age > 120) {//判断输入数据的有效性
cout << "输入错误" << endl;
return;//退出
}
age = m_age;
}
//获取年龄
int getage() {
//int age = 0;
return age;
}
//设置性别
void setSex(string m_sex) {
sex = m_sex;
}
private:
//姓名 可读可写
string m_name;
//年龄 只读
int age;
//性别 只写
string sex;
};
int main() {
//成员属性设置为私有
//1.可以自己控制读写权限
//2.对于写可以检测数据的有效性
Person p1;//实例化
p1.setName("张三");//写
//p1.showName();
cout << "姓名为:" << p1.showName() << endl;//读
p1.setage(32);
//p1.age = 18;//不可修改 只能用公共权限的getage()函数进行只读操作
cout << "年龄为:" << p1.getage() << endl;//只读
p1.setSex("男");//只写
//cout << p1.setSex("男") << endl;//报错 无法输出 没有返回值 无输出 只读状态
system("pause");
return 0;
}
创建类时,可以用来控制所建属性的权限,设置读写权限,在类内创建相应行为作为对外接口
~ 练习1:设计立方体类(Cube)
- 求出立方体的面积和体积,分别用全局函数和成员函数判断两个立方体是否相等
#include<iostream>
using namespace std;
#include<string>
//设计立方体类
//求出立方体的面积和体积
//分别用全局函数和成员函数 判断两个立方体是否相等
class Cube {
public:
int setL(int L) {
m_L = L;
return m_L;
}
int setH(int H) {
m_H = H;
return m_H;
}
int setW(int W) {
m_W = W;
return m_W;
}
//面积函数
int getS(int m_L, int m_H, int m_W) {
return m_L * m_H * 2 + m_L * m_W * 2 + m_H * m_W * 2;
}
//体积函数
int getV(int m_L, int m_H, int m_W) {
return m_L * m_H * m_W;
}
//成员函数
bool isSame(Cube& c) {
if (m_L == c.m_L && m_W == c.m_W && m_H == c.m_H) {
return true;
}
return false;
}
private:
//定义立方体的长宽高
int m_L, m_H, m_W;
};
//全局函数
//判断是否相同函数
bool ifSame(Cube& cube1, Cube& cube2) {//用引用的方法就不用开辟新内存了
if (cube2.setH(10) == cube1.setH(10) && cube2.setL(10) == cube1.setL(20) && cube2.setW(10) == cube1.setW(10)) {//这里我将读写放在一起了 长宽高可以拆开写6个
cout << "两个立方体相同" << endl;
return true;
}
else {
cout << "两个立方体不一样" << endl;
return false;
}
}
int main() {
//创建实例化对象
Cube cube1;
//直接用一个函数赋予读写权限
//cube1.getspace(10, 25, 10);
cout << cube1.getV(10, 25, 10) << endl;
cout << cube1.getS(10, 25, 10) << endl;
//创建第二个实例化对象
Cube cube2;
//判断是否相等,把长宽高分开,因为要分别比较
cube2.setH(10);
int res = ifSame(cube1,cube2);//全局函数 需要两个形参 两个都带进去
cout << "bool值为:" << res << endl;//布尔数据类型函数返回的是0/1
int rs = cube1.isSame(cube2);//成员函数 在cube1类里行为判断和另一个是否相同 只需要一个形参
system("pause");
return 0;
}
- 全局函数需要引入两个形参,都是创建类,利用类控制权限public行为读写,然后全局函数将两个类作为形参传入,利用行为函数判断长宽高是否相等
- 成员函数是在一个类内基础上创建一个行为函数来判断与另一个是否相等,所以成员函数只需要一个形参即可,调用的时候利用该实例化类的行为即可
~ 练习2:点和圆的关系
- 设计一个圆形类(Circle)和一个点类(Point),计算点和圆的关系
#include<iostream>
using namespace std;
//一个点类
class point {
public:
void setx(int m_x) {
x = m_x;
}
int showx() {
return x;
}
void sety(int m_y) {
y = m_y;
}
int showy() {
return y;
}
private:
int x;//设置横坐标纵坐标
int y;
};
//设计一个圆形类
class Circle {
public:
void setr(int m_r) {
r = m_r;
}
int showr() {
return r;
}
void setCenter(point m_center) {
center = m_center;
}
//在一个类中可以把另一个类作为它的成员
point getCenter() {
return center;
}
private:
int r;//设置圆的半径
point center;//设置原点
};
//判断点和圆关系
void isInCircle(Circle& c1, point& p1) {
//计算两点之间距离 平方
int distance = (c1.getCenter().showx() - p1.showx()) * (c1.getCenter().showx() - p1.showx()) + (c1.getCenter().showy() - p1.showy()) * (c1.getCenter().showy() - p1.showy());
//计算半径的平方
int rdistance = c1.showr()* c1.showr();
//判断关系
if (distance == rdistance) {
cout << "点在圆上" << endl;
}
else if (distance > rdistance) {
cout << "点在圆外" << endl;
}
else {
cout << "点在圆内" << endl;
}
}
int main() {
//创建圆
Circle c1;
c1.setr(2);
point center;
center.setx(2);
center.sety(2);
c1.setCenter(center);
//创建点
point p1;
p1.setx(4);
p1.sety(5);
//判断
isInCircle(c1,p1);
system("pause");
return 0;
}
- 在一个类中可以引用另一个类作为成员
- 遇到多个类时,通常会把类作为头文件分文件编写
- 例如此题,在头文件分别编写圆类和点类的头文件,在类的头文件里只需要对类里成员函数进行声明,分号结尾,如这样:
1.添加相关类的头文件.h
这是point.h
#pragma once
#include<iostream>
using namespace std;
class point {
public:
void setx(int m_x);
int showx();
void sety(int m_y);
int showy();
private:
int x;//设置横坐标纵坐标
int y;
};
这是circle.h
#pragma once
#include<iostream>
using namespace std;
#include "point.h"
class Circle {
public:
void setr(int m_r);
int showr();
void setCenter(point m_center);
//在一个类中可以把另一个类作为它的成员
point getCenter();
private:
int r;//设置圆的半径
point center;//设置原点
};
point报错是因为这个类在这里没有,可以在头文件加上头文件引入即可
2.编写相关类的.cpp文件
这里把circle和point类具体行为函数写成独立的.cpp文件,只需要把类里行为函数写出来即可,其他都不要,记得开头加上相应类的头文件,这样一改就变成全局函数了,所以在函数名前面加个局部定义域,告诉是在xxx作用域下的函数
circle.cpp
#include "circle.h"
void Circle::setr(int m_r) {
r = m_r;
}
int Circle::showr() {
return r;
}
void Circle::setCenter(point m_center) {
center = m_center;
}
point Circle::getCenter() {
return center;
}
point.cpp
#include "point.h"
void point::setx(int m_x) {//告诉是point作用域下的函数
x = m_x;
}
int point::showx() {
return x;
}
void point::sety(int m_y) {
y = m_y;
}
int point::showy() {
return y;
}
3.主程序里调用
主程序只剩判断函数和主函数调用部分了,在主程序里头文件要加上这两个类的头文件即可
#include<iostream>
using namespace std;
#include "point.h"
#include "circle.h"
//判断点和圆关系
void isInCircle(Circle& c1, point& p1) {
//计算两点之间距离 平方
int distance = (c1.getCenter().showx() - p1.showx()) * (c1.getCenter().showx() - p1.showx()) + (c1.getCenter().showy() - p1.showy()) * (c1.getCenter().showy() - p1.showy());
//计算半径的平方
int rdistance = c1.showr()* c1.showr();
//判断关系
if (distance == rdistance) {
cout << "点在圆上" << endl;
}
else if (distance > rdistance) {
cout << "点在圆外" << endl;
}
else {
cout << "点在圆内" << endl;
}
}
int main() {
//创建圆
Circle c1;
c1.setr(2);
point center;
center.setx(2);
center.sety(2);
c1.setCenter(center);
//创建点
point p1;
p1.setx(4);
p1.sety(5);
//判断
isInCircle(c1,p1);
system("pause");
return 0;
}
结果相同
17.2 对象的初始化和清理
C++中的面向对象来源于生活,每个对象都会有初始设置以及对象销毁前的清除数据的设置
17.2.1 构造函数和析构函数
- 注意:构造函数和析构函数都是系统自动调用的,如果不提供构造和析构,编译器会调用空文件的构造函数和析构函数,如果在其中编写程序会自动调用,而且只调用一次
C++中,定义一个类后, 编译器自动提供三个函数:
1,默认构造函数(空实现)
2,默认析构函数(空实现)
3,默认拷贝构造函数(值拷贝)
#include<iostream>
using namespace std;
//对象的初始化和清理
class Initional {
public://注意加个公共权限
//1.构造函数
Initional() {
cout << "这是构造函数" << endl;
}
//2.析构函数
~Initional() {
cout << "这是析构函数" << endl;
}
};
void test01() {
Initional ex1;
};
int main() {
test01();
system("pause");
return 0;
}
如果在主函数里创建对象,结果如下:
int main() {
//test01();
Initional ex1;
system("pause");
return 0;
}
注意事项:
1,若用户自定义有参构造函数, 编译其不再提供默认无参构造函数, 仅提供析构函数和默认拷贝构造函数
2,若用户自定义拷贝构造函数, 编译其不再提供构造函数, 仅提供析构函数
3,这里如果把main()函数里面的test01()注释掉,在主函数创建类实例化对象,析构函数就不会调用,因为这是在main()函数上调用的,存储在栈区里,会在函数里开辟一个空间存储,在函数执行完毕后就会释放销毁该对象,放在main()函数中创建后,下一步的语句是system:请按任意键继续,再按就退出窗口了,函数执行完毕才会有析构函数执行,当然,在窗口关闭一瞬间后还是可以看到析构函数的调用
17.2.2 构造函数的分类及调用
两种分类方式:
- 按照参数分:有参构造和无参构造
- 按照类型分:普通构造和拷贝构造
三种调用方式:
括号法,显示法,隐式转换法
#include<iostream>
using namespace std;
//1.构造函数的分类及调用
//分类
//参数分类:有参,无参(默认)
//类型分类:普通构造、拷贝构造
class Person {
public:
//构造函数
Person() {
cout << "这是无参构造函数的调用" << endl;
}
Person(int a) {
age = a;
cout << "这是有参构造函数的调用" << endl;
}
//拷贝构造函数
Person(const Person &p) {//以引用的方式传进来且不可误改原值
age = p.age;//将传入的对象所有属性拷贝到当前对象
cout << "拷贝构造函数调用" << endl;
}
//析构函数
~Person() {
cout << "这是析构函数的调用" << endl;
}
int age;
};
//调用:1.括号法,2.显示法 3.隐式转换法
void test01() {
//1.括号法
Person P1;//默认构造函数调用
Person P2(10);//有参构造函数
Person p3(P2);//拷贝构造函数
//2.显示法
//Person p1;
//Person p2 = Person(10);//有参构造
//Person p3 = Person(p2);//拷贝构造
Person(10);//等号右侧单独拿出来就是匿名对象 特点:当前行执行结束后,系统会立即回收匿名对象
cout << "aa" << endl;//这是有参构造函数的调用//这是析构函数的调用//aa //在打印aa之前就会把匿名对象的析构函数打印出来即匿名对象该行执行结束后会立即收回
//3.隐式转换法
Person p4 = 10;//相当于写了 Person p4 = Person(10);有参构造
Person p5 = p4;//拷贝函数 会自动把隐式转换为显式
}
int main() {
test01();
system("pause");
return 0;
}
- 几点注意事项:
- 1.调用默认构造函数时,不要加(),因为Person p1();
会被编译器看作是一个函数的声明,不会认为在创建对象。
- 2.不要利用拷贝构造函数 初始化匿名对象 编译器会把Person(p3) === Person p3;
会看作对象声明。
17.2.3 拷贝构造函数调用时机
C++中调用拷贝构造函数通常有三种情况:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#include<iostream>
using namespace std;
//构造函数和析构函数
class Person {
public:
//默认构造函数
Person() {
cout << "这是默认构造函数的调用" << endl;
}
Person(int age) {
m_Age = age;
cout << "这是有参构造函数的调用" << endl;
}
Person(const Person& p) {
m_Age = p.m_Age;
cout << "这是拷贝函数的调用" << endl;
}
~Person() {
cout << "这是析构函数的调用" << endl;
}
int m_Age;
};
void test01() {
//1.使用一个已经创建完毕的对象来初始化一个新对象
Person p1(20);
Person p2(p1);
}
void Work(Person p) {
}
void test02() {
//2.值传递的方式给函数参数传值 //拷贝临时的副本 不会影响下面原本的值 所以这里修改p的值并不会影响到下面p的值 这两个是不一样的
Person p;
Work(p);
}
//3.值方式返回局部对象
Person Work2() {
Person p3;
cout << (int*)&p3 << endl;
return p3;//这里是值方式传递 不会返回上面p3
}
void test03() {
Person p = Work2();
cout << (int*)&p << endl;//加*返回16进制,不加返回10进制
//这里是拷贝函数调用 但是上述p3本体和p并不是一个,Work2()调用过后p3就会被释放,释放后无法调用,p3本体已死 ,return p3是克隆体,所以函数接受的是克隆体的数据,于是就创造一个拷贝函数返回给p
//注意:这里编译器开了返回值优化(RVO),只有一个地址 所以最终结果是地址一样的
}
int main() {
//test01();
//test02();
test03();
system("pause");
return 0;
}
test01()结果:
test02()结果:
test03()结果:
这里注意编译器进行了优化,返回的是同一个地址,可参考编译器之返回值优化
预期结果:
17.2.4 构造函数调用规则
默认情况下,C++编译器会至少给一个类添加3个函数:
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
#include<iostream>
using namespace std;
// 构造函数和析构函数
class Person {
public:
//默认构造函数
Person() {
cout << "这是默认构造函数的调用" << endl;
}
Person(int age) {
m_Age = age;
cout << "这是有参构造函数的调用" << endl;
}
/*Person(const Person& p) {
cout << "这是拷贝函数的调用" << endl;
m_Age = p.m_Age;
}*/
~Person() {
cout << "这是析构函数的调用" << endl;
}
int m_Age;
};
void test01() {
Person p1;
p1.m_Age = 18;
Person p2 = Person(p1);
cout << "p2的年龄为:" << p2.m_Age << endl;
}
void test02() {
//如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
Person p3;//如果这里把定义的无参构造注释掉,代码会报错,因为系统不存在默认构造函数了
p3.m_Age = 28;
Person p4(p3);
cout << "p4的年龄为:" << p4.m_Age << endl;//拷贝构造还是会提供,这里就把定义的拷贝函数注释掉,打印拷贝对象属性是对的,即此时拷贝函数系统默认存在,但是不会打印定义的拷贝函数了,还是会默认执行17行的操作,做了简单的值拷贝
}
void test03() {
//如果用户定义拷贝构造函数,C++不会再提供其他构造函数
//Person p5;//把定义的默认构造和有参构造注释掉,此行就会报错 无论默认还是有参调用
}
int main() {
//test01();
test02();
//test03();
system("pause");
return 0;
}
test01()结果为:
test02()结果为(去掉定义的拷贝函数):
17.2.5 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#include<iostream>
using namespace std;
//深拷贝与浅拷贝
class Person {
public:
//默认构造函数
Person() {
cout << "这是默认构造函数的调用" << endl;
}
Person(int age,int H) {
m_Age = age;
m_H = new int(H);//new在堆区建立的数据要用指针来接收,因为它返回的是一个地址
cout << "这是有参构造函数的调用" << endl;
}
Person(const Person& p) {
cout << "这是拷贝函数的调用" << endl;
m_Age = p.m_Age;
//m_H = p.m_H;//这为编译器默认实现的赋值代码 默认拷贝的时候会把m_H存储的数据即堆区身高数值的地址给它 这样析构的时候他们指向的就是同一个地址所以报错
//深拷贝操作
m_H = new int(*p.m_H);//把指针解引用并开辟一个新的地址给它
}
~Person() {
//析构函数,进行堆区开辟数据的释放
if (m_H != NULL) {
delete m_H;//释放堆区数据
m_H = NULL;//防止野指针
}
cout << "这是析构函数的调用" << endl;
}
int m_Age;
int* m_H;//用指针接收
};
void test01() {
Person p1(18,160);
cout << "p1的年龄为:" << p1.m_Age << " " << "身高为:" << *p1.m_H << endl;//这里把H前面带*是解指针,得到指向地址的值
Person p2 = Person(p1);//栈里的数据,遵循先进后出原则,所以p2会先被释放,先构造的后析构
cout << "p2的年龄为:" << p2.m_Age << " " << "身高为:" << *p2.m_H << endl;
}
int main() {
test01();
//test02();
//test03();
system("pause");
return 0;
}
*注意:*直接进行赋值操作,为浅拷贝,带来的问题是,如果在堆区创建数据,利用拷贝构造创建的数据在析构释放时,原数据和拷贝数据都会指向同一个地址,当拷贝数据释放该地址后,原来数据再次执行析构函数时,就不存在那个地址,出现访问报错,非法操作,要解决这个问题,用深拷贝。解决方法:自己实现拷贝构造函数,解决浅拷贝的问题
注意:
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
17.2.6 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)...{}
#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(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {
}
int m_A;
int m_B;
int m_C;
};
void test() {
//Person p(10, 20, 30);
Person p(20, 30, 60);
cout << "m_A = " << p.m_A << endl;
cout << "m_B = " << p.m_B << endl;
cout << "m_C = " << p.m_C << endl;
}
int main() {
test();
system("pause");
return 0;
}
传统构造函数初始化结果:
上述代码结果:
- 传统赋值初始化相当于先声明类,再做赋值操作,初始化列表相当于直接声明一个有初始值的类型,省略了赋值操作。在大型项目中,class类成员变量极多时,初始化列表效率更高。该方法可以用在类嵌套中直接对类中类进行赋值,只需调用一次函数实现多个类不同属性初始化。
17.2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员。
如:
#include<iostream>
using namespace std;
#include<string>
//类里有另一个类如何初始化参数和调用的
// 类里类构造和析构顺序如何
//类对象作为类成员
class A {
public:
//构造函数
A(int m_Friend) {
Friend = m_Friend;
cout << "This is A 构造函数的调用" << endl;
}
//析构函数
~A() {
cout << "This is A 析构函数的调用" << endl;
}
int Friend;
};
class B {
public:
//构造函数
B(string m_name,int m_a):name(m_name),a(m_a) {//隐式转换法 A a = m_a;
cout << "This is B 构造函数的调用" << endl;
}
//析构函数
~B() {
cout << "This is B 析构函数的调用" << endl;
}
//虽然这里a是一个类,但是B里的构造函数调用时用的是int 类型却没有报错 这里相当于做了个隐式转换
//A a
string name;
A a;//一个类里放进另一个类
};
//当类中成员是其他类对象时,我们称该成员为对象成员
//构造的顺序为 先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
void test() {
B b("张三", 20);
cout << b.name << "今年" << b.a.Friend << "岁" << endl;
}
int main() {
test();
system("pause");
return 0;
}
17.2.8 静态成员
- 定义:静态成员就是在成员变量和成员函数前面加上关键字static,称为静态成员
- 静态成员分为:
- 静态成员变量(全局区)
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量
- 静态成员变量(全局区)
#include<iostream>
using namespace std;
//创建一个类
//静态成员变量
class Person {
public:
//所有对象共享同一份数据
//在编译阶段分配内存
//类内声明,类外初始化
//创建静态成员变量
//static int m_age = 10;//这样定义为错,这里不是静态变量,而是类里静态成员变量,所以要满足类内声明,类外初始化
//类内声明
static int m_age;
int b;
//静态成员函数
static void func() {
//所有对象共享一个函数
//静态成员函数只能访问静态成员变量
cout << "This is static func()" << endl;
m_age = 500;//在成员函数内 调用该函数会输出执行该行
cout << "func() m_age " << m_age << endl;
//b = 20;//静态成员函数只能访问静态成员变量 因为非静态成员变量是对象属性的 这里没有指定对象 不清楚是赋予谁 而静态成员变量 不是某个对象属性 属于全局变量 可以改
}
private:
static int m_A;//静态成员变量也是有访问权限的
//静态成员函数也有访问权限
};//类后面是有冒号的!!
//类外初始化
int Person::m_age = 20;//加上作用域,限定哪个的变量,否则就是全局变量了
int Person::m_A = 10;
void test() {
//创建类对象
Person p;
cout << p.m_age << endl;//20
Person p2;
p2.m_age = 50;
cout << p.m_age << endl;//50 //用另一个类对象把静态成员变量值改了 其他对象访问的时候也变了 说明他们是共享这一份数据的
//静态成员变量 不属于任何一个对象属性 所有对象共享一份数据
//静态成员变量有两种访问形式
//1.创建对象进行访问(如上)
//2.通过类名进行访问
cout << Person::m_age << endl;//非静态成员都要依赖于对象 //50
//cout << Person::m_A << endl; //静态成员变量的私有权限 类外无法访问
//静态成员函数访问方式同静态成员变量 也有两种 它同静态成员变量一样 不属于任何一个对象属性
//1.通过对象访问
p.func();
//2.通过类名进行访问
Person::func();
}
int main() {
test();
system("pause");
return 0;
}
第九篇笔记到此结束,C++基础学习会持续更新在C++学习笔记合集中,当作学习笔记复习,如果能帮助其他小伙伴就更好了。
笔记是看黑马程序C++时做的记录,笔记中如果有错误和改进的地方,欢迎大家评论交流,up up up!!!
学习原视频来自:黑马程序员C++从0到1