c++笔记

1.类

构造函数

拷贝构造函数的使用

  1. 当用类的一个对象去初始化该类的另一个对象时系统自动调用拷贝构造函数实现拷贝赋值。
  2. 若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。
  3. 当函数的返回值是类对象时,系统自动调用拷贝构造函数。

继承中的构造函数的写法

在继承中,派生类的构造函数需要调用基类的构造函数初始化基类继承的成员变量和行为。有两种方式来实现派生类的构造函数与基类构造函数的调用:

  1. 显式调用基类构造函数:在派生类构造函数的初始化列表中,使用基类名加圆括号调用基类的构造函数
Derived() : Base(args) {
    // 派生类的其他初始化操作
}

  1. 隐式调用基类构造函数:如果派生类没有显式调用基类构造函数,那么编译器会自动隐式调用基类的默认构造函数。这适用于没有参数的情况或派生类构造函数中不需要额外的初始化操作的情况。

在使用多重继承时,需要注意派生类构造函数调用多个基类构造函数的方式。每个基类都应在派生类构造函数的初始化列表中显式调用其对应的构造函数,以确保每个基类都能正确地初始化

例如:

class Base1 {
public:
    Base1(int param) {
        // 初始化 Base1
    }
};

class Base2 {
public:
    Base2(double param) {
        // 初始化 Base2
    }
};

class Derived : public Base1, public Base2 {
public:
    Derived(int param1, double param2) : Base1(param1), Base2(param2) {
        // 初始化 Derived
    }
};

在上述代码中,Derived 类从 Base1 和 Base2 继承,并在其构造函数的初始化列表中分别调用这两个基类的构造函数来初始化它们。

通过合适地选择构造函数的写法和正确调用基类的构造函数,可以确保派生类正确地初始化从基类继承的成员变量和行为

类中的成员变量

当在类中定义了一个成员变量时,每个类的对象都会具有相同的成员变量。这是因为类定义的成员变量与类本身相关联,而不是与对象相关联。

例如,假设我们有一个类 Person,其中定义了一个名为 age 的成员变量,如下所示:

class Person {
public:
    int age;
};

现在,我们可以创建多个 Person 对象,并将它们的 age 成员变量设置为不同的值,如下所示:

Person p1, p2, p3;
p1.age = 20;
p2.age = 30;
p3.age = 40;

在上述代码中,每个 Person 对象都具有一个名为 age 的成员变量。尽管每个对象具有不同的 age 值,但它们都是由 Person 类定义的成员变量。如果我们修改了 Person 类的定义,那么所有 Person 对象的行为都会受到影响。例如,如果我们将 age 成员变量的类型从 int 改为 double,那么所有的 Person 对象的 age 成员变量都将变为 double 类型。

类变量

在C++中,可以使用static关键字来定义类变量。类变量是属于类本身的变量,而不是类的每个对象所拥有的。因此,类变量的值在所有对象之间是共享的

以下是一个简单的例子,说明如何在类中定义和使用静态变量:

#include 
using namespace std;

class MyClass {
public:
  static int count; // 静态变量
  MyClass() { count++; }
  ~MyClass() { count--; }
};
// 注意:只能在类的内部进行声明,然后在类外面进行定义和初始化!
int MyClass::count = 0; // 静态变量初始化

int main() {
  MyClass obj1;
  MyClass obj2;
  cout << "对象个数:" << MyClass::count << endl; // 访问静态变量
  return 0;
}

在上面的代码中,我们定义了一个类MyClass,其中包含了一个静态变量count。在构造函数中,每次创建一个新的对象时,count的值都会增加1。同样,每次销毁一个对象时,count的值都会减少1。最后,我们在main函数中通过类名和作用域解析运算符来访问静态变量count,并输出其值。

需要注意的是,静态变量的初始化必须在类外进行,因为静态变量属于类,而不是对象。此外,静态变量可以在类中声明,但不能在类中初始化。

this指针

在 C++ 中,如果成员函数的形参名与成员变量的名称相同,那么在成员函数中使用该变量时,编译器会默认使用形参变量。这时,如果你想访问该对象的成员变量而不是形参变量,就需要使用 this 指针来明确指出要访问的是对象的成员变量。

例如,假设一个类中有一个成员变量 age,并且有一个成员函数 getAge,我们可以使用 this 指针来访问对象的 age 属性:

class Person {
public:
    int age;
    int getAge() {
        return this->age;
    }
};

当我们调用 getAge 函数时,实际上是通过对象来调用这个函数,编译器会隐式地将对象的地址传递给 this 指针,从而让 getAge 函数可以访问到对象的 age 属性。因此,在实际的编码中,可以选择使用 this 指针或者省略它来访问对象的成员变量,但使用 this 指针可以使代码更加清晰易懂。

成员变量、成员函数属性

在C++中,类中的成员变量和成员函数都有不同的属性,以及不同的访问权限。

成员变量的属性包括:

public:公有属性,任何地方都可以访问。
protected:受保护的属性,只有该类及其子类可以访问。
private:私有属性,只有该类本身可以访问。
成员函数的属性包括:

public:公有属性,任何地方都可以调用。
protected:受保护的属性,只有该类及其子类可以调用。
private:私有属性,只有该类本身可以调用。
可以使用访问修饰符来设置成员变量和成员函数的访问权限。访问修饰符包括:

public:在类的任何地方都可以访问。
protected:只能在该类和其子类中访问。
private:只能在该类中访问。

+运算符重载(重载成员函数)

C++中的运算符重载是一种特殊的函数重载,它允许我们重新定义运算符的含义,使其适用于用户自定义的数据类型。例如,我们可以使用运算符重载定义自定义数据类型的相加或相等操作。

下面是一个简单的例子,演示如何重载加号运算符,使其能够用于自定义类的两个对象之间的相加操作:

#include 
using namespace std;

class Complex {
   private:
      int real, imag;

   public:
      Complex(int r = 0, int i =0) {
         real = r;
         imag = i;
      }
      
      // 运算符重载:+
      Complex operator + (Complex const &obj) {
         Complex res;
         res.real = real + obj.real;
         res.imag = imag + obj.imag;
         return res;
      }
      
      void print() {
         cout << real << " + i" << imag << endl;
      }
};

int main() {
   Complex c1(3, 7), c2(6, 5);
   Complex c3 = c1 + c2; // 使用重载的加号运算符
   c3.print(); // 输出结果:9 + i12
   return 0;
}

在上面的代码中,我们重载了+运算符,使它能够用于自定义类Complex的两个对象之间的相加操作。我们使用了operator +函数来定义此运算符的新含义,将两个Complex对象相加,并返回一个新的Complex对象,该对象的实部和虚部等于两个对象的实部和虚部之和。最后,我们在main()函数中调用重载的+运算符,将两个Complex对象相加,将结果存储在第三个Complex对象中,并输出结果。

+运算符重载(重载非成员函数)

下面是一个重载 + 运算符的非成员函数的示例,其功能是将两个 Complex 类型的对象相加并返回结果:

class Complex {
private:
    double real;
    double imag;
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    double getReal() const { return real; }
    double getImag() const { return imag; }
};

Complex operator+(const Complex& lhs, const Complex& rhs) {
    double real = lhs.getReal() + rhs.getReal();
    double imag = lhs.getImag() + rhs.getImag();
    return Complex(real, imag);
}

在这个例子中,operator+ 是一个非成员函数,使用了两个 Complex 对象作为参数。这个函数返回一个新的 Complex 对象,其实部和虚部分别是两个参数对象的和。

友元

C++中的友元(friend)是一种特殊的关系,它允许其他类或函数访问类的私有成员。友元可以是一个函数、一个类或者一个函数模板。下面是一些关于C++友元的示例代码:

#include 
using namespace std;

class MyClass {
private:
    int privateData;

public:
    MyClass(int data) : privateData(data) {}

    friend void friendFunction(MyClass obj); // 友元函数声明

    friend class FriendClass; // 友元类声明
};
// 注意:这里的函数friendFunction是一个普通的函数,并不是类中的成员函数!!!
void friendFunction(MyClass obj) {
    cout << "Friend Function accessing private data: " << obj.privateData << endl;
}

class FriendClass {
public:
    void accessPrivateData(MyClass obj) {
        cout << "Friend Class accessing private data: " << obj.privateData << endl;
    }
};

int main() {
    MyClass obj(42);

    friendFunction(obj); // 调用友元函数

    FriendClass fc;
    fc.accessPrivateData(obj); // 调用友元类的成员函数
    return 0;
}

友元函数

C++中的友元函数是一种特殊的函数,它可以访问类的私有成员。通常情况下,只有类的成员函数才能够访问类的私有成员,但有时候需要在类外部定义一个函数来访问类的私有成员,这时候可以使用友元函数。

友元函数的定义可以在类内部,也可以在类外部定义。一般来说,在类定义中声明友元函数,然后在类外部定义它更加清晰和易于理解。为了让一个函数成为一个类的友元函数,需要在类中声明这个函数为友元函数。在**函数声明前加上关键字“friend”**即可。

以下是一个简单的示例,演示了如何在C++中使用友元函数:

#include 
using namespace std;

class MyClass {
    private:
        int a;
        int b;
    public:
        MyClass() {
            a = 0;
            b = 0;
        }
        friend int sum(MyClass obj);
};

// 实现了其他函数访问类中的私有成员
int sum(MyClass obj) {
    int result = obj.a + obj.b;
    return result;
}

int main() {
    MyClass obj;
    obj.a = 10;
    obj.b = 20;
    cout << "Sum: " << sum(obj) << endl;
    return 0;
}

在上面的示例中,sum()函数被声明为MyClass类的友元函数。因此,它可以访问MyClass的私有成员a和b,并返回它们的总和。在主函数中,创建了一个MyClass对象,并将其传递给sum()函数进行计算。

<<运算符重载(重载成员函数)

C++ 中左移运算符是 <<,可以重载成员函数或非成员函数。左移运算符将数据插入到流中,输出时使用。

以下是一个示例,展示了如何重载左移运算符 << 以便将自定义类对象的属性插入到输出流中:

注意:<<和>>运算符的重载只能使用的是非成员函数,也就是说:这两函数的重载只能有一种形式,就是写成非成员函数的形式!

#include 
using namespace std;

class MyClass {
public:
  MyClass(int n) : num(n) {}
  friend ostream& operator<<(ostream& os, const MyClass& obj) {
    os << "num: " << obj.num;
    return os;
  }

private:
  int num;
};

int main() {
  MyClass obj(42);
  cout << obj << endl;
  return 0;
}

在这个示例中,我们定义了一个 MyClass 类,并重载了 << 运算符以将对象的属性插入到输出流中。我们还定义了一个全局函数,使用关键字 friend 来使其成为 MyClass 的友元函数,以便该函数可以访问私有属性 num。最后,在主函数中,我们创建了 MyClass 对象 obj,并使用重载的 << 运算符将其插入到输出流中。

将友元函数在类内声明,在类外定义:
可以将友元函数声明在类内,定义在类外。以下是相应的代码示例:

#include 
using namespace std;

class MyClass {
public:
  MyClass(int n) : num(n) {}
  friend ostream& operator<<(ostream&, const MyClass&);

private:
  int num;
};

ostream& operator<<(ostream& os, const MyClass& obj) {
  os << "num: " << obj.num;
  return os;
}

int main() {
  MyClass obj(42);
  cout << obj << endl;
  return 0;
}

在这个示例中,operator<< 函数被声明为 MyClass 的友元函数。注意,在类内部声明友元函数时,不需要提供函数定义,因为函数定义在类外部实现。因此,我们在类外部提供了 operator<< 的实现。

模板

C++模板是一种泛型编程工具,它允许在编写代码时使用参数化类型或值。通过使用模板,可以编写通用的代码,适用于多种数据类型或值。

C++提供了两种类型的模板:函数模板和类模板。

函数模板

函数模板允许编写一个通用的函数,可以在不同的数据类型上进行操作。函数模板使用template关键字来定义,然后在尖括号<>内指定参数化类型。

下面是一个简单的函数模板示例,演示如何实现一个通用的最大值函数

#include 
using namespace std;

template<typename T>
T Max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    int intMax = Max(10, 20);
    cout << "Max of 10 and 20: " << intMax << endl;

    double doubleMax = Max(3.14, 2.71);
    cout << "Max of 3.14 and 2.71: " << doubleMax << endl;

    return 0;
}

在上述代码中,Max 函数被定义为一个函数模板,使用了模板参数 typename T。这意味着可以在调用函数时传递不同类型的参数。

函数模板不具有兼容性

类模板

类模板允许定义一个通用的类,可以在不同的数据类型上实例化。类模板使用template关键字来定义,然后在尖括号<>内指定参数化类型。

下面是一个简单的类模板示例,演示如何实现一个通用的栈类:

#include 
using namespace std;

template<typename T>
class Stack {
private:
    T data[100];
    int top;

public:
    Stack() : top(-1) {}

    void Push(T item) {
        data[++top] = item;
    }

    T Pop() {
        return data[top--];
    }
};

int main() {
    Stack<int> intStack;
    intStack.Push(10);
    intStack.Push(20);
    intStack.Push(30);

    cout << "Popped item: " << intStack.Pop() << endl;
    cout << "Popped item: " << intStack.Pop() << endl;

    Stack<string> stringStack;
    stringStack.Push("Hello");
    stringStack.Push("World");

    cout << "Popped item: " << stringStack.Pop() << endl;

    return 0;
}

在上述代码中,Stack 类被定义为一个类模板,使用了模板参数 typename T。这意味着可以在实例化类时指定不同的类型。

C++模板是一项强大的特性,它允许编写通用的代码并提高代码的重用性。使用模板,可以在编译时生成适用于不同类型或值的代码,从而实现更高效和更灵活的程序设计。

类模板中的成员函数类外实现

#include 
using namespace std;

template<typename T>
class Stack {
private:
    T data[100];
    int top;

public:
    Stack();

    void Push(T item);

    T Pop();
};

template<typename T>
Stack<T>::Stack() : top(-1) {}

template<typename T>
void Stack<T>::Push(T item) {
    data[++top] = item;
}

template<typename T>
T Stack<T>::Pop() {
    return data[top--];
}

int main() {
    Stack<int> intStack;
    intStack.Push(10);
    intStack.Push(20);
    intStack.Push(30);

    cout << "Popped item: " << intStack.Pop() << endl;
    cout << "Popped item: " << intStack.Pop() << endl;

    Stack<string> stringStack;
    stringStack.Push("Hello");
    stringStack.Push("World");

    cout << "Popped item: " << stringStack.Pop() << endl;

    return 0;
}

类模板 函数模板 <<,>>运算符重载


#include 
using namespace std;

template <typename T>
class Array
{
private:
    T *ptr;
    int size;

public:
    // 类内声明以及初始化
    // Array(int s) : size(s)
    // {
    //     ptr = new T[size];
    // }

    // 类内声明,类外初始化
    Array(int s);

    ~Array()
    {
        delete[] ptr;
    }
    /* 
    重载的输入运算符 operator>> 是作为友元函数实现的。
    这是合理的做法,但要确保该友元函数在类外部定义时被声明为模板函数。
    可以在类内声明 operator>> 为模板函数,然后在类外部实现时也指定为模板函数
    */ 
    template <typename U>
    friend istream & operator>>(istream &input, Array<U> &arr);

    friend ostream &operator<<(ostream &output, const Array<T> &arr)
    {
        for (int i = 0; i < arr.size; i++)
        {
            output << arr.ptr[i] << " ";
        }
        return output;
    }
};
// 类外实现,重载>>运算符
template <typename T>
istream &operator>>(istream &input, Array<T> &arr)
{
    for (int i = 0; i < arr.size; i++)
    {
        cout << "Enter element " << i + 1 << ": ";
        input >> arr.ptr[i];
    }
    return input;
}

// 类内声明,类外初始化 类模板中的函数
template<typename T>
Array<T>::Array(int s) : size(s){
    ptr = new T[size];
}

int main()
{
    int size;
    cout << "Enter the size of the array: ";
    cin >> size;

    Array<int> arr(size);

    cout << "Enter " << size << " elements for the array:" << endl;
    cin >> arr;

    cout << "Elements in the array: " << arr << endl;

    return 0;
}



多继承

虚基类

虚基类(Virtual Base Class)是在多重继承中使用的一种特殊类型的基类。当一个类被声明为虚基类时,它的派生类共享同一个虚基类实例避免了在多个派生类中出现多个实例的情况。

#include 

class Animal {
protected:
    int age;
public:
    Animal(int _age) : age(_age) {}

    void displayAge() {
        std::cout << "Age: " << age << std::endl;
    }
};
// 声明Animal是一个虚基类是在继承的时候体现出来的
class Mammal : virtual public Animal {
protected:
    int weight;
public:
    Mammal(int _age, int _weight) : Animal(_age), weight(_weight) {}

    void displayWeight() {
        std::cout << "Weight: " << weight << " kg" << std::endl;
    }
};

class Bird : virtual public Animal {
protected:
    int wingspan;
public:
    Bird(int _age, int _wingspan) : Animal(_age), wingspan(_wingspan) {}

    void displayWingspan() {
        std::cout << "Wingspan: " << wingspan << " cm" << std::endl;
    }
};

class Bat : public Mammal, public Bird {
public:
    Bat(int _age, int _weight, int _wingspan) : Animal(_age), Mammal(_age, _weight), Bird(_age, _wingspan) {}
};

int main() {
    Bat bat(5, 200, 50);
    bat.displayAge();       // 通过虚基类 Animal 访问年龄
    bat.displayWeight();    // 访问来自 Mammal 的体重
    bat.displayWingspan();  // 访问来自 Bird 的翼展
    return 0;
}

在这个例子中,我们有一个虚基类 Animal,它具有一个成员变量 age 表示动物的年龄,并提供了一个函数 displayAge() 用于显示年龄。

然后,我们定义了两个派生类 Mammal 和 Bird,它们分别从 Animal 类虚继承,表示哺乳动物和鸟类,分别拥有各自的特性,并添加了一些额外的成员变量和成员函数。

接着,我们创建了一个名为 Bat 的派生类,它同时继承了 Mammal 和 Bird。由于 Mammal 和 Bird 都虚继承自 Animal,因此在 Bat 类中只会共享一个 Animal 实例,避免了多个实例的情况。

在 main() 函数中,我们创建了一个 Bat 对象,并演示了通过虚基类访问共享的 Animal 实例的年龄,以及通过 Mammal 和 Bird 访问各自的特性。

通过使用虚基类,我们确保了在多重继承中共享相同基类实例的能力,避免了多个实例导致的冗余数据和二义性问题。

虚基类解决了多重继承中的二义性和冗余数据的问题。通过虚基类,多个派生类可以共享基类的成员,而不会产生冗余数据或歧义性。

虚函数

虚函数(Virtual Function)是在面向对象编程中使用的一种特殊类型的成员函数。当一个成员函数被声明为虚函数时,它可以在派生类中被重写(覆盖),从而实现多态性。通过使用虚函数,可以根据对象的实际类型在运行时确定调用哪个版本的函数

虚函数通过使用动态绑定(Dynamic Binding)实现运行时多态性。当通过基类指针或引用调用虚函数时,编译器会根据指针或引用指向的对象的实际类型来确定应该调用哪个派生类的函数版本。

当一个类被声明为虚基类时,它的派生类将共享同一个虚基类实例。以下是一个示例,展示了一个包含虚函数的虚基类以及派生类的继承关系:

#include 

class Base
{
public:
    virtual void display()
    {
        std::cout << "Base class" << std::endl;
    }
};

class Derived : public virtual Base
{
public:
    void display() override
    {
        std::cout << "Derived class" << std::endl;
    }
};

int main()
{
    Derived derived;
    Base *basePtr = &derived;

    basePtr->display(); // 输出 "Derived class"

    return 0;
}

在上述示例中,Base 类是一个虚基类,其中的 display 函数被声明为虚函数。Derived 类通过公有继承虚基类 Base,并重写了虚函数 display。在 main 函数中,创建了 Derived 类的对象,并将其地址赋值给 Base 类型的指针。通过调用 basePtr->display(),会根据对象的实际类型来确定调用的是基类的虚函数还是派生类的重写函数。在这个示例中,输出结果将是 “Derived class”。

通过虚基类和虚函数的组合,可以实现多态性的特性,即在运行时根据对象的实际类型来决定调用哪个版本的函数。这在面向对象的编程中非常有用,可以实现更灵活和可扩展的代码结构。

纯虚函数&虚函数

虚函数和纯虚函数是面向对象编程中的两个概念,它们在实现多态性方面有一些区别。

虚函数(Virtual Function):

虚函数是在基类中声明并通过基类的指针或引用进行动态调用的成员函数。
虚函数可以有默认的实现,即可以在基类中提供函数的定义。
派生类可以选择性地覆盖(重写)虚函数,提供自己的实现。
虚函数允许在运行时根据对象的实际类型来调用相应的函数实现,实现动态绑定(Dynamic Binding)。
虚函数的定义使用 virtual 关键字

纯虚函数(Pure Virtual Function):

纯虚函数是在基类中声明没有提供默认实现的虚函数
纯虚函数在基类中没有具体的函数定义,它只是为了让派生类提供相应的实现
派生类必须实现纯虚函数,否则派生类也会成为抽象类。
纯虚函数用于定义接口,强制派生类提供相应的实现
通过纯虚函数可以实现接口继承,派生类必须实现所有的纯虚函数才能成为具体类。
纯虚函数的定义使用 virtual 关键字和 = 0。

2. 基础知识

数据类型

在C++中,基本数据类型中,整型、浮点型和字符型是可变的,即可以修改它们的值。例如,可以对整型变量进行赋值操作来改变其值。

然而,C++中还有一些数据类型是不可变的,也称为常量。这些包括以下几种:

  1. 字符串常量:字符串常量是不可变的,一旦定义后,其值不能被修改。
const char* str = "Hello"; // 字符串常量,不可修改
  1. 常量变量:使用 const 关键字定义的变量是常量,它们的值在定义后不能被修改。
const int num = 10; // 常量变量,不可修改
  1. 枚举类型:枚举类型定义的值是不可变的,它们代表了一组预定义的常量值。
enum Color { RED, GREEN, BLUE }; // 枚举类型,不可修改
  1. 类中的常量成员:在类中定义的 const 成员变量是不可修改的。
class MyClass {
    const int value; // 类中的常量成员,不可修改
public:
    MyClass(int val) : value(val) {}
};

总结起来,可变的数据类型包括整型、浮点型和字符型,而不可变的数据类型包括字符串常量、常量变量、枚举类型和类中的常量成员。

指针和引用

C++中的指针和引用是两种用于处理变量间关系的机制,它们有一些区别,包括以下几个方面:

  1. 定义方式:

    指针:通过使用*来声明一个指针变量,例如:int* ptr;
    引用:通过使用&来声明一个引用,例如:int& ref = num;

  2. 内存关联:
    指针:指针是一个独立的实体,它存储变量的内存地址。指针可以指向任何数据类型,也可以为空(nullptr)。
    引用:引用是一个变量的别名,它与所引用的变量共享相同的内存地址。引用必须在声明时初始化,并且不能为空。

  3. 重新赋值:
    指针:指针可以被重新赋值为指向不同的变量或空地址。
    引用:引用一旦初始化后,就不能被重新赋值为引用其他变量。
    下面是一个例子来说明指针和引用的区别:

#include 
using namespace std;

int main()
{
    int num = 10;
    int *ptr = &num; // 声明一个指针,并将其指向 num 变量
    int &ref = num;  // 声明一个引用,将其绑定到 num 变量

    cout << "num: " << num << endl;   // 10
    cout << "Pointer value: " << *ptr << endl; // 解引用指针  10
    cout << "Reference value: " << ref << endl;  // 10

    num = 20; // 修改 num 的值
    cout << "num: " << num << endl;    // 20
    cout << "Pointer value: " << *ptr << endl;  // 指针的值仍然是 num 的地址 // 20
    cout << "Reference value: " << ref << endl; // 引用的值也被更新  // 20

    int anotherNum = 30;
    ptr = &anotherNum; // 将指针重新赋值为指向另一个变量
    // ref = anotherNum;  // 引用不能重新赋值

    cout << "Pointer value: " << *ptr << endl;  // 30
    cout << "Reference value: " << ref << endl;  // 20
    return 0;
}

在上述代码中,我们声明了一个指针ptr和一个引用ref,它们都指向变量num。我们可以通过解引用指针或直接使用引用来访问变量的值。在修改变量的值时,指针和引用都会反映出变化。指针可以重新赋值为指向不同的变量,而引用则不能重新赋值。

总结起来,指针提供了更多的灵活性,可以动态地指向不同的变量,而引用提供了更直接、更简洁的方式来访问变量,但在声明时需要确切地指定被引用的变量。

常量指针(底层const)和指针常量(顶层const)

指针常量:
指针常量是指定义了⼀个指针,这个指针的值只能在定义时初始化,其他地⽅不能改变。指针常量强调的是指针的不可改变性。

形式:
数据类型 * const 指针变量=变量名

int temp = 10;
int temp1 = 12;
int* const p = &temp;
// 更改:
p = &temp1; // 错误
*p = 9; // 正确

常量指针(底层const )
常量指针:
是指定义了⼀个指针,这个指针指向⼀个只读的对象,不能通过常量指针来改变这个对象的值。常量指针强调的是指针对其所指对象的不可改变性
形式:
(1)const 数据类型 *指针变量 = 变量名

int temp = 10;
const int* a = &temp;
int const *a = &temp;
// 更改:
*a = 9; // 错误:只读对象
temp = 9; // 正确

底层const和顶层const

在C++中,我们可以将const修饰符应用于不同的位置,以实现顶层const和底层const的概念。

  1. 顶层const(top-level const):它表示被修饰的对象本身是常量,不能被修改。可以将其应用于变量的定义或声明的顶层位置。

示例:

const int num = 10;  // 定义一个顶层const的整数变量
// num = 20;       // 错误!无法修改顶层const的值

在这个例子中,num 是一个顶层const的整数变量,其值被设为常量10,无法再修改为其他值。

  1. 底层const(low-level const):它表示被修饰的对象所指向的值是常量,不能通过该指针修改所指向的值。可以将其应用于指针的类型。
int num1 = 10;
const int* ptr1 = &num1;    // 底层const的指针,指向的值不可修改
// *ptr1 = 20;             // 错误!无法通过指针修改所指向的值
// 顶层const是常量,因此是指针常量。
int* const ptr2 = &num1;    // 顶层const的指针,指针本身不可修改
*ptr2 = 20;                 // 可以通过指针修改所指向的值

c++提高代码复用

C++中有几种方法可以实现代码复用,其中常用的包括继承和模板。

  1. 继承(Inheritance):继承是一种面向对象的特性,允许一个类从另一个类派生出来并继承其属性和方法。通过继承,子类可以重用父类的代码并添加自己的特定实现。这样可以避免重复编写相同的代码。
class Shape {
public:
    virtual double area() const = 0;
};

class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    double area() const override {
        return 3.14159 * radius * radius;
    }
};

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;
    }
};

在上述示例中,Circle和Rectangle类都继承自Shape类,并重写了area方法。通过继承,Circle和Rectangle类可以重用Shape类中的接口和行为,避免了重复编写计算面积的代码。

  1. 模板(Templates):模板是一种通用的代码复用机制,允许编写泛型代码,适用于多种数据类型。通过使用模板,可以在不同的类型上实例化相同的代码,提高代码的复用性。
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

在上述示例中,getMax函数是一个模板函数,可以接受任意类型的参数,并返回较大的值。通过使用模板,可以在编译时根据实际的参数类型生成对应的函数实例,实现了对不同类型的复用。

隐式类型转换

在C++中,可以通过重载赋值操作符(=)和类型转换函数来实现隐式的类型转换。

示例代码如下:

#include 

class MyString {
private:
    std::string str;

public:
    MyString(const std::string& s) : str(s) {}

    // 重载赋值操作符
    MyString& operator=(const std::string& s) {
        str = s;
        return *this;
    }

    // 类型转换函数
    operator std::string() const {
        return str;
    }
};

int main() {
    MyString myStr = "Hello";  // 隐式类型转换,将const char*转换为MyString对象
    std::string str = myStr;   // 隐式类型转换,将MyString对象转换为std::string

    std::cout << myStr << std::endl;  // 输出: Hello
    std::cout << str << std::endl;    // 输出: Hello

    return 0;
}

在上述代码中,定义了一个名为MyString的类,它内部包含一个std::string类型的成员变量。通过重载赋值操作符和定义类型转换函数,可以实现对MyString对象和std::string对象之间的隐式类型转换。

在main函数中,我们可以直接使用字符串字面值将其赋值给MyString对象myStr,通过重载的赋值操作符实现了从const char*到MyString的隐式类型转换。同样地,通过隐式类型转换函数,可以将MyString对象转换为std::string对象并赋值给str。

最后,通过输出语句打印了myStr和str的值,验证了隐式类型转换的结果。

char*

char* 是 C++ 中的一种指针类型,用于表示指向字符(char)数据的指针

在 C++ 中,字符串通常使用以空字符 (‘\0’) 结尾的字符数组来表示,也称为 C 风格字符串。而 char* 可以用于指向这样的字符数组首地址,从而方便对字符串进行处理和操作。

以下是 char* 的一些常见用法和操作:

  1. 字符串的声明和初始化:
char* str; // 声明一个指向字符的指针
str = "Hello"; // 初始化指针指向字符串常量
  1. 字符串的输出:
char* str = "Hello";
cout << str << endl; // 输出字符串 "Hello"
  1. 字符串的拷贝和连接:
char* src = "Hello";
char dest[10];
strcpy(dest, src); // 将源字符串拷贝到目标字符串
strcat(dest, " by"); // 连接字符串
  1. 字符串的比较:
char* str1 = "Hello";
char* str2 = "World";
int result = strcmp(str1, str2); // 比较两个字符串
if (result == 0) {
    cout << "Strings are equal" << endl;
} else if (result < 0) {
    cout << "String 1 is less than String 2" << endl;
} else {
    cout << "String 1 is greater than String 2" << endl;
}
  1. strlen

strlen 是 C++ 中的一个字符串处理函数,用于计算一个以空字符 (‘\0’) 结尾的字符数组C 风格字符串)的长度,即字符串中字符的个数(不包括空字符)。

以下是 strlen 函数的使用方法和示例:

#include 
#include 
using namespace std;

int main() {
    char str[] = "Hello";
    int length = strlen(str); // 使用 strlen 计算字符串长度 长度为5
    cout << "Length of the string: " << length << endl
    return 0;
}

在上述代码中,我们包含 头文件以使用 strlen 函数。然后,我们声明一个字符数组 str,并初始化为 “Hello”。通过调用 strlen(str),我们计算出字符串的长度并将结果存储在变量 length 中,最后将其输出。

需要注意的是,strlen 函数返回的是 size_t 类型的无符号整数,表示字符串的长度。当字符串长度超过 size_t 类型的表示范围时,可能会导致结果不准确。

此外,要确保传递给 strlen 函数的字符数组以空字符结尾,否则结果可能不正确。在 C++ 中,字符串字面值会自动以空字符结尾,而动态分配的字符数组或使用字符数组函数的返回值需要手动添加空字符。

总而言之,strlen 函数是一个常用的字符串处理函数,可以方便地获取字符串的长度,以便进行后续的字符串操作。

异常捕获

当在 C++ 程序中发生异常时,可以使用异常处理机制来捕获并处理异常。下面是一个简单的示例:

#include 
using namespace std;

int main() {
    try {
        int numerator, denominator;
        cout << "Enter the numerator: ";
        cin >> numerator;
        cout << "Enter the denominator: ";
        cin >> denominator;

        if (denominator == 0) {
            throw "Division by zero is not allowed!";
        }

        int result = numerator / denominator;
        cout << "Result: " << result << endl;
    }
    catch (const char* errorMessage) {
        cout << "Exception occurred: " << errorMessage << endl;
    }

    return 0;
}

在上述示例中,用户输入了两个数字作为分子和分母,然后进行除法运算。如果分母为零,则会抛出一个字符串类型的异常,指示除以零是不允许的。

在 try 块中,我们进行除法运算,并使用 throw 语句抛出异常。如果发生了异常,程序会跳转到 catch 块,并执行相应的异常处理代码。在本例中,我们捕获了一个 const char* 类型的异常,并输出相应的错误消息。

通过使用异常处理机制,我们可以在程序中检测和处理潜在的错误情况,以保证程序的稳定性和可靠性。

3.文件读取 txt文件

有 10 个学生,每个学生的数据包括学号、姓名、3 门课的成绩,从键盘输入 10 个学生的数据,并将其保存到文本文件 grade.txt 中,保存完成后,并读取显示。

#include 
#include 

struct Student
{
    int id;
    std::string name;
    double grades[3];
};

int main()
{
    const int numStudents = 10;
    Student students[numStudents];

    // 从键盘输入学生数据
    for (int i = 0; i < numStudents; i++)
    {
        std::cout << "Enter details for Student " << i + 1 << ":" << std::endl;
        std::cout << "ID: ";
        std::cin >> students[i].id;
        std::cout << "Name: ";
        std::cin >> students[i].name;
        std::cout << "Grades (3 subjects): ";
        for (int j = 0; j < 3; j++)
        {
            std::cin >> students[i].grades[j];
        }
    }

    // 将学生数据保存到文本文件
    std::ofstream outputFile("grade.txt");
    if (outputFile.is_open())
    {
        for (int i = 0; i < numStudents; i++)
        {
            outputFile << "Student " << i + 1 << ":" << std::endl;
            outputFile << "ID: " << students[i].id << std::endl;
            outputFile << "Name: " << students[i].name << std::endl;
            outputFile << "Grades: ";
            for (int j = 0; j < 3; j++)
            {
                outputFile << students[i].grades[j] << " ";
            }
            outputFile << std::endl
                       << std::endl;
        }
        outputFile.close();
        std::cout << "Student data saved to grade.txt." << std::endl;
    }
    else
    {
        std::cout << "Unable to create or open the file." << std::endl;
        return 1;
    }

    // 从文本文件读取并显示学生数据
    std::ifstream inputFile("grade.txt");
    if (inputFile.is_open())
    {
        std::string line;
        while (std::getline(inputFile, line))
        {
            std::cout << line << std::endl;
        }
        inputFile.close();
    }
    else
    {
        std::cout << "Unable to open the file for reading." << std::endl;
        return 1;
    }

    return 0;
}

打开文件模式

ios::in:表示以读取模式打开文件。该模式用于从文件中读取数据。如果文件不存在,则打开操作将失败。

ios::app:表示以追加模式打开文件。该模式用于在文件末尾添加数据,而不会覆盖原有内容。如果文件不存在,将创建一个新文件。

ios::out:表示以写入模式打开文件。该模式用于向文件中写入数据。如果文件不存在,将创建一个新文件。如果文件已存在,将清除原有内容并写入新数据。

函数指针

函数指针是指向函数的指针变量。它允许你在程序运行时动态地传递和调用函数,使得函数的行为可以根据需要进行灵活的变化。

  1. 声明函数指针:
返回类型 (*指针变量名)(参数列表);

这里的 返回类型 是函数的返回类型,指针变量名 是你给函数指针起的名字,参数列表 是函数的参数类型列表。

  1. 初始化函数指针:
指针变量名 = 函数名;

直接将函数名赋值给函数指针变量。

  1. 使用函数指针:
指针变量名(参数列表);

通过调用函数指针变量,可以执行对应的函数。

下面是一个简单的示例,演示了函数指针的使用:

#include 

// 声明一个函数指针类型
// 相当于说: 定义了一个函数指针的类型,也就是说,OperationFunc就表示的是(一种函数指针的类型)
// 指向返回值的类型为int,参数为int,int的函数
typedef int (*OperationFunc)(int, int);

// 定义加法函数
int add(int a, int b)
{
    return a + b;
}

// 定义减法函数
int subtract(int a, int b)
{
    return a - b;
}

int main()
{
    OperationFunc operation; // 声明函数指针变量

    // 初始化函数指针变量,指向加法函数
    operation = add;

    int result = operation(5, 3); // 使用函数指针调用加法函数
    printf("Result: %d\n", result);

    // 修改函数指针变量,指向减法函数
    operation = subtract;

    result = operation(5, 3); // 使用函数指针调用减法函数
    printf("Result: %d\n", result);

    return 0;
}

函数的返回值类型为函数指针的写法

#include 

int add(int a, int b)
{
    return a + b;
}

int subtract(int a, int b)
{
    return a - b;
}

// 返回一个函数指针,该函数指针指向一个返回类型为 int,接受两个 int 参数的函数
// 这是一个函数,函数名为getOperation,函数的参数为operators,函数的返回值是一个函数指针
// 这个函数指针指向返回值为int类型,参数为int,int的函数!
int (*getOperation(char operators))(int, int)
{
    if (operators == '+') {
        return add;
    } else if (operators == '-') {
        return subtract;
    } else {
        return NULL; // 返回空指针,表示无效操作符
    }
}

int main()
{
    char operators = '+';
    int result;

    // 调用 getOperation 函数获取相应的操作函数指针
    int (*operation)(int, int) = getOperation(operators);
    if (operation != NULL)
    { // 这时operation这个函数指针指向add这个函数
        result = operation(5, 3);
        printf("Result: %d\n", result);
    }
    else
    {
        printf("Invalid operator: %c\n", operators);
    }

    return 0;
}

你可能感兴趣的:(c++,笔记)