当前位置: 首页 > news >正文

基于C语言基础对C++的进一步学习_知识补充、组合类、类中的静态成员与静态函数、类中的常对象和常成员函数、类中的this指针、类中的友元

0、前言:

  • 这段时间,C++知识的总结有些太概括,有些知识需要细分析,没有时间做,这篇文章就先补充一些知识点的细究总结,再继续学习C++面向对象的基础知识。

1、知识补充:

1.1、浅拷贝和深拷贝:

  • 在 C++ 里,深拷贝和浅拷贝都是处理数据复制时的两种方式,主要区别在于是否真正 “复制” 了数据本身
#include<iostream>
#include<cstring>
using namespace std;
//创建类
class Per {char* name;int num;
public:Per(char* na, int nu); // 构造函数Per(const Per& a); // 拷贝构造函数实现深拷贝void get_name();void show_ip();
};
// 定义类中方法
Per::Per(char* na, int nu):name(na)
{num = nu;
}
Per::Per(const Per& a) 
{// 开辟空间(实现深拷贝)name = new char(strlen(a.name)+1);// 拷贝内容memcpy(name, a.name, strlen(a.name) + 1);
}
void Per::get_name()
{cout<<"name:" << name << endl;
}
void Per::show_ip() {cout << "name_ip:" << (int*)name << endl;
}int main() {// 没有写拷贝构造函数的时候,调用浅拷贝;//char a[] = "张三";//Per s1(a, 1);//Per s2(s1);//s1.get_name(); // name:张三//s1.show_ip(); // name_ip:00007FF68557BC10//s2.get_name(); // name:张三//s2.show_ip(); // name_ip:00007FF68557BC10// 写了拷贝构造函数的时候,调用拷贝;char a[] = "李四";Per s1(a, 1);Per s3(s1);s1.get_name(); // name:李四s1.show_ip(); // name_ip:000000EB6ACFF9B4s3.get_name(); // name:李四s3.show_ip(); // name_ip:000001F554DC6650/*通过对比可以发现,深拷贝是通过开辟空间的方式,两次虽然都进行了对象拷贝但是深拷贝得到地址明显和原来对象的空间地址不一样。*/}

1.2、在C++的类中“成员初始化列表”只能是构造函数(包括拷贝构造函数)来用:

  • ★★★在 C++ 类中,只有构造函数可以使用参数列表(成员初始化列表)的方式初始化成员变量,其他成员函数(包括普通成员函数、析构函数、静态成员函数等)都不能使用这种语法。

1.3、c++中对内存的划分:

  • 栈、堆、全局/静态存储区、常量存储区、代码存储区在硬件的RAM(内存)中,(少数特殊场景如嵌入式系统的代码可能在 ROM 中),通过操作系统对物理内存的逻辑划分,通过权限控制(只读 / 读写)、管理方式(自动 / 手动)、生命周期(临时 / 持久)来区分。硬件(内存 + CPU 的 MMU【内存管理单元】)负责将这些逻辑地址映射到实际的物理内存单元,并强制执行访问规则(比如写只读区会触发异常)。

1.4、定义类的时候,如果有常量,应该如何写构造函数?

  • 如下面代码所示,如果出现常量Pi,就不要在构造函数的声明当中写了,在构造函数或者拷贝构造函数定义的时候,通过“成员初始化列表”完成对Pi的赋值。
class Point {int x;int y;
public:Point(int x1 = 0, int y1 = 0);Point(const Point& p);int get_x();int get_y();
};
class Circle {const double Pi;Point center;double radius;
public://Circle(const double Pi1, Point center1, double radius);  // 常量就不要写在参数列表当中了Circle(Point center1, double radius);Circle(const Circle& p);
};
Circle::Circle(Point center1, double radius1) :Pi(3.14)
{center = center1;/*center = center1;这条代码会先默认构造 center(Point 类没有显式定义默认构造函数,编译器会自动生成一个)。再调用 operator= 进行赋值。*/radius = radius1;cout << "正在构造Circle" << endl;
}
Circle::Circle(const Circle& p) :Pi(p.Pi)
{center = p.center;   radius = p.radius;cout << "正在拷贝构造Circle" << endl;
}

1.3、★★★C++中为什么喜欢用引用传参?

  • 普通形参(值传递):会创建实参的完整副本,消耗与实参同等大小的内存。
  • 引用传参:不会赋值实参数据,仅通过地址绑定原变量,内存开销小(只存储地址),且能够直接操作原变量,尤其是在传递类中对象的时候,引用传递形参相对值传递更好一些。

1.4、类中成员函数参数的默认值写在哪里?

  • 答:写在声明中即可,定义中不能重复指定。
#include <iostream>
using namespace std;class MyClass {
private:int value;
public:// 构造函数声明:在这里指定参数默认值MyClass(int v = 10);  // 默认值10写在声明中void show() {cout << "value: " << value << endl;}
};// 构造函数定义:不需要再写默认值
MyClass::MyClass(int v) {  // 这里不能写成 int v = 10value = v;
}

2、组合类:把类嵌套到另一个类当中

2.1、 实现类的组合的前提条件:

  • 类的属性不仅可以是基本数据类型,也可以是类的对象;

2.2、 组合类中构造函数执行的顺序:

  • ★类中如果有多个内嵌对象,则组合类构造函数的执行顺序如下:如果在类中构造了该内嵌对象,则按照内嵌对象的声明顺序构造;如果在主函数中构造内嵌对象,构造顺序就是主函数中的构造顺序,【C++ 中,当一个类没有定义任何构造函数时,编译器会自动生成一个 “默认构造函数”(无参数、什么都不做)。但如果你手动定义了任何构造函数(哪怕只有一个带参数的),编译器就不会再自动生成默认构造函数了。】具体案例如下:
#include <iostream>
using namespace std;// 组件类A
class A {
private:int id;
public:A(int id) : id(id) {cout << "A" << id << " 被构造" << endl;}
};// 组件类B
class B {
private:int id;
public:B(int id) : id(id) {cout << "B" << id << " 被构造" << endl;}
};// 组合类C(内部声明内嵌对象)
class C {
private:// 内嵌对象声明顺序:A在前,B在后A a;B b;
public:// 构造函数初始化列表C(int aId, int bId) : a(aId), b(bId) {cout << "组合类C 被构造" << endl;}
};// 组合类D(不内部声明内嵌对象,而是通过参数传入)
class D {
private:A a;B b;
public:// 接收外部构造的A和B对象D(A aObj, B bObj) : a(aObj), b(bObj) {cout << "组合类D 被构造" << endl;}
};int main() {cout << "=== 场景1:内嵌对象在组合类内部构造 ===" << endl;C c(1, 2);  // 在C内部构造A1和B2cout << "\n=== 场景2:内嵌对象在主函数中构造 ===" << endl;B b3(3);    // 主函数中先构造B3A a4(4);    // 主函数中后构造A4D d(a4, b3);  // 将外部构造的对象传入Dreturn 0;
}
/*
=== 场景1:内嵌对象在组合类内部构造 ===
A1 被构造
B2 被构造
组合类C 被构造=== 场景2:内嵌对象在主函数中构造 ===
B3 被构造
A4 被构造
组合类D 被构造
*/

2.3、组合类函数构造函数使用“成员初始化列表”的理由:

  • ★★★组合类中关于构造函数踩过的坑:类会给定义未初始化的对象调用默认构造函数,把对象赋值给同一个类的另一个对象,就会默认调用拷贝函数;
#include <iostream>
using namespace std;// 定义类A
class A {
private:int id;
public:A(int a);A();
};
A::A(int a) {id = a;cout << "正在构造A:" << id << endl;
}
A::A() {id = 0;cout << "正在构造A:" << id << endl;
}// 定义B
class B {
private:int b;A a;
public:B(A a1, int b);
};
B::B(A a1, int b1) {a = a1;b = b1;cout << "正在构造B:" << b << endl;
}// 定义C
class C {
private:int c;A a2;
public:C(A a3, int b1);
};
C::C(A a3, int c1) :a2(a3), c(c1){cout << "正在构造C:" << c << endl;
}int main() {//首先通过案例说明在组合类中使用“普通初始化”时踩的坑A a(1);B b(a,2);/*执行结果:正在构造A:1正在构造A:0正在构造B:2解释:正在构造A:1,这个结果就是由:A a(1);代码而来;正在构造A:0,这个结果是B的构造函数中,a = a1;;先执行A a,构造a的时候,调用了A当中无参构造函数,生成的默认结果id = 0;然后再把传入到B的构造函数中的a(1)拷贝给a,再执行b=2,打印“正在构造B:2”,完成对B的构造,所以你会发现,这种方式在构造B的时候,会多构造一个a,还得有无参构造函数;*///最后通过案例说明应该如何正确给组合类“初始化”A a1(3);C c(a1, 4);/*执行结果:正在构造A:3正在构造C : 4解释:通过这种方式,就会发现,用“成员初始化列表”的方式,就能构造其中嵌入的类的对象,省去了再创建一个a2对象的麻烦;*/return 0;
}

3、类中的静态成员与静态函数:

3.1、什么是静态成员,什么是静态函数:

  • 静态成员:静态成员可以想象成 “类的共享变量”,所有对象都共用这一个变量,而不是每个对象各有一份。
  • 静态函数:它是 “类的共享函数”,在其访问权限内,通过类和对象都可以调用。

3.2、静态成员和静态函数的特点:

  • 静态成员的特点:1、类中普通成员在每一个对象中都有一个拷贝,存储不同的值,但是静态数据成员是每一个类中只有一个拷贝,这个类创建的所有对象都可以访问这个静态数据成员;书写格式:static 数据类型标识符 成员名; 2、静态成员是在类外进行赋值的,格式:数据类型标识符 类名::静态数据成员名=初始值; 3、在静态成员访问的权限之类,可以通过: 类名::静态数据成员名 访问。
  • 静态函数的特点:1、书写格式:声明时,在成员函数前面加static;2、只能访问静态数据成员和静态成员函数,不能访问非静态数据成员和非静态成员函数;3、可以通过:类名::静态函数的方式直接调用;【 ★★★理由:静态函数属于类级别,不绑定到任何具体对象,没有 this 指针(指向对象的指针),因此无法访问 “对象独有的非静态成员”; 而非静态成员和函数依赖 this 指针找到具体对象,所以静态函数不能调用它们。】
  • ★★★★静态成员和静态函数可以被任何对象访问和修改(只要权限允许,比如 public 修饰),但更重要的是:它们不需要通过对象也能直接访问(通过类名即可)。

3.3、案例演示:

  • 下面的案例,创建学生类,初始化具体学生对象的例子,演示静态数据成员和静态函数成员的特点;
#include<iostream>
using namespace std;class Student {char const* name;int age;int id;static int count;
public:static int a;  // 假设有个静态数据成员,经过验证,可以通过类名全局访问;Student(char const* na, int ag);void get_infor();// 静态函数static void get_count() {cout << "现在有:" << count << "人" << endl;//cout << age << endl;  // 这么写会报错;}
};
Student::Student(char const* na, int ag) :name(na)
{age = ag;id  = count+20500;count += 1;
}
// 静态函数
int Student::count = 0;
int Student::a = 1;
void Student::get_infor() {cout << "姓名:" << name << " 年龄:" << age << " id:" << id << endl;
}int main() {Student::a;  // 可以访问Student s1("s1", 16);Student s2("s2", 17);Student s3("s3", 18);cout << "---------------" << endl;s1.get_infor();s2.get_infor();s3.get_infor();s1.get_count(); // 现在有:3人Student::get_count(); // 现在有:3人return 0;
}

4、类中的常对象和常成员函数:

4.1、定义

  • 常对象:“被冻结的对象”—— 一旦创建,它的成员变量(普通成员)就不能被修改了,相当于给对象加了一把 “只读锁”;定义方式:const 类名 对象名;
  • 常成员函数:“保证不修改对象数据的函数”—— 这类函数内部不会(也不允许)修改对象的成员变量,是 “安全的只读操作”。(为了能够访问常对象的属性,C++引入的常成员函数)定义方式:函数类型 函数名(形参) const {函数定义}

4.2、特点:

  • C++禁止使用常对象来调用普通成员函数(因为普通成员函数有可能改变对象的属性值),常对象能不能调用类里面的数据成员关键还得看这个成员的属性,如果是public就可以访问。
  • 常函数可以访问静态成员和普通数据成员(只能访问不能修改),常函数可以访问静态数据成员,并且可以修改,但是常函数不可以访问普通函数(因为普通成员函数有可能改变对象的属性值),常函数可以调用静态函数。

4.3、案例演示

#include <iostream>
using namespace std;class Demo {
private:int normalNum;       // 普通私有成员(仅类内可访问)
public:static int staticNum; // 静态公有成员(类内外均可访问)// 构造函数Demo(int n) : normalNum(n) {}// 普通成员函数(可能修改成员)void setNormalNum(int n) {normalNum = n; // 允许修改普通成员}// 静态函数(属于类,可修改静态成员)static void setStaticNum(int n) {staticNum = n;}// 常成员函数void showInfo() const {// 访问普通私有成员(只读)cout << "普通私有成员(normalNum): " << normalNum << endl;// 访问并修改静态公有成员cout << "修改前静态成员(staticNum): " << staticNum << endl;staticNum++;cout << "修改后静态成员(staticNum): " << staticNum << endl;// 调用静态函数setStaticNum(100);cout << "静态函数修改后(staticNum): " << staticNum << endl;// 不能调用普通函数(编译错误)// setNormalNum(20); }
};// 初始化静态成员
int Demo::staticNum = 0;int main() {Demo obj(10);obj.setNormalNum(20); // 普通对象调用普通函数obj.showInfo();       // 普通对象调用常函数cout << "------------------------" << endl;const Demo constObj(30);constObj.showInfo();  // 常对象调用常函数// 常对象通过类名访问静态成员(public允许)Demo::setStaticNum(200);cout << "类外访问静态成员: " << Demo::staticNum << endl;// 常对象不能调用普通函数(编译错误)// constObj.setNormalNum(40); // 不能直接访问私有成员(编译错误)// cout << constObj.normalNum; return 0;
}
/*
普通私有成员(normalNum): 20
修改前静态成员(staticNum): 0
修改后静态成员(staticNum): 1
静态函数修改后(staticNum): 100
------------------------
普通私有成员(normalNum): 30
修改前静态成员(staticNum): 100
修改后静态成员(staticNum): 101
静态函数修改后(staticNum): 100
类外访问静态成员: 200
*/

5、类中的this指针:

5.1、定义

  • 引入:c++中的指针可以理解为 “地址标签”。想象你有很多个抽屉(就像电脑里的内存单元),每个抽屉都有自己的编号(内存地址),抽屉里放着东西(数据)。指针就好比你在纸上写下的某个抽屉的编号。当你想知道那个抽屉里有什么时,不用挨个找,直接看这个编号就能立刻找到对应的抽屉,取出里面的东西。this想象成每个对象自带的 "自我标签。 在方法内部,可以直接用 this->属性 来明确访问当前对象的成员,
  • 定义:简单说,this指针就是让对象在执行方法时,不会搞混 “操作的是自己还是别人”,确保每个对象都能正确处理自己的数据。

5.2、作用:解决参数名冲突时用this只是它的一个常见用法,而它的核心功能是让类的成员函数知道 “自己正在操作哪个具体对象”。

  • 1、区分成员变量和参数(最常用场景)
class Person {
private:string name;  // 成员变量
public:// 参数名也叫name,和成员变量重名了void setName(string name) {// this->name 明确表示"当前对象的name成员"this->name = name;  // 左边是成员变量,右边是参数}
};
  • 2、返回当前对象(实现链式调用)
#include <iostream>
using namespace std;class Calculator {
private:int result;
public:Calculator() { result = 0; }// 加法:返回当前对象(*this表示当前对象本身)Calculator& add(int num) {result += num;return *this;  // 返回当前对象,允许继续调用其他方法}// 乘法:同样返回当前对象Calculator& multiply(int num) {result *= num;return *this;}int getResult() { return result; }
};int main() {Calculator cal;// 链式调用:连续调用add和multiplyint res = cal.add(5).add(3).multiply(2).getResult();cout << "结果:" << res << endl;  // 计算过程:(0+5+3)*2=16,输出16return 0;
}
  • 3、比较当前对象和其他对象
#include <iostream>
using namespace std;class Student {
private:int id;  // 学号(假设唯一)
public:Student(int id) { this->id = id; }// 判断当前对象是否和另一个对象是同一个人bool isSame(Student& other) {// this指向当前对象,&other是另一个对象的地址return this->id == other.id;  }
};int main() {Student s1(1001);Student s2(1001);Student s3(1002);cout << "s1和s2是否相同:" << (s1.isSame(s2) ? "是" : "否") << endl;  // 是cout << "s1和s3是否相同:" << (s1.isSame(s3) ? "是" : "否") << endl;  // 否return 0;
}
  • 总结:理解 this 的关键是记住:谁调用成员函数,this 就指向谁。

6、类中的友元:

61、定义:

  • 友元(Friend)是 C++ 中一种特殊的访问机制,允许类外部的函数其他类访问当前类的私有(private)和保护(protected)成员。类通过 “声明友元”,主动向特定的外部元素开放自己的私有成员访问权限,就像给这些外部元素 “开了后门”。
  • 本质: 友元是类主动授予外部元素(函数或类)访问其私有 / 保护成员的权限,是对封装机制的补充(而非破坏)。

6.2、特点:

  • 书写格式:friend void externalFunc(MyClass& obj);
  • 注意其他类或者类外函数如果作为当前类的友元,必须在当前类中声明friend,在定义中不用加friend;
  • 友元关系是单向的(A 声明 B 为友元,不代表 B 自动把 A 作为友元)。
  • 友元关系不能传递(A 是 B 的友元,B 是 C 的友元,不代表 A 是 C 的友元)。
  • 友元关系不能继承(如果类 A 声明类 B 为友元,那么类 B 的派生类(比如类 C,继承自 B)不会自动成为类 A 的友元。类 C 无法直接访问类 A 的私有 / 保护成员,除非类 A 明确将类 C 也声明为友元;如果类 A 声明类 B 为友元,那么类 B 只能访问类 A 的私有 / 保护成员,不能自动访问类 A 的派生类(如类 C)的私有 / 保护成员,除非类 C 自己明确声明类 B 为友元。)
  • 总结:上面给友元加这么多限制,本身就是对类继承属性的一种保护性补充。
  • 注意: 友元声明不受类访问控制符(public/private/protected)的影响,可以放在类中的任何位置。

6.3、案例演示

#include <iostream>
#include <string>
using namespace std;// 前向声明,供友元声明使用
class Teacher;// 学生类
class Student {
private:string name;int score;public:Student(string n, int s) : name(n), score(s) {}// 声明友元函数:可以访问私有成员friend void showStudentInfo(Student& s);// 声明Teacher类为友元类friend class Teacher;
};// 教师类
class Teacher {
private:string name;public:Teacher(string n) : name(n) {}// 作为Student的友元,可以访问其私有成员void checkScore(Student& s) {cout << "教师" << name << "查看学生" << s.name << "的成绩:" << s.score << endl;}// 声明一个成员函数为其他类的友元(需要特殊语法)void assist(Student& s);
};// 友元函数定义(无需friend关键字)
void showStudentInfo(Student& s) {// 直接访问私有成员cout << "学生信息:" << s.name << ",成绩:" << s.score << endl;
}// 教师类成员函数的定义
void Teacher::assist(Student& s) {cout << "教师" << name << "辅导学生" << s.name << endl;
}// 测试友元的单向性:Teacher是Student的友元,但Student不是Teacher的友元
void testFriendDirection() {cout << "\n=== 测试友元单向性 ===" << endl;Student s("小明", 85);Teacher t("李老师");t.checkScore(s);  // 合法:Teacher是Student的友元// 下面代码会编译错误:Student不是Teacher的友元,不能访问其私有成员// cout << s.name << "访问教师姓名:" << t.name << endl;
}// 测试友元不可继承性
class SubTeacher : public Teacher {
public:SubTeacher(string n) : Teacher(n) {}void checkScore(Student& s) {cout << "代课教师查看学生成绩:";// 下面代码会编译错误:友元关系不能继承// cout << s.name << "的成绩:" << s.score << endl;cout << "无法直接访问学生成绩(友元不可继承)" << endl;}
};void testFriendInheritance() {cout << "\n=== 测试友元不可继承性 ===" << endl;Student s("小红", 90);SubTeacher st("王老师");st.checkScore(s);  // 子类无法继承友元权限
}int main() {// 测试友元函数Student s1("张三", 78);showStudentInfo(s1);  // 友元函数访问私有成员// 测试友元类Teacher t1("张老师");t1.checkScore(s1);// 测试友元特性testFriendDirection();testFriendInheritance();return 0;
}

总结:

1、弄清楚:普通成员,静态成员、静态函数、常函数、普通函数、类,常对象、普通对象他们之间的关系

  • 普通对象的调用权限其实是最大的,可以调用常函数、静态函数、静态数据成员及其他普通成员。
  • 常对象可以访问:常函数、静态函数、静态数据成员,至于能不能访问普通成员,要看这个成员访问权限是不是私有的,私有的(private)不可以,公有的(public)可以,常对象能修改的类中的数据成员只有静态成员。【区分读写权限和访问权限】
  • 如果普通对象遇到了同名函数,一个是普通函数,一个是常函数,优先调用普通函数。【这是因为普通对象可以调用这两种函数,但普通函数是更精确的匹配。】
  • 普通函数可以访问普通成员、常函数、静态成员。
  • 常函数可以访问静态成员和有访问权限的普通数据成员(只能访问不能修改),不可以访问普通函数。
  • 静态函数只能访问静态数据成员和静态成员函数。
  • 【记住:静态只认类,常函数 / 常对象在有访问权限的前提下只认读,普通对象 / 函数最自由。】

2、类当中访问权限和读写权限是两种权限:

  • 访问权限(public/private/protected),读写权限是看有没有const限制。

3、静态函数和常函数关键字位置剖析:

  • 静态函数中static只写在声明里,定义里不用写,因为静态函数修饰的是一个类。常函数中const,定义和声明中都要写,因为const是函数的一部分;
  • static写在函数前面,不会有歧义,const写在函数前面会有歧义,例如const int 函数名,可能导致误认为函数返回值是const int。
http://www.lryc.cn/news/626032.html

相关文章:

  • 网络编程day3
  • 机器翻译60天修炼专栏介绍和目录
  • 大模型问题:幻觉分类+原因+各个训练阶段产生幻觉+幻觉的检测和评估基准
  • 【技术揭秘】AI Agent操作系统架构演进:从单体到分布式智能的跃迁
  • Incredibuild 新增 Unity 支持:击破构建时间过长的痛点
  • Pygame第11课——实现经典打方块小游戏
  • 数据结构:二叉树oj练习
  • Linux------《零基础到联网:CentOS 7 在 VMware Workstation 中的全流程安装与 NAT 网络配置实战》
  • Apache ShenYu网关与Nacos的关联及如何配合使用
  • AJAX (一)
  • C# DevExpress控件安装使用教程
  • 【学习】Linux 内核中的 cgroup freezer 子系统
  • 【自动化运维神器Ansible】Playbook调用Role详解:从入门到精通
  • 常用css
  • 【C++】C++ 的护身符:解锁 try-catch 异常处理
  • 用java语言完成手写mybatis框架(第2章)
  • 借助AI将infoNES移植到HarmonyOS平台的详细方案介绍
  • Linux操作系统编程——进程间的通信
  • 极海APM32F107V6 gpio模拟串口
  • 决策树算法学习总结
  • 【Vivado TCL 教程】从零开始掌握 Xilinx Vivado TCL 脚本编程(三)
  • UML常见图例
  • 一文精通 Swagger 在 .NET 中的全方位配置与应用
  • Java NIO 核心精讲(上):Channel、Buffer、Selector 详解与 ByteBuffer 完全指南
  • 【3-3】流量控制与差错控制
  • Linux资源管理
  • JUC之CompletableFuture【上】
  • Orbbec---setBoolProperty 快捷配置设备行为
  • 设备树下的LED驱动实验
  • 从数据表到退磁:Ansys Maxwell中N48磁体磁化指南