📋 前言
🌈个人主页:Sarapines Programmer
🔥 系列专栏:本期文章收录在《C++闯关笔记》,大家有兴趣可以浏览和关注,后面将会有更多精彩内容!
⏰翰墨致赠:文墨扬长风,情随碧波舞。江山孕雄心滚滚,志立云霄梦遨游。笔翰激雷鸣,豪情犹似澎湃浪涛,扬帆挟梦翱翔云际。
🎉欢迎大家关注🔍点赞👍收藏⭐️留言📝
🔔作者留言:欢迎来到我的【C++】魔法学堂!这里是探索编程世界的秘境,我的学习笔记博客为你打开C++的魔法之门。在这里,我不仅分享C++的基础知识和高级技巧,还有着涉猎实用技术和项目经验的魔法药水。无论你是新手还是编程巫师,这个魔法堂会为你施展出奇幻的学习魔法,帮助你在C++的魔法森林中踏上一场奇妙之旅。准备好了吗?跟着我,让我们一起编织属于自己的魔法代码!
目录
📋 前言
⛳️第一章 C++入门
1.1 基本知识
1.2 练习
⛳️第二章 基本数据类型和输入输出
2.1 数据类型
2.2 输入输出
⛳️第三章 表达式和语句
3.1 基础知识
⛳️第四章 过程化语句
⛳️第五章 函数
5.1 基本知识
5.2 内联函数 inline
⛳️第六章 程序结构
⛳️第七章 数组
⛳️第八章 指针
8.1 基本知识
8.2 new、delete用法
⛳️第九章 引用
9.1 基本知识
9.2 练习
⛳️第十章 结构
⛳️第十一章 类
11.1 基本知识
11.2 练习
⛳️第十二章 构造函数
12.1 基本知识
12.2 练习
⛳️第十三章 面向对象程序设计
13.1 基本知识
13.2 练习
⛳️第十四章 堆与拷贝构造函数
14.1 基本知识
14.2 堆练习
14.3 拷贝构造函数练习
⛳️第十五章 静态成员与友元
15.1 基本知识
15.2 练习
⛳️第十六章 继承
16.1 基本知识
16.2 练习
⛳️第十七章 多态
17.1 基本知识
17.2 练习
⛳️第十八章 运算符重载
18.1 基本知识
18.2 练习
⛳️第十九章 I/O流
19.1 基本知识
19.2 练习
⛳️第二十章 模板
20.1 基本知识
20.2 练习
📝总结
⛳️第一章 C++入门
1.1 基本知识
1. C++是面向对象编程(OOP),特点如下:
- 封装和数据隐藏
- 继承和重写
- 多态
2. main()函数的返回类型可以是任意的数据类型,而且是唯一一个非void型【 即void main()】可以不用return,因为main()由操作系统直接控制,不能被其他函数调用。
3. '0'与"0"与0不同
- '0' 是字符,占1个字节
- "0"是字符串,占2个字节(末尾加上'\0')
- 0 是整型,占4个字节;等价于'\0' NULL
4. 常量在定义时必须初始化,如
const float pi = 3.14; //正确 而对于 const float pi; //error pi = 3.14; //error
5. 结构化程序设计=功能分解+逐步求精
6. 变量命名:
驼峰原则:myCar
匈牙利表记法:imyCar //imyCar表示为int型的imyCar
1.2 练习
【例1.1】hello world!
#include<iostream>
using namespace std;
int main(){
cout<<"hello world!"<<endl;
return 0;
}
【例1.2】3*a-2*b+1
#include <iostream>
using namespace std;
int main()
{
int a,b;
cin>>a>>b;
cout<<"3a-2b+1="<<3*a-2*b+1<<endl;
return 0;
}
【例1.3】最大值的平方根
强制转换:static_case<double>(int 型) ; 而不能是double(int 型);
#include <iostream>
#include <cmath>
using namespace std;
int max(int a,int b);
int main()
{
int a,b;
cin>>a>>b;
cout<<"Max value's sqrt is "<<sqrt(static_cast<double>(max(a,b)))<<endl;
return 0;
}
int max(int a,int b)
{
return a>b?a:b;
}
⛳️第二章 基本数据类型和输入输出
2.1 数据类型
见【C语言】第三章 3.2
注意typedef可以增加数据类型的别名
typedef int INT;
//int a=10;
INT a=10;
2.2 输入输出
输出控制
添加头文件#include <iomanip> + #include <iostream>
注意
- 输出%,则printf("%%");
setprecision、fixed
会影响接下来的所有浮点数的输出,直到下个setprecision、fixed
出现为止。setprecision(2)表示精确2位,如 11.235 则输出 11 3.14 输出 3.1
要求精确到小数点后n位,使用cout<<fixed<<setprecision(n)<<value;
/*输入输出简单示例*/
int a;
cin>>a;
cout<<"the value a is"<<a<<endl;
/*限定输出格式*/
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double a = 11.2345;
cout << setprecision(2) << a << endl; // 精确输出2个,这里输出11
cout << fixed<<setprecision(2) << a << endl; // 保留2位小数,输出11.23
cout << setw(8) << a << endl; // 控制输出宽度为8,右对齐(默认)
cout << right << setw(8) << a << endl; // 控制输出宽度为8,右对齐
cout << left << setw(8) << a << endl; // 控制输出宽度为8,左对齐
}
/*输出
11
11.23
11.23
11.23
11.23
*/
⛳️第三章 表达式和语句
3.1 基础知识
- 左值和右值:C/C++面试题之语言基础篇(一)-CSDN博客
- 算术运算类型的转换朝着更精确的方向进行。如1.0/2=浮点型数据
⛳️第四章 过程化语句
过程化语句和C语言一样:
- while语句
- do ...while语句
- for语句
详见第五章:【C语言】自学终极笔记-CSDN博客
【例】利用公司ㄇ=4*(1-1/3+1/5-1/7...),直到最后一项的绝对值<1e-8为止
#include<iostream>
#include<cmath>
using namespace std;
double Pi_value(double n);
int main(){
double n=1e-8;
cout<<Pi_value(n)<<endl;
return 0;
}
double Pi_value(double n)
{
double sum=1;
double k=1;
int sign=-1;
int i=3;
for(;abs(k)>=n;){
k=1.0/i;
sum=sum+sign*k;
sign=(-1)*sign;
i=i+2;
}
return sum*4;
}
【例】给定某数,判断是否为素数
#include<iostream>
using namespace std;
int SuNum(int n);
int main(){
int n;
cin>>n;
int flag=SuNum(n);
if(flag>0){
cout<<n<<"是素数"<<endl;
}
else {
cout<<n<<"不是素数"<<endl;
}
return 0;
}
int SuNum(int n)
{
for(int i=2;i<n;i++){
if(n%i==0) {
return -1;
}
}
return 1;
}
⛳️第五章 函数
5.1 基本知识
函数原型:即函数声明,如 int max(int a,int b); 等价于 int max(int ,int );
为什么用函数声明做函数原型? 便于对函数调用的合法性进行检查
- 函数分两种:标准库函数+用户自定义函数
- 函数定义=函数声明+函数体
重载函数至少在参数个数、参数类型、参数顺序有所不同。
错误示例:
void func(int); int func(int);//返回类型不同则无法实现重载
默认参数的函数:
- 有默认值的参数应该位于参数列表的右侧
默认参数应该从右向左设置: 默认参数的赋值应该从右边的参数开始,不能跳过某个参数。在上述示例中,首先给
name
设置了默认值,然后是age
。默认参数只能在函数声明中出现一次: 默认参数只能在函数声明中出现一次,而不应该在函数定义中重复提供默认值。
/*默认参数的函数*/
#include <iostream>
using namespace std;
// 函数声明,参数有默认值,默认参数的赋值应该从右边的参数开始
void greet(string name = "Guest", int age = 25);//ok
//void greet(string name, int age = 25);//ok
//void greet(string name = "Guest", int age);//error
int main() {
greet();//Hello, Guest! Age: 25
greet("John");//Hello, John! Age: 25
greet("Alice", 30);//Hello, Alice! Age: 30
return 0;
}
// 函数定义,注意参数的默认值只需要在声明处提供就好,否则报错
void greet(string name, int age) {
cout << "Hello, " << name << "! Age: " << age << endl;
}
5.2 内联函数 inline
内联函数和宏函数 | 函数的区别:C/C++面试题之语言基础篇(一)-CSDN博客
#include <iostream>
// 声明内联函数
inline int add(int a, int b) {
return a + b;
}
int main() {
int x = 5, y = 10;
// 调用内联函数
int result = add(x, y);
std::cout << "Sum: " << result << std::endl;
return 0;
}
⛳️第六章 程序结构
预处理程序:#include、#define、#if
其中#include两种格式:
- #inlcude <xxx>
- #inlcude "xxx"
区别:如果头文件是由开发者创建并与源文件位于同一项目中,使用双引号格式是比较常见的。而如果是标准库或系统提供的头文件,使用尖括号格式更为合适。
⛳️第七章 数组
详见:【C语言】自学终极笔记第六章
⛳️第八章 指针
8.1 基本知识
详见:【C语言】自学终极笔记第八章
8.2 new、delete用法
堆上创建内存分配使用new、delete【区别于C语言的malloc、free】
new/delete注意:
- 创建单个元素:int *a=new int;
- 创建一维数组:int *arr=new int[n];//n为数组具体大小
- 创建二维数组:
int **arr=new *int[n];//先创建arr[n] for(int i=0;i<n;i++){ arr[i]=new int[k]; }
new调用的默认是C++提供的无参构造函数,当然也可以调用自己写的。
#include <iostream> using namespace std; class Person { public: // 带有参数的构造函数 Person(string name, int age) : name(name), age(age) { cout << "Person object created with name: " << name << ", age: " << age << endl; } // 析构函数 ~Person() { cout << "Person object destroyed." << endl; } private: string name; int age; }; int main() { // 使用带有参数的构造函数创建对象 Person *personPtr = new Person("John Doe", 25); // 使用对象... // 记得在不需要对象时释放内存 delete personPtr; return 0; }
简单示例:
1.单个元素
#include <iostream>
using namespace std;
int main() {
int *a=new int;
*a=5;
cout<<*a<<endl;//5
cout<<a<<endl;//0x677d80 [内存值]
delete a;
return 0;
}
2.一维数组
#include <iostream>
using namespace std;
int main() {
int n=5;
int *a=new int[n];
//a={0,1,2,3,4} is error
for(int i=0;i<n;i++){
cin>>a[i];
}
for(int i=0;i<n;i++){
cout<<a[i]<<" ";
}
cout<<endl;
delete [] a;
return 0;
}
3.二维数组
#include <iostream>
using namespace std;
//赋值
void func(int **a,int n){
for(int i=0;i<n;i++){
for(int j=0;j<3;j++){
a[i][j]=i+j;
}
}
}
//输出二维
void Disp(int **a,int n){
for(int i=0;i<n;i++){
for(int j=0;j<3;j++){
cout<<a[i][j]<<" ";
}
cout<<endl;
}
}
int main() {
//创建a[n][k],先创建a[n],后创建各自的一维数组
int n=4;
int k=n;
int **a=new int*[n];
for (int i = 0; i < n; i++) {
a[i] = new int[k];
}
func(a,n);
Disp(a,n);
//释放,和创建顺序相反,即对称
for (int i = 0; i < n; i++) {
delete[] a[i];
}
delete [] a;
return 0;
}
/*输出
0 1 2
1 2 3
2 3 4
3 4 5
*/
简单示例
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass() {//构造函数
cout << "Constructor called." << endl;
}
~MyClass() {//析构函数
cout << "Destructor called." << endl;
}
};
int main() {
/*********new \ delete************/
// 创建单个对象
MyClass* singleObject = new MyClass;//输出Constructor called.
// 删除单个对象
delete singleObject;//输出Destructor called.
// 创建对象数组
MyClass* arrayObjects = new MyClass[3];//输出3个Constructor called.
// 删除对象数组
delete[] arrayObjects;//输出3个Destructor called.
/*********malloc \ free************/
int *a = (int *)malloc(sizeof(int));
int *arr = (int *)malloc(sizeof(int) * 5);
free(a);
free(arr);
return 0;
}
⛳️第九章 引用
9.1 基本知识
1. 引用是个别名,不占存储空间:引用允许你通过不同的名字访问相同的内存位置,而不是创建一个新的存储空间。
如下面示例,b与a属于同一个地址
int a; int &b=a;//b是a的引用
2. 引用一旦维系便无法更改。后续的赋值也就仅仅是赋值而不是引用。
int a=5; int &b=a;//b是a的引用 int c=10; b=c;//则b=a=10
3. 不允许void 引用
void & a=3; //error
引用的简单示例:
#include <iostream>
using namespace std;
int main() {
int originalVariable = 42;
// 创建引用
int& reference = originalVariable;
cout << "Original variable: " << originalVariable << endl;//42
cout << "Reference: " << reference << endl;//42
// 修改引用会修改原始变量
reference = 100;
cout << "After modifying reference:" << endl;
cout << "Original variable: " << originalVariable << endl;//100
cout << "Reference: " << reference << endl;//100
return 0;
}
9.2 练习
引用做swap()完成值交换,传递的是地址 理解即可
#include <iostream>
using namespace std;
// 使用引用实现 swap 函数
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int num1 = 5;
int num2 = 10;
// 调用 swap 函数
swap(num1, num2);
cout << "After swapping:" << endl;
cout << "num1: " << num1 << endl;//10
cout << "num2: " << num2 << endl;//5
return 0;
}
⛳️第十章 结构
C++结构体无需typedef后续定义也可以是Date date; 而c语言则是 struct Date today;
关于结构体的内容详见:【C语言】自学终极笔记第九章
⛳️第十一章 类
11.1 基本知识
- 类class默认是private;C语言的struct默认public
- 类名不能和函数名相同,但可以与形参同名。
- 对象(类class)=成员函数+成员变量
- 类的封装:类中有些成员是保护的,不能被外界直接修改(可以通过公共接口修改);另一些是公共的,提供接口供外界使用。
11.2 练习
1. 类中类外定义成员函数+继承+重写 看懂即可
#include <iostream>
using namespace std;
class MyBaseClass// 基类
{
public:
virtual void virtualFunction()
{
cout << "基类虚函数" << endl;
}
// 类外定义
void normalFunction(int x);
// 重载函数
void normalFunction(double x)
{
cout << "基类 函数(double)" << x << endl;
}
private:
int x;
double y;
};
// 在类外部定义成员函数: "类名::"放中间
void MyBaseClass::normalFunction(int x)//MyBaseClass:: void normalFunction() is error
{
cout << "基类 函数(int)" <<x<< endl;
}
class MyDerivedClass : public MyBaseClass// 派生类
{
public:
// 重写基类的虚函数
void virtualFunction() override
{
cout << "派生类 重写函数" << endl;
}
// 新增成员函数
void additionalFunction()
{
cout << "派生类 额外函数" << endl;
}
// 重载函数的派生类版本
void normalFunction(double y)
{
cout << "派生类 重写函数(double)" << y << endl;
}
};
int main()
{
MyDerivedClass derivedObj;
derivedObj.virtualFunction(); // 派生类 重写函数
derivedObj.normalFunction(2); // 派生类 重写函数(double)2
derivedObj.additionalFunction(); // 派生类 额外函数
derivedObj.normalFunction(42.1); // 派生类 重写函数(double)42.1
return 0;
}
2. 重载成员函数 看懂即可
#include <iostream>
using namespace std;
class MyClass {
public:
void display() {
cout << "This is the original display function." << endl;
}
// 重载display函数,不同参数列表
void display(int value) {
cout << "Displaying value: " << value << endl;
}
};
int main() {
MyClass myObject;
myObject.display(); // This is the original display function.
myObject.display(42); // Displaying value: 42
return 0;
}
3. 用指针和引用调用成员函数的示例
#include <iostream>
using namespace std;
class MyClass {
public:
void display() {
cout << "This is the function" << endl;
}
};
int main() {
MyClass myObject;
// 使用指针调用成员函数
MyClass* ptr = &myObject;
ptr->display(); //This is the function
(*ptr).display();//This is the function
// 使用引用调用成员函数
MyClass& ref = myObject;
ref.display(); //This is the function
return 0;
}
⛳️第十二章 构造函数
12.1 基本知识
构造函数作用:创建+初始化类对象
析构函数作用:撤销类对象
- 构造函数、析构函数可以在类内和类外定义
构造函数:
- 可以有参数
- 无返回值,但可以有 "return;"
- 无函数类型
- 自动调用,格式为 类名
- 允许重载
析构函数:
- 没有参数
- 无函数类型
- 自动调用,格式为 ~类名
- 不能重载
注意:
- C++的每个类都必须要有构造函数,若用户未提供则系统提供一个默认的无参构造函数【用户提供则系统不再默认提供】
- 对于无参构造函数的创建
Tdate today;//ok Tdate today();//error
对于静态成员变量,只能在类内声明,类外初始化
#include <iostream> using namespace std; class MyClass { public: // 静态成员变量的声明 static int staticVar; // 静态成员函数 static void staticFunction() { cout << "Static function called." << endl; } }; // 静态成员变量的定义和初始化 int MyClass::staticVar = 42; int main() { // 调用静态成员函数 MyClass::staticFunction(); // 访问静态成员变量 cout << "Static variable value: " << MyClass::staticVar << endl; return 0; } /*输出 Static function called. Static variable value: 42 */
12.2 练习
1. 多个类,其中一个类的数据成员包含其他类对象,调用构造函数是依次调用,析构函数顺序与构造函数调用顺序相反
#include <iostream>
using namespace std;
class InnerClass
{
public:
InnerClass()
{
cout << "1Class Constructor called." << endl;
}
~InnerClass()
{
cout << "1Class Destructor called." << endl;
}
};
class OuterClass
{
public:
OuterClass()
{
cout << "2Class Constructor called." << endl;
}
~OuterClass()
{
cout << "2Class Destructor called." << endl;
}
private:
InnerClass innerObject;//类的数据成员包含另一个类对象
};
int main()
{
// 创建 outerObject 对象,触发构造函数
OuterClass outerObject;
// 对象在 main 函数结束时销毁,触发析构函数
return 0;
}
/*输出
1Class Constructor called.
2Class Constructor called.
2Class Destructor called.
1Class Destructor called.
*/
2. 析构函数简单示例
#include <iostream>
using namespace std;
class MyClass {
private:
int* dynamicArray;
public:
// 构造函数
MyClass(int size) {
dynamicArray = new int[size];
cout << "MyClass Constructor called." << endl;
}
// 析构函数
~MyClass() {
delete[] dynamicArray;
cout << "MyClass Destructor called." << endl;
}
};
int main() {
// 创建 MyClass 对象
MyClass myObject(5);
// 对象在 main 函数结束时销毁,触发析构函数调用
return 0;
}
/*输出
MyClass Constructor called.
MyClass Destructor called.
*/
3. 构造函数重载简单示例
#include <iostream>
using namespace std;
class MyClass {
private:
int value;
public:
// 默认构造函数
MyClass() {
value = 0;
cout << "Default Constructor called." << endl;
}
// 带参数的构造函数
MyClass(int initValue) {
value = initValue;
cout << "Constructor called with value: " << value << endl;
}
// 另一个带两个参数的构造函数
MyClass(int initValue, bool isSpecial) {
if (isSpecial) {
value = initValue * 2;
} else {
value = initValue;
}
cout << "Special Constructor called with value: " << value << endl;
}
};
int main() {
// 使用不同构造函数创建 MyClass 对象
MyClass defaultObject; // 默认构造函数
MyClass objectWithParam(42); // 带参数的构造函数
MyClass specialObject(30, true); // 另一个带两个参数的构造函数
return 0;
}
/*输出
Default Constructor called.
Constructor called with value: 42
Special Constructor called with value: 60
*/
⛳️第十三章 面向对象程序设计
13.1 基本知识
抽象:抽象的主要目标是提供一种清晰的、高层次的接口,使得实现细节可以被隐藏,同时允许派生类提供特定的实现。
13.2 练习
1. 纯虚函数: 纯虚函数是在基类中声明但没有实现的虚函数,通过在声明中使用
= 0
来标记。任何包含纯虚函数的类都被认为是抽象类,不能被实例化。
class AbstractClass {
public:
// 纯虚函数
virtual void pureVirtualFunction() const = 0;
};
2. 抽象类: 抽象类是包含纯虚函数的类。抽象类不能被实例化,它用于定义接口,由派生类提供具体实现。
class AbstractClass {
public:
// 纯虚函数,使类成为抽象类
virtual void abstractFunction() const = 0;
// 普通成员函数
void commonFunction() const {
// 具体实现
}
};
⛳️第十四章 堆与拷贝构造函数
14.1 基本知识
堆:
- malloc/free和new/delete区别详见:C/C++面试题之语言基础篇(一)-CSDN博客
- malloc、free不会调用构造函数和析构函数
- new 对象数组 调用的构造函数只能是 默认的构造函数,没有提供则出错【不写C++会提供默认的无参构造函数,但用户自己给了则不再提供默认的无参构造函数】
拷贝构造函数:
拷贝场景一:对象可以初始化另一个对象
Tdate day1(12,3,1997); Tdate day2=day1;//day1去初始化day2
拷贝场景二:对象需要做参数传递
void func(Tdate day){} int main() { Tdate day1; func(day1);//传递给形参day }
基本概念:
- 拷贝构造函数参数应该是Tdate(Tdate &day);而非Tdate(Tdate *day);
- C++提供默认拷贝构造函数(浅拷贝)
- 深拷贝与浅拷贝,区别详见C/C++面试题之语言基础篇(一)-CSDN博客 示例见14.3
- 建议在拷贝构造函数中使用
const
修饰符。这可以防止在拷贝过程中修改原始对象。如class A{ A(const A &ohter){//拷贝构造函数 ...} ... };
14.2 堆练习
malloc、free不会调用构造函数和析构函数示例
#include <iostream>
#include <cstdlib>
using namespace std;
class MyClass {
public:
MyClass() {
cout << "Constructor called." << endl;
}
~MyClass() {
cout << "Destructor called." << endl;
}
void display() {
cout << "Displaying." << endl;
}
};
int main() {
/*错误示例*/
// 使用 malloc 分配内存
MyClass* myObject = (MyClass*)malloc(sizeof(MyClass)));//不会调用构造函数
// 使用 free 释放内存(析构函数不会被调用)
free(myObject);
/*正确示例*/
// 使用 new 运算符分配内存并调用构造函数
MyClass* myObject = new MyClass;
// 使用 delete 运算符释放内存并调用析构函数
delete myObject;
return 0;
}
14.3 拷贝构造函数练习
拷贝构造函数简单示例
#include <iostream>
using namespace std;
class MyClass {
private:
int* data;
public:
// 构造函数
MyClass(int value) {
data = new int(value);
cout << "Constructor called with value: " << *data << endl;
}
// 拷贝构造函数
MyClass(MyClass& other) {
data = new int(*(other.data));
cout << "Copy Constructor called. Copied value: " << *data << endl;
}
// 析构函数
~MyClass() {
cout << "Destructor called. Value: " << *data << endl;
delete data;
}
};
void fn(MyClass s){
cout<<"fn message"<<endl;
}
int main() {
// 创建对象
MyClass originalObject(42);//Constructor called with value: 42
// 使用拷贝构造函数创建新对象
fn(originalObject);//Copy Constructor called. Copied value: 42
return 0;
}
/*输出
Constructor called with value: 42
Copy Constructor called. Copied value: 42
fn message
Destructor called. Value: 42
Destructor called. Value: 42
*/
默认拷贝构造函数【浅拷贝】
#include <iostream>
using namespace std;
class MyClass {
public:
// 构造函数
MyClass(int value) : data(value) {
cout << "Constructor called with value: " << data << endl;
}
//拷贝构造函数
MyClass(MyClass &other){
data=other.data;
}
// 析构函数
~MyClass() {
cout << "Destructor called for value: " << data << endl;
}
private:
int data;
};
int main() {
// 创建对象
MyClass originalObject(42);
// 使用默认拷贝构造函数创建新对象
MyClass copiedObject = originalObject;
return 0;
}
/*输出
Constructor called with value: 42
Destructor called for value: 42
Destructor called for value: 42
*/
深拷贝简单示例:在拷贝构造函数加入new 分配堆资源
#include <iostream>
#include <cstring>
using namespace std;
class MyString {
public:
// 构造函数
MyString(char* value) {
size = strlen(value);
data = new char[size + 1];
strcpy(data, value);
cout << "Constructor called with value: " << data << endl;
}
// 拷贝构造函数
MyString(MyString& other) {
size = other.size;
data = new char[size + 1];//如果这里不加则为浅拷贝,后续delete会出错
strcpy(data, other.data);
cout << "Copy Constructor called. Copied value: " << data << endl;
}
// 析构函数
~MyString() {
cout << "Destructor called for value: " << data << endl;
delete[] data;
}
private:
char* data;
size_t size;
};
int main() {
// 创建对象
MyString originalObject("Hello");
// 使用拷贝构造函数创建新对象
MyString copiedObject = originalObject;
return 0;
}
/*输出
Constructor called with value: Hello
Copy Constructor called. Copied value: Hello
Destructor called for value: Hello
Destructor called for value: Hello
*/
⛳️第十五章 静态成员与友元
15.1 基本知识
静态数据成员:
- 类中声明,类外初始化(不能在任何函数内分配空间+初始化)。
- const 静态数据成员: 静态数据成员可以声明为 const,必须在类中声明时初始化。
class MyClass { public: static const int constStaticData = 42; };
静态成员函数:
访问权限: 静态成员函数只能访问静态成员和静态函数,而不能访问非静态成员或非静态函数。
this 指针: 静态成员函数没有隐含的
this
指针。调用方式: 静态成员函数可以通过类名直接调用,而不需要通过类的实例。例如:
ClassName::staticMemberFunction()
。不能声明为 const 或 volatile: 静态成员函数不能被声明为
const
或volatile
,因为这两个关键字都与实例相关。静态数据成员和静态成员函数的根本区别:
静态数据成员有this指针,而静态成员函数无this指针。
友元(friend关键字)
破坏封装性: 友元机制破坏了类的封装性,因为允许外部实体访问类的一切成员。
不是成员函数,友元声明可以在类中任何位置(效果都一样),定义在类外
15.2 练习
静态成员的简单示例。
#include <iostream>
using namespace std;
class MyClass {
public:
// 静态数据成员的声明
static int staticData;
// 静态成员函数,用于访问和修改静态数据成员
static void printStaticData() {
cout << "Static Data: " << staticData << endl;
}
};
// 静态数据成员的初始化
int MyClass::staticData = 0;
int main() {
// 创建类的对象
MyClass obj1, obj2;
MyClass::staticData = 42;// 通过类名访问静态数据成员
obj1.staticData=43;// 通过对象访问静态数据成员
cout << "Object 1 Static Data: " << obj1.staticData << endl;
cout << "Object 2 Static Data: " << obj2.staticData << endl;
// 通过静态成员函数访问和修改静态数据成员
MyClass::printStaticData();
MyClass::staticData = 100;
MyClass::printStaticData();
return 0;
}
/*输出
Object 1 Static Data: 43
Object 2 Static Data: 43
Static Data: 43
Static Data: 100 0
*/
友元的简单示例
#include <iostream>
class MyClass {
private:
int privateData;
friend void friendFunction(const MyClass& obj); // 友元函数声明
public:
MyClass() : privateData(0) {}//构造函数,privateData默认初始化为0
void setPrivateData(int value) {
privateData = value;
}
};
// 友元函数的定义
void friendFunction(const MyClass& obj) {
std::cout << "Friend Function Accessing Private Data: " << obj.privateData << std::endl;
}
int main() {
MyClass obj;
obj.setPrivateData(42);
// 友元函数的调用
friendFunction(obj);
return 0;
}
/*输出
Friend Function Accessing Private Data: 42
*/
⛳️第十六章 继承
16.1 基本知识
继承:
派生类(子类)继承基类(父类)的成员函数和数据成员,并在此基础上可以构建自己的成员函数和数据成员。避免了一些重复性的工作。
class A{ //... }; //单个继承 class B : public A{//公有继承:B继承A的成员函数和数据成员 //... } class c : protected A{//保护继承:c继承A的成员函数和数据成员 //... } class d : private A{//私有继承:d继承A的成员函数和数据成员 //... } //多重继承 class B : public A,public E{//公有继承:B继承A+E的成员函数和数据成员 //... }
- 继承后派生类能够访问父类的public、protected成员,不能访问private成员
- 而类外普通函数、对象能够访问父类的public成员,不能访问private、protected成员
不管哪种继承方式,父类的私有成员都不可以访问
派生类的构造:
会依次调用父类的构造函数,析构与构造顺序相反。见12.2练习1
虚拟继承:
虚拟继承用于解决由多重继承导致的菱形继承问题。虚拟继承通过关键字
virtual
实现,可以确保在继承体系中共享相同基类的实例只有一份。class A{ //... }; //单个继承 class B : virtual public A{//公有继承:B继承A的成员函数和数据成员 //... } class c : virtual public A{//保护继承:c继承A的成员函数和数据成员 //... } //多重继承:虚拟继承 class B : public A,public E{//公有继承:B继承A+E的成员函数和数据成员 //... }
16.2 练习
继承的简单示例
#include <iostream>
// 基类(父类)
class Animal {
public:
Animal(const std::string& name) : name(name) {}
void eat() const {
std::cout << name << " is eating." << std::endl;
}
void sleep() const {
std::cout << name << " is sleeping." << std::endl;
}
private:
std::string name;
};
// 派生类(子类)
class Dog : public Animal {
public:
Dog(const std::string& name, const std::string& breed)
: Animal(name), breed(breed) {}
void bark() const {
std::cout << "Woof! Woof!" << std::endl;
}
private:
std::string breed;
};
int main() {
// 创建派生类对象
Dog myDog("Buddy", "Golden Retriever");
// 调用基类的成员函数
myDog.eat();
myDog.sleep();
// 调用派生类自己的成员函数
myDog.bark();
return 0;
}
/*输出
Buddy is eating.
Buddy is sleeping.
Woof! Woof!
*/
虚拟继承的简单示例
#include <iostream>
using namespace std;
// 基类 Animal,加入数据成员
class Animal {
protected:
string species; // 动物的种类
public:
Animal(const string& species) : species(species) {}
// 虚拟函数,表示动物的叫声
virtual void makeSound() const {
cout << "Animal makes a sound" << endl;
}
};
// 虚拟继承方式1
class Mammal : public virtual Animal {
public:
Mammal(const string& species) : Animal(species) {}//构造函数
// 虚拟函数,表示哺乳动物的行为
virtual void nurse() const {
cout << "Mammal nurses its young" << endl;
}
};
// 虚拟继承方式2
class Bird : public virtual Animal {
public:
Bird(const string& species) : Animal(species) {}//构造函数
// 虚拟函数,表示鸟类的飞行
virtual void fly() const {
cout << "Bird flies in the sky" << endl;
}
};
// 最终派生类,这样设计确保了最终派生类 Bat 中只包含一个共享的 Animal 子对象【来自于最远的、最顶层的虚拟基类】,避免了菱形继承问题。
class Bat : public Mammal, public Bird {//虚拟继承
public:
Bat(const string& species) : Animal(species), Mammal(species), Bird(species) {}//构造函数
// 重写虚拟函数,表示蝙蝠的叫声
void makeSound() const override {
cout << "Bat makes a high-pitched sound" << endl;
}
};
int main() {
// 创建蝙蝠对象
Bat bat("Bat");
// 调用虚拟函数
bat.makeSound(); // 蝙蝠特有的叫声
bat.nurse(); // 继承自哺乳动物
bat.fly(); // 继承自鸟类
return 0;
}
/*输出
Bat makes a high-pitched sound
Mammal nurses its young
Bird flies in the sky
*/
⛳️第十七章 多态
17.1 基本知识
虚函数:
- 在基类中通过使用
virtual
关键字声明虚函数,virtual
关键字基类必须要用,而派生类可以省略。class Base { public: virtual void display() const { // 虚函数的实现 } };
- 派生类可以重写基类中的虚函数,提供自己的实现【函数原型必须完全一样,区别于函数重载(参数个数、参数顺序、参数类型至少有一个不同)】。在派生类中,使用
override
关键字可以显式地表明这是对基类虚函数的重写【也可以没有】。class Derived : public Base { public: void display() const{ // 派生类对虚函数的实现 //... } //显式重写则是 void display() const override { // 派生类对虚函数的实现 //... } };
虚函数不能是静态成员函数、内联函数: 虚函数必须是非静态成员函数。虚函数的调度机制是通过对象的虚函数表(vtable)来实现的,而静态成员函数不属于对象的实例,因此不能是虚函数。
构造函数不能是虚函数: 构造函数不能是虚函数。在对象构造的过程中,虚表还没有被构建,因此无法实现虚函数的多态性。
析构函数应该声明为虚函数: 如果类中包含虚函数,通常应该将析构函数声明为虚函数。这确保在使用基类指针指向派生类对象时,可以正确调用派生类的析构函数,避免内存泄漏。
纯虚函数:
纯虚函数本身在基类中没有具体的实现,而是在派生类中被强制要求实现。
纯虚函数的声明和定义的一般形式如下:
注意:class AbstractBase { public: virtual void pureVirtualFunction() const = 0; // 纯虚函数声明 virtual ~AbstractBase() {} // 虚析构函数 };
- 在声明纯虚函数时,在函数声明的末尾使用
= 0
表示这是一个纯虚函数- 要求在派生类中被强制要求实现
- 如果一个类中包含了纯虚函数,它就成为抽象类
多态:
多态允许不同类型的对象调用同一函数或操作能够产生不同的响应。在C++中,主要通过虚函数(Virtual Function)来实现多态性。
分成运行时多态和静态多态
- 运行时多态【多态的主要形式,也称动态多态】:主要体现:虚函数和继承。通过使用指向基类的指针或引用,调用相同的虚函数时,根据实际对象类型来确定调用哪个版本的函数。
int main() { Base* ptr = new Derived(); // 指向派生类对象的基类指针 ptr->display(); // 调用派生类的实现,而不是基类的实现 delete ptr; return 0; }
- 编译时多态【静态多态】:主要体现:函数重载和模板实现。在编译时确定调用哪个函数。
void print(int value) { // 实现1 } void print(double value) { // 实现2 } int main() { print(42); // 调用 print(int value) print(3.14); // 调用 print(double value) return 0; }
17.2 练习
虚函数+村虚函数简单示例
#include <iostream>
using namespace std;
// 基类
class Shape {
public:
// 纯虚函数,表示计算面积
virtual double area() const = 0;
// 虚函数,用于显示形状信息
virtual void display() const {
cout << "Shape" << endl;
}
// 虚析构函数
virtual ~Shape() {}
};
// 圆形类,继承自基类
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 实现基类中的纯虚函数
double area() const override {
return 3.14 * radius * radius;
}
// 重写基类中的虚函数
void display() const override {
cout << "Circle with radius " << radius << endl;
}
};
// 矩形类,继承自基类
class Rectangle : public Shape {
private:
double length;
double width;
public:
Rectangle(double l, double w) : length(l), width(w) {}
// 实现基类中的纯虚函数
double area() const override {
return length * width;
}
// 重写基类中的虚函数
void display() const override {
cout << "Rectangle with length " << length << " and width " << width << endl;
}
};
int main() {
// 通过基类指针实现多态
Shape* shape1 = new Circle(5.0);
Shape* shape2 = new Rectangle(4.0, 6.0);
// 调用虚函数,实现多态性
shape1->display(); // 输出:Circle with radius 5
cout << "Area: " << shape1->area() << endl; // 输出:Area: 78.5
shape2->display(); // 输出:Rectangle with length 4 and width 6
cout << "Area: " << shape2->area() << endl; // 输出:Area: 24
// 释放动态分配的内存
delete shape1;
delete shape2;
return 0;
}
⛳️第十八章 运算符重载
18.1 基本知识
运算符重载
- 优先级和结合性、操作个数保持不变。
- 不能重载运算符:点操作(.)、域操作(::)、条件操作符(?)等等
- 重载形式
returnType operator op(parameters) { // 运算符的新实现 } /*比如 class A{ ... }; int poerator +(A&,A&); */
18.2 练习
复数加法:重载加法运算符
+
#include <iostream>
using namespace std;
class Complex {
private:
double real;
double imag;
public:
Complex(double r, double i) : real(r), imag(i) {}
// 重载加法运算符
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
void display() const {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
// 创建两个复数对象
Complex c1(3.0, 4.0);
Complex c2(1.5, 2.5);
// 使用重载的加法运算符
Complex result = c1 + c2;
// 显示结果
cout << "Result of addition: ";
result.display();
return 0;
}
/*输出
Result of addition: 4.5 + 6.5i
*/
⛳️第十九章 I/O流
19.1 基本知识
待续...
19.2 练习
待续...
⛳️第二十章 模板
20.1 基本知识
模板
- 分为类模板+函数模板
使用模板的优势:
- 通用性: 模板使得可以编写适用于多种数据类型的通用代码,而不需要为每种数据类型编写特定的代码。
template <typename T> T add(T a, T b) { return a + b; } int result_int = add(3, 4); double result_double = add(3.5, 4.5);
灵活性: 模板提供了一种在编译时实现多态性的方式。通过在编译时生成特定的代码版本,可以避免运行时的性能开销,并在编译时进行类型检查。
代码重用: 使用模板可以创建通用的数据结构和算法,以适应不同的需求。这样可以减少代码的复制粘贴,提高代码的重用性。
template <typename T> class Pair { private: T first; T second; public: Pair(const T& f, const T& s) : first(f), second(s) {} }; Pair<int> intPair(1, 2); Pair<double> doublePair(3.5, 4.5);
类模板:
以下是类模板的一般语法:
template <typename T1, typename T2, ...> class ClassName { // 类成员和成员函数的定义 public: ClassName(T1 param1, T2 param2, ...); // 其他成员函数或声明 };
其中,
T1
,T2
, ... 是模板参数列表,用逗号分隔。这些模板参数可以在类定义中的成员变量、成员函数、构造函数等地方使用,起到泛型的作用。函数模板:
- 函数模板的一般语法如下:
template <typename T> T functionName(T param1, T param2, ...) { // 函数体 }
其中,
typename T
表示模板参数,T
可以是任何合法的标识符,用于表示函数的参数和返回类型。在实际调用时,编译器会根据传入的参数类型,自动推导出正确的类型。- 待续
函数模板和模板函数区别
- 函数模板: 函数模板是模板的定义。创建通用函数的机制,其中函数的定义使用模板参数。这使得函数能够接受不同类型的参数,从而实现对多种数据类型的通用操作。函数模板使用
template
关键字声明,并且可以包含一个或多个类型参数。template <typename T> T add(T a, T b) { return a + b; }
- 模板函数: 模板函数是函数定义。指通过函数模板实例化得到的具体函数。在调用函数时,编译器会根据传递的参数类型自动生成相应的函数版本。
int result_int = add(3, 4); // 实例化为 int 版本 double result_double = add(3.5, 4.5); // 实例化为 double 版本
类模板和模板类区别
- 类模板: 类模板是模板定义。一种创建通用类的机制,其中类的定义使用模板参数。这使得类能够处理不同类型的数据,从而实现对多种数据类型的通用数据结构或算法。类模板使用
template
关键字声明,并且可以包含一个或多个类型参数。template <typename T> class Pair { private: T first; T second; public: Pair(const T& f, const T& s) : first(f), second(s) {} };
- 模板类: 模板类是类定义。指通过类模板实例化得到的具体类。在使用类时,可以为类的模板参数指定具体的类型,从而实例化得到特定的类。
Pair<int> intPair(1, 2); // 实例化为处理 int 类型的 Pair Pair<double> doublePair(3.5, 4.5); // 实例化为处理 double 类型的 Pair
20.2 练习
函数模板简单示例
#include <iostream>
using namespace std;
// 函数模板
template <typename T>
T findMin(T a, T b) {
return (a < b) ? a : b;
}
int main() {
// 使用模板函数
int intMin = findMin(3, 7);
double doubleMin = findMin(4.5, 2.7);
// 显示结果
cout << "Minimum of integers: " << intMin << endl;
cout << "Minimum of doubles: " << doubleMin << endl;
return 0;
}
/*输出
Minimum of integers: 3
Minimum of doubles: 2.7
*/
类模板简单示例
#include <iostream>
using namespace std;
// 类模板的定义
template <typename T>
class Pair {
private:
T first;
T second;
public:
Pair(const T& f, const T& s) : first(f), second(s) {}
//将成员变量 first 和 second 初始化为传递进来的参数值 f 和 s。
T getFirst() const {
return first;
}
T getSecond() const {
return second;
}
};
int main() {
// 使用类模板实例化具体的类型
Pair<int> intPair(1, 2);
Pair<double> doublePair(3.14, 2.71);
// 访问成员函数
cout << "Int Pair: " << intPair.getFirst() << ", " << intPair.getSecond() << endl;
cout << "Double Pair: " << doublePair.getFirst() << ", " << doublePair.getSecond() << endl;
return 0;
}
/*输出
Int Pair: 1, 2
Double Pair: 3.14, 2.71
*/
📝总结
嘘,听说有一位C++大师突破了次元壁,成功通关了编程的炼金之路!从入门到进阶,你的代码之旅如同编程宇宙的星空,熠熠生辉。你不仅掌握了代码的秘法,更诠释了编程的独特魔力。每一次Debug都是一场魔法修炼,每一行代码都是一篇炫目的咒语。恭喜你,编程冒险家,你已经成为这片代码大陆的传奇英雄。未来等着你用键盘书写更加壮丽的代码史
诗,展开属于你的数字冒险。继续释放编码魔法,让代码的光芒照亮前行的路途!文章来源:https://www.toymoban.com/news/detail-754846.html
文章来源地址https://www.toymoban.com/news/detail-754846.html
到了这里,关于【C++】自学终极笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!