【C++】自学终极笔记

简介: 【C++】自学终极笔记



⛳️第一章 C++入门

1.1 基本知识

1. C++是面向对象编程(OOP)特点如下:

  1. 封装和数据隐藏
  2. 继承和重写
  3. 多态

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>

注意

  1. 输出%,则printf("%%");
  2. setprecision、fixed会影响接下来的所有浮点数的输出,直到下个setprecision、fixed出现为止。
  3. setprecision(2)表示精确2位,如 11.235 则输出 11   3.14 输出 3.1
  4. 要求精确到小数点后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 基础知识

  1. 左值和右值:C/C++面试题之语言基础篇(一)-CSDN博客
  2. 算术运算类型的转换朝着更精确的方向进行。如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 基本知识

  1. 函数原型:即函数声明,如 int max(int a,int b);    等价于    int max(int ,int );
    为什么用函数声明做函数原型?    便于对函数调用的合法性进行检查
  2. 函数分两种:标准库函数+用户自定义函数
  3. 函数定义=函数声明+函数体
  4. 重载函数至少在参数个数、参数类型、参数顺序有所不同。
    错误示例:
void func(int);
int func(int);//返回类型不同则无法实现重载

默认参数的函数:

  1. 有默认值的参数应该位于参数列表的右侧
  2. 默认参数应该从右向左设置: 默认参数的赋值应该从右边的参数开始,不能跳过某个参数。在上述示例中,首先给 name 设置了默认值,然后是 age
  3. 默认参数只能在函数声明中出现一次: 默认参数只能在函数声明中出现一次,而不应该在函数定义中重复提供默认值。
/*默认参数的函数*/
#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两种格式:

  1. #inlcude <xxx>
  2. #inlcude "xxx"

区别:如果头文件是由开发者创建并与源文件位于同一项目中,使用双引号格式是比较常见的。而如果是标准库或系统提供的头文件,使用尖括号格式更为合适。


⛳️第七章 数组

详见:【C语言】自学终极笔记第六章


⛳️第八章 指针

8.1 基本知识

详见:【C语言】自学终极笔记第八章

8.2 new、delete用法

堆上创建内存分配使用new、delete【区别于C语言的malloc、free

new/delete注意:

  1. 创建单个元素:int *a=new int;
  2. 创建一维数组:int *arr=new int[n];//n为数组具体大小
  3. 创建二维数组:
int **arr=new *int[n];//先创建arr[n]
for(int i=0;i<n;i++){
    arr[i]=new int[k];
}
  1. 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 基本知识

  1. 类class默认是private;C语言的struct默认public
  2. 类名不能和函数名相同,但可以与形参同名。
  3. 对象(类class)=成员函数+成员变量
  4. 类的封装:类中有些成员是保护的,不能被外界直接修改(可以通过公共接口修改);另一些是公共的,提供接口供外界使用。

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 基本知识

  1. 构造函数作用:创建+初始化类对象
    析构函数作用:撤销类对象
  2. 构造函数、析构函数可以在类内和类外定义

构造函数:

  1. 可以有参数
  2. 无返回值,但可以有 "return;"
  3. 无函数类型
  4. 自动调用,格式为   类名
  5. 允许重载

析构函数:

  1. 没有参数
  2. 无函数类型
  3. 自动调用,格式为   ~类名
  4. 不能重载

注意:

  1. C++的每个类都必须要有构造函数,若用户未提供则系统提供一个默认的无参构造函数【用户提供则系统不再默认提供】
  2. 对于无参构造函数的创建
Tdate today;//ok
Tdate today();//error
  1. 对于静态成员变量,只能在类内声明,类外初始化
#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 基本知识

堆:

  1. malloc/free和new/delete区别详见:C/C++面试题之语言基础篇(一)-CSDN博客
  2. malloc、free不会调用构造函数和析构函数
  3. new 对象数组 调用的构造函数只能是  默认的构造函数,没有提供则出错【不写C++会提供默认的无参构造函数,但用户自己给了则不再提供默认的无参构造函数】

拷贝构造函数:

拷贝场景一:对象可以初始化另一个对象

Tdate day1(12,3,1997);
Tdate day2=day1;//day1去初始化day2

拷贝场景二:对象需要做参数传递

void func(Tdate day){}
int main()
{
    Tdate day1;
    func(day1);//传递给形参day
}

基本概念:

  1. 拷贝构造函数参数应该是Tdate(Tdate &day);而非Tdate(Tdate *day);
  2. C++提供默认拷贝构造函数(浅拷贝)
  3. 深拷贝与浅拷贝,区别详见C/C++面试题之语言基础篇(一)-CSDN博客  示例见14.3
  4. 建议在拷贝构造函数中使用 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 基本知识

静态数据成员:

  1. 类中声明,类外初始化(不能在任何函数内分配空间+初始化)。
  2. const 静态数据成员: 静态数据成员可以声明为 const,必须在类中声明时初始化。
class MyClass {
public:
    static const int constStaticData = 42;
};

静态成员函数:

  1. 访问权限: 静态成员函数只能访问静态成员和静态函数,而不能访问非静态成员或非静态函数。
  2. this 指针: 静态成员函数没有隐含的 this 指针。
  3. 调用方式: 静态成员函数可以通过类名直接调用,而不需要通过类的实例。例如:ClassName::staticMemberFunction()
  4. 不能声明为 const 或 volatile: 静态成员函数不能被声明为 constvolatile,因为这两个关键字都与实例相关。

静态数据成员和静态成员函数的根本区别:

静态数据成员有this指针,而静态成员函数无this指针。

友元(friend关键字)

  1. 破坏封装性: 友元机制破坏了类的封装性,因为允许外部实体访问类的一切成员。
  2. 不是成员函数,友元声明可以在类中任何位置(效果都一样),定义在类外

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 基本知识

虚函数:

  1. 在基类中通过使用 virtual 关键字声明虚函数,virtual 关键字基类必须要用,而派生类可以省略。
class Base {
public:
    virtual void display() const {
        // 虚函数的实现
    }
};
  1. 派生类可以重写基类中的虚函数,提供自己的实现【函数原型必须完全一样,区别于函数重载(参数个数、参数顺序、参数类型至少有一个不同)】。在派生类中,使用 override 关键字可以显式地表明这是对基类虚函数的重写【也可以没有】。
class Derived : public Base {
public:
    void display() const{ // 派生类对虚函数的实现
        //...
    }
    //显式重写则是
    void display() const override { // 派生类对虚函数的实现
        //...
    }
};
  1. 虚函数不能是静态成员函数、内联函数: 虚函数必须是非静态成员函数。虚函数的调度机制是通过对象的虚函数表(vtable)来实现的,而静态成员函数不属于对象的实例,因此不能是虚函数。
  2. 构造函数不能是虚函数: 构造函数不能是虚函数。在对象构造的过程中,虚表还没有被构建,因此无法实现虚函数的多态性。
  3. 析构函数应该声明为虚函数: 如果类中包含虚函数,通常应该将析构函数声明为虚函数。这确保在使用基类指针指向派生类对象时,可以正确调用派生类的析构函数,避免内存泄漏。

纯虚函数:

纯虚函数本身在基类中没有具体的实现,而是在派生类中被强制要求实现。

纯虚函数的声明和定义的一般形式如下:

class AbstractBase {
public:
    virtual void pureVirtualFunction() const = 0;  // 纯虚函数声明
    virtual ~AbstractBase() {}  // 虚析构函数
};
注意:
  • 在声明纯虚函数时,在函数声明的末尾使用 = 0 表示这是一个纯虚函数
  • 要求在派生类中被强制要求实现
  • 如果一个类中包含了纯虚函数,它就成为抽象类

多态:

多态允许不同类型的对象调用同一函数或操作能够产生不同的响应。在C++中,主要通过虚函数(Virtual Function)来实现多态性。

分成运行时多态静态多态

  1. 运行时多态【多态的主要形式,也称动态多态】:主要体现:虚函数和继承。通过使用指向基类的指针或引用,调用相同的虚函数时,根据实际对象类型来确定调用哪个版本的函数。
int main() {
    Base* ptr = new Derived();  // 指向派生类对象的基类指针
    ptr->display();  // 调用派生类的实现,而不是基类的实现
    delete ptr;
    return 0;
}
  1. 编译时多态【静态多态】主要体现:函数重载和模板实现在编译时确定调用哪个函数。
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 基本知识

运算符重载

  1. 优先级和结合性、操作个数保持不变。
  2. 不能重载运算符:点操作(.)、域操作(::)、条件操作符(?)等等
  3. 重载形式
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 基本知识

模板

  1. 分为类模板+函数模板

使用模板的优势:

  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);
  1. 灵活性: 模板提供了一种在编译时实现多态性的方式。通过在编译时生成特定的代码版本,可以避免运行时的性能开销,并在编译时进行类型检查。
  2. 代码重用: 使用模板可以创建通用的数据结构和算法,以适应不同的需求。这样可以减少代码的复制粘贴,提高代码的重用性。
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, ... 是模板参数列表,用逗号分隔。这些模板参数可以在类定义中的成员变量、成员函数、构造函数等地方使用,起到泛型的作用。

函数模板:

  1. 函数模板的一般语法如下:
template <typename T>
T functionName(T param1, T param2, ...) {
    // 函数体
}
  1. 其中,typename T 表示模板参数,T 可以是任何合法的标识符,用于表示函数的参数和返回类型。在实际调用时,编译器会根据传入的参数类型,自动推导出正确的类型。
  2. 待续

函数模板和模板函数区别

  1. 函数模板: 函数模板是模板的定义。创建通用函数的机制,其中函数的定义使用模板参数。这使得函数能够接受不同类型的参数,从而实现对多种数据类型的通用操作。函数模板使用 template 关键字声明,并且可以包含一个或多个类型参数。
template <typename T>
T add(T a, T b) {
    return a + b;
}
  1. 模板函数: 模板函数是函数定义。指通过函数模板实例化得到的具体函数。在调用函数时,编译器会根据传递的参数类型自动生成相应的函数版本。
int result_int = add(3, 4);      // 实例化为 int 版本
double result_double = add(3.5, 4.5);  // 实例化为 double 版本

类模板和模板类区别

  1. 类模板: 类模板是模板定义。一种创建通用类的机制,其中类的定义使用模板参数。这使得类能够处理不同类型的数据,从而实现对多种数据类型的通用数据结构或算法。类模板使用 template 关键字声明,并且可以包含一个或多个类型参数。
template <typename T>
class Pair {
private:
    T first;
    T second;
public:
    Pair(const T& f, const T& s) : first(f), second(s) {}
};
  1. 模板类:模板类是类定义。指通过类模板实例化得到的具体类。在使用类时,可以为类的模板参数指定具体的类型,从而实例化得到特定的类。
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都是一场魔法修炼,每一行代码都是一篇炫目的咒语。恭喜你,编程冒险家,你已经成为这片代码大陆的传奇英雄。未来等着你用键盘书写更加壮丽的代码史

诗,展开属于你的数字冒险。继续释放编码魔法,让代码的光芒照亮前行的路途!

目录
相关文章
|
2月前
|
Java 编译器 C++
C++入门指南:类和对象总结笔记(下)
C++入门指南:类和对象总结笔记(下)
30 0
|
2月前
|
存储 编译器 C语言
C++入门: 类和对象笔记总结(上)
C++入门: 类和对象笔记总结(上)
34 0
|
4月前
|
设计模式 安全 编译器
Effective C++55 笔记
Effective C++55 笔记
21 0
|
2天前
|
安全 Java 程序员
【C++笔记】从零开始认识继承
在编程中,继承是C++的核心特性,它允许类复用和扩展已有功能。继承自一个基类的派生类可以拥有基类的属性和方法,同时添加自己的特性。继承的起源是为了解决代码重复,提高模块化和可维护性。继承关系中的类形成层次结构,基类定义共性,派生类则根据需求添加特有功能。在继承时,需要注意成员函数的隐藏、作用域以及默认成员函数(的处理。此外,继承不支持友元关系的继承,静态成员在整个继承体系中是唯一的。虽然多继承和菱形继承可以提供复杂的设计,但它们可能导致二义性、数据冗余和性能问题,因此在实际编程中应谨慎使用。
5 1
【C++笔记】从零开始认识继承
|
2月前
|
编译器 C语言 C++
C++入门指南:类和对象总结笔记(中)
C++入门指南:类和对象总结笔记(中)
49 0
|
4月前
|
存储 算法 C++
《C++ STL开发技术引导》 第五章-C++ STL泛化技术分析笔记
《C++ STL开发技术引导》 第五章-C++ STL泛化技术分析笔记
|
4月前
C++2.0(C++11) 笔记 二
C++2.0(C++11) 笔记 二
20 0
|
4月前
|
编译器 容器
C++11 (C++2.0)笔记 一
C++11 (C++2.0)笔记 一
33 0
|
5月前
|
C语言 C++ iOS开发
嵌入式c++软件开发笔记 第六讲
嵌入式c++软件开发笔记 第六讲
17 0
|
1天前
|
存储 Java C++
【C++类和对象】探索static成员、友元以及内部类
【C++类和对象】探索static成员、友元以及内部类