本专栏记录C++学习过程包括C++基础以及数据结构和算法,其中第一部分计划时间一个月,主要跟着黑马视频教程,学习路线如下,不定时更新,欢迎关注。
当前章节处于:
---------第1阶段-C++基础入门
---------第2阶段实战-通讯录管理系统,
=====>第3阶段-C++核心编程,
---------第4阶段实战-基于多态的企业职工系统
---------第5阶段-C++提高编程
---------第6阶段实战-基于STL泛化编程的演讲比赛
---------第7阶段-C++实战项目机房预约管理系统
文章目录
- 一、 封装
- 1.1 封装基本概念
- 1.2 访问权限
- 1.3 成员属性设置成私有
- 二、对象特性
- 2.1 对象的初始化和清理
- 2.2 构造函数的分类与调用
- 2.3 构造函数调用规则
- 2.4 浅拷贝与深拷贝
- 2.5 初始化列表
- 2.6 类对象作为类成员
- 2.7 静态成员
- 三. C++对象模型和this指针
- 3.1 成员变量和成员函数分开存储
- 3.2 this指针概念
- 3.3 空指针访问成员函数
- 3.4 const修饰成员函数
类和对象是C++的核心,C++面向对象的三大特性:封装、继承、多态
,C++认为万事万物都是对象,对象尤其属性和行为。
一、 封装
1.1 封装基本概念
封装是C++面向对象三大特性之一,意义在于:
- 将属性和行为作为一个整体,表现生活中的事务
- 将属性和行为加以权限控制
语法:
class 类名{ 访问权限:属性/ 行为};
代码举例说明,设计一个圆类,计算圆的周长:
#include <iostream>
using namespace std;
#define PI 3.14
class Circle {
// 权限
public:
//属性
int r = 0;
// 行为
double getLength() {
return r * r * PI;
}
};
int main() {
// 实例化对象
Circle c1;
c1.r = 3;
cout << "圆的周长为:" << c1.getLength() << endl;
system("pause");
return 0;
}
圆的周长为:28.26
请按任意键继续. . .
创建类后需要实例化,才可以使用,相当于类是一个模具,实例化出来的对象才是产品。
#include <iostream>
using namespace std;
class Student {
// 权限
public:
// 属性
string name;
string cardnum;
// 行为
string getname() {
return name;
}
string getcardnum() {
return cardnum;
}
};
int main() {
// 实例化对象
Student stu;
stu.name = "张三";
stu.cardnum = "123";
cout << "姓名为:" << stu.getname() << " 学号为:" << stu.getcardnum() << endl;
system("pause");
return 0;
}
姓名为:张三 学号为:123
请按任意键继续. . .
1.2 访问权限
访问权限分为三种:
- public 公共权限:
类内可以访问,类外可以访问
- protected 保护权限
类内可以访问,类外不可以访问
- private 私有权限
类内可以访问,类外不可以访问
protected和private区别在于继承时,儿子可以继承父亲中的protected内容,不可以继承父亲中private中内容
#include <iostream>
using namespace std;
class Person {
public:
string name;
protected:
string car;
private:
string password;
public:
void test() {
// 类内都可以访问
name = "张三";
car = "自行车";
password = "12345";
}
};
int main() {
Person p1;
p1.name = "李四";// 类外可以访问
// p1.car; // 类外不可访问
// p1.password;// 类外不可访问
system("pause");
return 0;
}
struct也可以定义类,但是struct默认权限是公共权限,而class默认权限是公有;
#include <iostream>
using namespace std;
class test1 {
// 默认私有
int a = 0;
};
struct test2 {
// 默认公共
int b = 0;
};
int main() {
test1 test_1;
test_1.a = 10; // 不能访问
test2 test_2;
test_2.b = 20; // 可以访问
system("pause");
return 0;
}
1.3 成员属性设置成私有
优点在于:
- 将所有成员属性设置成私有,可以自己控制读写权限
- 对于写权限,可以检测数据的有效性
通过get和set方法进行访问和赋值
#include <iostream>
using namespace std;
class Student {
// 权限
private:
// 属性
string name="张三"; // 可读可写
string cardnum="123";// 只读
int score=0; // 只写
public:
// 可读可写
void setname(string inputname) {
name = inputname;
}
string getname() {
return name;
}
// 只读
string getcarnum() {
return cardnum;
}
// 只写
void setscore(int inputscore) {
score = inputscore;
}
};
int main() {
Student stu;
cout << stu.getname() << endl;
stu.setname("李四");
cout << stu.getname() << endl;
system("pause");
return 0;
}
张三
李四
请按任意键继续. . .
可以用get和set方法进行值的合法性判断。
练习案例1:设计立方体类
- 设计立方体类(Cube)
- 求出立方体的表面积和体积
- 分别用全局函数和成员函数判断两个立方体是否相等
全局函数需要传入两个实例化对象,成员函数只要传入一个实例化对象即可
#include <iostream>
using namespace std;
class Cube {
public:
double x=0.0f; // 边长
double y=0.0f; // 边长
double z=0.0f; // 边长
void setinf(double inputx, double inputy, double inputz) {
x = inputx;
y = inputy;
z = inputz;
}
int getS() {
return 3 * (x * y+x*z+y*z);
}
int getV() {
return x * y * z;
}
bool judge(Cube c2) {
if ((c2.x == x)&&(c2.y==y)&&(c2.z==z)){
return true;
}
else {
return false;
}
}
};
// 定义全局函数
bool g_judge(Cube c1, Cube c2) {
if ((c2.x == c1.x) && (c2.y == c1.y) && (c2.z == c1.z)) {
return true;
}
else {
return false;
}
}
int main() {
Cube c1,c2; // 创建
c1.setinf(1, 2, 3);
c2.setinf(2, 3, 4);
cout <<"c1表面积:"<< c1.getS() << endl;
cout <<"c1体积:"<< c1.getV() << endl;
// 成员函数判断
string flag = c1.judge(c2) ? "Yes" : "No";
cout <<"通过成员函数判断两立方体是否相等:"<< flag << endl;
// 全局函数判断
string g_flag = g_judge(c1,c2) ? "Yes" : "No";
cout << "通过全局函数判断两立方体是否相等:" << flag << endl;
system("pause");
return 0;
}
c1表面积:33
c1体积:6
通过成员函数判断两立方体是否相等:No
通过全局函数判断两立方体是否相等:No
请按任意键继续. . .
练习案例2:点和圆的关系
计算一个圆形类(Circle)和一个点类(Point),计算点和圆的关系
#include <iostream>
using namespace std;
class Circle {
public:
int x = 0;
int y = 0;
int r;
void set_inf(int inputx,int inputy,int inputr) {
x = inputx;
y = inputy;
r = inputr;
}
};
class Point {
public:
int P_x = 0;
int P_y = 0;
void set_inf(int input_P_x, int input_P_y) {
P_x = input_P_x;
P_y = input_P_y;
}
};
string judge(Circle c, Point p) {
int distance = sqrt((c.x - p.P_x)*(c.x - p.P_x) + (c.y - p.P_y)*(c.y - p.P_y));
if (distance == c.r) {
return "在圆上";
}
else if (distance < c.r) {
return "在圆内";
}
else {
return "在圆外";
}
}
int main() {
Circle c1;
Point p1;
c1.set_inf(1, 1, 1);
p1.set_inf(1, 0);
string flag = judge(c1, p1);
cout << flag << endl;
system("pause");
return 0;
}
在圆上
请按任意键继续. . .
代码比较多,可以分文件编写,分成Circle.cpp,Circle.h,Point.h,Point.cpp,cpp中放函数实现方法,.h中放函数申明
circle.cpp
#include "circle.h"
void Circle::set_inf(int inputx, int inputy, int inputr) {
x = inputx;
y = inputy;
r = inputr;
};
circle.h
#pragma once
#include <iostream>
using namespace std;
class Circle {
public:
int x = 0;
int y = 0;
int r;
void set_inf(int inputx, int inputy, int inputr);
};
point.cpp
#include "point.h"
void Point::set_inf(int input_P_x, int input_P_y) {
P_x = input_P_x;
P_y = input_P_y;
}
point.h
#pragma once
#include <iostream>
using namespace std;
class Point {
public:
int P_x = 0;
int P_y = 0;
void set_inf(int input_P_x, int input_P_y);
};
main.cpp
#include <iostream>
using namespace std;
#include "circle.h"
#include "point.h"
//class Circle {
//public:
// int x = 0;
// int y = 0;
// int r;
// void set_inf(int inputx,int inputy,int inputr) {
// x = inputx;
// y = inputy;
// r = inputr;
// }
//};
//class Point {
//public:
// int P_x = 0;
// int P_y = 0;
// void set_inf(int input_P_x, int input_P_y) {
// P_x = input_P_x;
// P_y = input_P_y;
// }
//};
string judge(Circle c, Point p) {
int distance = sqrt((c.x - p.P_x)*(c.x - p.P_x) + (c.y - p.P_y)*(c.y - p.P_y));
if (distance == c.r) {
return "在圆上";
}
else if (distance < c.r) {
return "在圆内";
}
else {
return "在圆外";
}
}
int main() {
Circle c1;
Point p1;
c1.set_inf(1, 1, 1);
p1.set_inf(1, 0);
string flag = judge(c1, p1);
cout << flag << endl;
system("pause");
return 0;
}
在圆上
请按任意键继续. . .
二、对象特性
2.1 对象的初始化和清理
C++面向对象来源于生活,每个对象都会有初始化设置以及对象销毁前的清理数据设置。对象的初始化和清理也是两个非常重要的安全问题。
- 一个对象或者变脸没有初始状态,对其使用后果是未知。
- 同样,使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。
C++利用构造函数
和析构函数
解决以上问题,两个函数会被编译器自动调用,如果不提供构造和析构,编译器会提供构造函数和析构函数的空实现。
- 构造函数:创建对象时为对象的成员属性赋值;
- 析构函数:对象销毁前系统自动调用,执行一些清理工作。
构造函数语法 类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号~
- 析构函数不可以有参数,因此不可以发成重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include <iostream>
using namespace std;
class Person {
public: // 加上作用域
Person() {
cout << "构造函数的调用!" << endl;
}
~Person() {
cout << "析构函数的调用!" << endl;
}
};
void test() {
Person p; // 在栈区创建实例化对象
}
int main() {
test();
system("pause");
return 0;
}
构造函数的调用!
析构函数的调用!
请按任意键继续. . .
2.2 构造函数的分类与调用
分类方式:
- 按参数分:有参构造和无参构造
- 按类型分:普通构造和拷贝构造
调用方式:
- 括号法
- 显示法
- 隐式转化法
#include <iostream>
using namespace std;
class Person {
// 构造函数
// 分类方式:有参构造、无参构造 普通构造和拷贝构造
// 无参构造
public:
Person() {
cout << "调用的为无参构造!" << endl;
}
// 有参构造
Person(int a) {
age = a;
cout << "调用的为有参构造!" << endl;
}
// 以上都为普通构造
// 拷贝构造
Person(const Person &p) {
age = p.age;
cout << "调用的为拷贝构造!" << endl;
}
int age;
};
int main() {
// 调用方式
// 1. 括号法
// 无参调用
Person p1;
// 有参调用
Person p2(10);
// 拷贝调用
Person p3(p2);
// 2.显示法
Person p4 = Person(10);
// Person(p4); 不可用拷贝构造 初始化匿名函数
Person p5 = Person(p4); // Person(p4)为匿名对象,当前行执行结束后,会立即被析构掉
// 隐式转化法
Person p6 = 10; //相当于Person p6 = Person(10)
system("pause");
return 0;
}
调用的为无参构造!
调用的为有参构造!
调用的为拷贝构造!
调用的为有参构造!
调用的为拷贝构造!
调用的为有参构造!
请按任意键继续. . .
拷贝构造函数使用时机
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 值方式返回局部对象
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "无参构造" << endl;
}
Person(int a) {
age = a;
cout << "有参构造" << endl;
}
Person(const Person &p) {
age = p.age;
cout << "拷贝构造" << endl;
}
int age;
};
void test() {
Person p1(10);
Person p2(p1); // 用一个已经创建好的对象来初始化一个新对象
cout <<"p1的年龄为:"<< p1.age << endl;
cout <<"p2的年龄为:"<< p2.age << endl;
}
void test2(Person p) {
}
Person test3() {
static Person p;
return p;
}
//void todo() {
// test3();
//}
int main() {
//Person p(10);
Person p = test3();
system("pause");
return 0;
}
无参构造
拷贝构造
请按任意键继续. . .
值传递的方式给函数参数传值和值方式返回局部对象都相当于将原来的值拷贝了一份,因此相当于把原来的拷贝了一份.
2.3 构造函数调用规则
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
可以这么理解: 拷贝构造>有参构造>无参构造,如果用户提供一个构造,编译器会自动补齐比他更高级的构造,不会提供比他更低级的构造。比如如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
#include <iostream>
using namespace std;
class Person {
public:
Person(int a) {
m_age = a;
}
int m_age;
};
int main() {
//Person p;// 报错
Person p(10); // 不报错
Person p2(p); // 不报错
system("pause");
return 0;
}
2.4 浅拷贝与深拷贝
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "无参构造" << endl;
}
Person(int a,int n) {
cout << "有参构造" << endl;
age = a;
// num = &n;报错
num = new int(n);
}
Person(const Person& p) {
cout << "拷贝函数" << endl;
age = p.age;
//num = p.num; // 报错
num = new int (*p.num);
}
// 析构函数
~Person() {
cout << "调用析构函数" << endl;
// 将指针开辟区域释放
if (num != NULL) {
delete num;
num = NULL;
}
}
int age;
int* num; // 指针在堆区开辟
};
void test() {
int num = 123;
// 有参构造
Person p1(10, num);
// 拷贝构造
Person p2(p1); // 报错
}
int main() {
test();
system("pause");
return 0;
}
有参构造
拷贝函数
调用析构函数
调用析构函数
请按任意键继续. . .
主要原因就是浅拷贝的地址被释放之后,二次释放。深拷贝会重新开辟一个地址,这样就不会再同一个地址释放多次。
2.5 初始化列表
语法:构造函数():属性1(值1),属性2(值2)…{}
#include <iostream>
using namespace std;
class Person {
public:
Person() {
}
Person(string n,int a,string ad):name(n),age(a),address(ad){
}
string name;
int age;
string address;
};
int main() {
Person p1("张三", 13, "安徽");
cout << "姓名:" <<p1.name<< " 年龄:" << p1.age<<" 住址:" << p1.address<<endl;
//Person p2("李四", 12); //报错,少参数
//cout << "姓名:" << p2.name << " 年龄:" << p2.age << " 住址:" << p2.address << endl;
system("pause");
return 0;
}
姓名:张三 年龄:13 住址:安徽
请按任意键继续. . .
2.6 类对象作为类成员
#include <iostream>
using namespace std;
class Student {
public:
string name;
int score;
Student(string n,int s):name(n),score(s){
cout << "Student有参构造" << endl;
}
~Student() {
cout << "Student析构函数" << endl;
}
};
class Teacher {
public:
string name;
Student stu;
Teacher(string t_n,string s_n,int s):name(t_n),stu(s_n,s){
}
};
void test() {
Teacher t1("李老师", "张三", 12);
cout << "老师姓名:" << t1.name << " 学生姓名:" << t1.stu.name << " 学生分数:" << t1.stu.score << endl;
}
int main() {
test();
system("pause");
return 0;
}
Student有参构造
老师姓名:李老师 学生姓名:张三 学生分数:12
Student析构函数
请按任意键继续. . .
2.7 静态成员
在成员变量和成员函数前加上关键字static
,成为静态成员,分为:
-
静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
-
静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
#include <iostream>
using namespace std;
class Person {
public:
string name;
static int score; // 静态成员变量,需要类内定义,类外初始化
void func() {
name = "张三";
score = 10;
cout << "调用函数func!" << endl;
}
static void func2() {
//name = "李四"; // 报错,静态成员函数只能调用静态成员变量
score = 200;
cout << "调用函数func2!" << endl;
}
};
int Person::score=100;
int main() {
Person p1;
cout <<"p1分数为:"<< p1.score << endl;
Person p2;
p2.score = 60;
cout << "p2分数为" << p2.score << endl;
cout << "p1分数为" << p1.score << endl;
// 直接使用类名调用静态成员变量
cout << "Person::score:" << Person::score << endl; // 注意不能使用. 而是使用::
// 静态成员函数
p2.func();
p2.func2();
system("pause");
return 0;
}
p1分数为:100
p2分数为60
p1分数为60
Person::score:60
调用函数func!
调用函数func2!
请按任意键继续. . .
三. C++对象模型和this指针
3.1 成员变量和成员函数分开存储
C++中类内成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。
#include <iostream>
using namespace std;
class Person {
public:
int a; // 是类的对象
static int b;// 不是类的对象
void func() {
}
static void func2(){
}
};
int Person::b = 0;
int main() {
Person p;
cout << "size of p=" << sizeof(p) << endl;
system("pause");
return 0;
}
size of p=4
请按任意键继续. . .
3.2 this指针概念
c++提供特殊的对象指针——this指针,this指针指向被调用的成员函数所属对象。this指针本质是一个指针常量,指向对象不能发生变化,指向对象的值可以发生变化。
- this指针时隐含每一个非静态成员函数内的一种指针
- this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
#include <iostream>
using namespace std;
class Person {
public:
int age;
Person(int age) {
//age = age; // 编译器认为这两个age是一样的
//1. 防止形参和成员变量同名
this->age = age; // 将形参age赋值给成员变量age
}
//2. 返回对象本身
// 如果不是引用类型的话,每一次都会创建一个新对象,而不是在原来的对象上进行累加
Person& Personadd(Person p) {
this->age += p.age;
return *this;
}
};
int main() {
Person p(10);
Person p1(20);
cout << "p年龄:" << p.age << endl;
cout << "p1年龄:" << p.age << endl;
p1.Personadd(p).Personadd(p); // 加两次
cout << "年龄:" << p1.age << endl;
system("pause");
return 0;
}
p年龄:10
p1年龄:10
年龄:40
请按任意键继续. . .
3.3 空指针访问成员函数
空指针可以调用成员函数,但是要注意有没有用到this指针。如果用到了this指针,需要加以判断保证代码的健壮性
#include <iostream>
using namespace std;
class Person {
public:
int age;
void func() {
cout << "func的调用" << endl;
}
void func2() {
// 添加判断 增强代码的健壮性
if (this == NULL) {
return;
}
cout << "年龄为" << age << endl; // age相当于this.age 如果是空指针的话会报错
}
};
int main() {
// 创建一个空指针
Person* p = NULL;
p->func();
p->func2(); // 用到this指针会报错
system("pause");
return 0;
}
func的调用
请按任意键继续. . .
3.4 const修饰成员函数
常函数
- 成员函数后加const后我们称这个函数为常函数
- 常函数不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中仍然可以修改
常对象
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
#include <iostream>
#include <iostream>
using namespace std;
class Person{
public:
string name; //成员变量
mutable int age; // mutable修饰使得常函数也能进行修改
// 无参构造函数
Person() {
}
// 定义一个成员函数
void test() {
cout << "调用test函数" << endl;
}
// 定义一个常函数
void func() const{
//name = "李四"; // 报错,不能修改正常的成员属性
age = 12; // 不报错,可以修改mutable修饰的成员变量
}
};
int main() {
// 创建一个常对象
const Person p;
//p.test();// 不可以调用正常的成员函数
p.func(); // 可以调用常函数
cout << "年龄为:" << p.age << endl;
system("pause");
return 0;
}
年龄为:12
请按任意键继续. . .