C++面向对象学习笔记(持续学习中)

一、类与对象(一)

1. 拷贝构造函数

class Complex{
   
    public:
        double real, imag;
        Complex(){
   }
        //拷贝构造函数
        Complex(const Complex & c){
   
            real = c.real;
            imag = c.imag;
            cout << "Copy Constructor called" << endl;
        }
        //拷贝构造函数不允许定义成Complex(Complex c)
}

拷贝构造函数起作用的三种情况

  1. 一个对象去初始化另一个对象
    • Complex c2 = c1;
    • Complex c2(c1);``````//初始化语句,不是赋值语句
  • 赋值语句并不调用拷贝构造函数
class A{
   
public:
    int val;
    A(int n){
   
        val = n;
    }
    A(const A & a){
   
        cout << "Copy constructor called" << endl;
    }
};
int main(){
   
    A a1, a2;
    a1.val = 5;
    a2 = a1;//这不是初始化,这是赋值
    return 0;
}
  1. 某个函数的参数是类的对象
class A
{
   
    public:
    A() {
   };
    A( A & a)
    {
   
        cout << "Copy constructor called" << endl;
    }
};
void Fun(A a){
   }
int main(){
   
    A a;
    Fun(a);
    return 0;
}
    结果为:```Copy constructor called```
  1. 函数的返回值是类的对象
class A{
   
public:
    int val;
    A(int n){
   
        val = n;
    }
    A(const A & a){
   
        cout << "Copy constructor called" << endl;
    }
};
A Func(){
   
    A b(3);
    return b;
}
int main(){
   
    cout << Func().val << endl;
    return 0;
}

结果是:
Copy constructor called
3

2. 内联函数

  • a. 解决函数调用中的效率问题
  • b. 在类中定义的函数都是内联函数,即使没有inline限定符
  • c. 编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,其他函数都是在运行时才被替换
  • 使用内联函数要求:

    1. 内联函数体内不允许存在switch和循环语句
    1. 内联函数定义在其第一次被调用之前
    1. 不允许定义内联递归函数

二、类与对象(二)

1. this指针

  • a. this指针如果为空,不能访问类的成员,但是只要不访问类的成员,对为空指针的类的指针可以调用类的成员函数。this指针存在于类的非静态成员函数里。一个对象被创建,this指针指向存放该对象数据的首地址
  • b. this指针是常量指针
class A{
   
    int i;
public:
    void hello(){
   
        std::cout << "hello" << std::endl;
    }
    void goodbye(){
   
        std::cout << this->i << "goodbye" <<std::endl;
    }
};
int main(){
   
    A *p = nullptr;
    p->hello();     //只是输入输出,没有访问对象
    //p->goodbye(); 出错,为空的实例化访问了成员
}

2. 静态数据成员

  • a. 初始化在类体外进行,初始化时不加访问类型限定符,初始化时要加作用于限定符
  • b. 静态数据成员是类的成员,而不是对象的成员,静态数据成员是类的所有对象共享的,本质上是全局变量
  • c. 静态数据成员在类加载的时候就会分配内存,静态数据成员只存储一处,sizoof运算符不会计算静态成员变量
class M{
   
public:
     M(int a) {
   
        A=a;
        B+=a;
    }
     static void f1(M m);
private:
     int A;
     static int B;
}
int M::B=0; //静态数据成员初始化的格式
void M::f1(M m)
{
   
     cout << "A = " << m.A << endl; //静态成员函数中通过对象来引用非静态成员
     cout << "B = " << B << endl;
}
int main(){
   
    M P(5),Q(10);
    M::f1(P); //静态成员函数调用时不用对象名
    M::f1(Q);
    return 0;
}

运行结果

A = 5
B = 15
A = 10
B = 15

3. 静态成员函数

  • a. 不能访问费静态成员变量
  • b. 不能调用费静态成员函数

4. 拷贝构造函数的调用时机

  • a. 调用一个以该类的对象作为参数的函数时
  • b. 调用一个以该类的对象作为返回值的函数时
  • c. 拷贝之后也需要消亡
class A{
   
public:
    int i;
    A(int x){
   
        i = x;
        cout << "Constructor called" <<endl;
    }
    ~A(){
   
        cout << "Destructor called" <<endl;
    }
    A(const A & p){
   
        i = p.i;//不加这句i就没有初始化
        cout << "Copied constructor called" << endl;
    }
    static void Test(A a, A b);
};
void A::Test(A a, A b){
   
    cout << a.i << endl;
    cout << b.i << endl;
}
int main(){
   
    A a(1), b(2);
    A::Test(a, b);
    return 0;
}

运行结果

Constructor called
Constructor called
Copied constructor called
Copied constructor called
1
2
Destructor called
Destructor called
Destructor called
Destructor called

5. 友元类和友元函数

    1. 一个类的友元函数可以访问该类的私有成员
    1. 可以将一个类的成员函数(包括构造函数和析构函数)说明为另一个类的友元
    1. A若为B的友元类,A的成员函数可以访问B的私有成员
class CCar{
   
private:
    int price;
    friend class CDriver;
    //声明 CDriver 为友元类
    friend void CDriver::ModifyCar(CCar * pCar);
    //声明另一个类的成员函数为友元函数
    friend int MostExpensiveCar( CCar cars[], int total);
    //声明函数为友元函数
};
    1. 友元类之间的关系不能传递,不能继承。

6. 常量成员函数

    1. 常量对象只能使用构造函数、析构函数和由const说明的函数(常量方法)
    1. 常量成员函数不能更改属性值,也不能调用非常量成员函数
    1. mutable成员变量:可以在const成员函数里修改
class A{
   
private:
    int value;
    mutable int n;
public:
    void Func(){
   }
    Sample(){
   }
    void SetValue(){
   }
    void SetValue2() const{
   
        n++;        //可以添加,mutable类型变量可以在常量方法中修改
        //value = 0;不能添加此语句,因为常量成员函数内部不能改变属性值
        //Func(); 也不能调用非常量成员函数
    }
};
const Sample Obj;
//Obj.SetValue();错误
Obj.SetValue2();//正确,常量成员使用常量成员函数

三、运算符重载

1. 重载格式

<T> operator 运算符(形参表)

2. 可以重载为普通函数,也可以重载为成员函数

  • 重载为成员函数时,参数个数为该运算符所需参数数目减一
  • 重载为普通函数时,参数个数为该运算符所需参数的数目
class Complex{
   
public:
    double real, imag;
    Complex(double r = 0.0, double i = 0.0): real(r), imag(i){
   
        cout << "Cons called"  << endl;
    }
    ~Complex(){
   
        cout << "Dest called"  << endl;
    }
    Complex(const Complex& a){
   
        real = a.real;//必不可少
        imag = a.imag;//必不可少
        cout << "Copied cons called" << endl;
    }

    Complex operator - (Complex a);
};
Complex operator + (Complex a, Complex b){
   
    return Complex(a.real + b.real, a.imag + b.imag);
}
Complex Complex::operator - (Complex a){
   
    return Complex(real - a.real, imag - a.imag);
}
int main()
{
   
    Complex a(4, 4), b(1, 1), c;//
    cout << a.real << ", " << a.imag << endl;
    c = a + b;//这里,要在拷贝构造函数里给拷贝对象赋值,否则为随机数
    cout << c.real << ", " << c.imag << endl;
    return 0;
}

3. 赋值运算符 '='的重载

  • a. 尽量保持原运算符特性
  • b. T & func(), 要用this返回

4. 运算符重载为友元函数:解决 c = c + 5c = 5 + c的问题

class Complex{
   
    double real, imag;
public:
    Complex(double r, double i): real(r), imag(i){
   };
    Complex operator + (double r);  //解释c+5的重载
    friend Complex operator + (double r, const Complex& a);  //解释5+c的重载
};
Complex Complex::operator + (double r){
   
    return Complex(real + r, imag);
}
Complex operator + (double r, const Complex& a){
   
    return Complex(a.real + r, a.imag);
}

5. 流运算符的重载

  • 直接输出复数:
class Complex{
   
    double real, imag;
public:
    Complex(double r = 0, double i = 0): real(r), imag(i){
   };
    friend ostream & operator << (ostream & is, const Complex & c);
    friend istream & operator >> (istream & is, Complex &c);
};
ostream & operator << (ostream & os, const Complex & c){
   
    os << c.real << " + " << c.imag << "i";
    return os;
}
istream & operator >> (istream & is, Complex & c){
   
    string s;   //把```is```的内容提取到string里,后续直接操作string来得到具体结果
    is >> s;
    int pos = s.find('+', 0);
    string sTmp = s.substr(0, pos);
    c.real = atof(sTmp.c_str());
    sTmp = s.substr(pos + 1, s. length() - pos - 2);
    c.imag = atof(sTmp.c_str());
    return is;
}
int main()
{
   
    Complex c;
    int n;
    cin >> c >> n;
    cout << c << "," << n;
    return 0;
}

6. 自增减运算符的重载

  • 重载为前置运算符
    • 返回值一定是 T &类型
    • 一定不带多余的参数,比如这种 T operator--(int)
  • 重载为后置运算符
    • 返回值一定不是 T &类型,而是T类型
    • 一定带有多余参数
  • 重载为成员函数
    • 参数列表一定不带有T
    • 前置:T & operator++();
    • 后置:T & operator++(int);
  • 重载为全局函数
    • 参数列表一定带有T
    • 前置:T & operator++(T1);
    • 后置:T operator++(T1, int);

四、继承与派生

1. 派生类拥有基类的全部成员函数和成员变量,不论是何种访问保护类型

2. 派生类的各个成员函数中,不能访问基类的private成员

3. 派生类对象包含着基类对象,在内存中的存储位置在基类对象的存储位置后

4. 设置属性时可调用基类的相同函数

void SetInfo(const string & name_, const string & id_, unsigned int age_, char gender_, string department_){
   
        CStudent::SetInfo(name_, id_, age_, gender_);
        department = department_;
    }

5. 类之间的两种关系:继承关系和复合关系

  • 继承关系: 基 类 A , 有 派 生 类 B 基类A,有派生类B AB ⇒ \Rightarrow 一 个 B 对 象 也 是 一 个 A 对 象 一个B对象也是一个A对象 BA
  • 复合关系:正确的写法为狗类和主人类都设置对象指针
class Master;
class Dog{
   
    Master *pm;
};
class Master{
   
    Dog *dogs[10];
};

6. 派生类覆盖基类成员:派生类定义了和基类成员同名的成员

  • 缺省情况下,默认访问派生类定义的成员,访问基类成员要是用作用于符号::
class base{
   
    int j;
public:
    int i;
    void func();
};
class derived: public base{
   
public:
    int i;
    void access();
    void func();
};
void derived::access() {
   
    //j = 5;error,直接访问了基类数据成员
    i = 5;       //引用的是派生类的 i
    base::i = 5; //引用的是基类的 i
    func();     //派生类的
    base::func();
}
int main(){
   
    derived obj;
    obj.i = 1;
    obj.base::i = 1;
}

7. protected成员的可访问性

  • 基类的private成员:
    • 基类的成员函数
    • 基类的友元函数
  • 基类的protected成员:
    • 基类的成员函数
    • 基类的友元函数
    • 派生类的成员函数:可以访问当前对象的基类的保护成员

“当前对象的基类”

class Father{
   
private:
    int nPrivate;
public:
    int nPublic;
protected:
    int nProtected;
};
class Son: public Father{
   
    void AccessFather(){
   
        nPunlic = 1;
        //nPrivate = 1;无权访问
        nProtected = 1;
        /* 错误,f不是当前对象 Son f; f.nProtected = 1; */
    }
};

8. 派生类构造函数:先调用基类构造函数,再执行各成员对象类的构造函数,最后执行派生类构造函数

FlyBug::FlyBug(int legs, int color, int wings): Bug(legs, color){
   
    nWings = wings;
}

9. public继承

有如下代码

class base;
class derived: public base;
base b;
derived b;
  • 派生类的对象可以赋值给基类对象:b = d;
  • 派生类的对象可以初始化基类引用:base& br = d;
  • 派生类的对象可以赋值给基类指针:base* pb = &d;

10. privateprotected继承

  • protected继承时,基类的public成员成为派生类的protected成员
  • private继承时,基类的public成员成为派生类的private成员,基类的protected成为派生类的不可访问成员

五、多态

1. 虚函数:由四.9的三种情况可知

  • 派生类的指针赋给基类指针,通过基类指针调用基类和派生类的同名虚函数:指针指向什么类的对象就调用什么类的虚函数
class A{
   
public:
    virtual void some(){
   
        cout << "A" << endl;
    }
};
class B: public A{
   
public:
    virtual void some(){
   
        cout << "B" << endl;
    }
};
int main(){
   
    A a;
    B b;
    A* pa = &a;
    B* pb = &b;
    pa->some(); //输出A
    pa = pb;
    pa->some(); //输出B
    return 0;
}
  • 通过判断基类的引用引用了哪种类型的对象来调用对应的虚函数
  • 纯虚函数 virtual void funtion1() = 0;
    • 在基类中只有声明,没有定义,必须在子类中加以实现。这个基类叫虚基类(抽象类)
    • 虚基类不能直接被实例化,只能在被继承并重写虚函数后才可以使用
A a     //错误,A是抽象类,不能创建对象
A * pa; //正确,可以定义抽象类的指针和引用
pa = new A; //错误,A是抽象类,不能创建对象
  • 通过判断基类的引用引用了哪种类型的对象来调用对应的虚函数
  • 纯虚函数 virtual void funtion1() = 0;
    • 在基类中只有声明,没有定义,必须在子类中加以实现。这个基类叫虚基类
    • 虚基类不能直接被实例化,只能在被继承并重写虚函数后才可以使用
    • 虚函数、纯虚函数区别和联系
  • 派生类中和基类中虚函数同名同参数的函数,不加virtual也自动成为虚函数

2. 在非构造函数、非析构函数的成员函数中调用虚函数是多态,在构造函数和析构函数中调用虚函数,不是多态

3. 虚析构函数:基类的析构函数应该声明为 virtual

六、输入输出

1. istream类的成员函数

  • istream& getline(char *buf, int bufSize, char delim)
    • char delim是额外参数,该函数从输入流读取bufSize - 1个字符到缓冲区buf,或者遇到delim为止
    • 自动在buf结尾添加\0,读取到\ndelim之前的字符数达到或超过bufSize个就会出错
    • 遇到\n就会返回
int x;
char buf[100];
cin >> x;
cin.getline(buf, 90);
cout << buf << endl;
return 0;

输入1:
12 abcd(回车)
输出1:
abcd
输入2:
12(回车)
输出1:
立即返回(读到了留在流中的'\n'

2. 输入输出重定向

#include <cstdio>
#include <iostream>
using namespace std;
int main(){
   
    int n;
    freopen("out.txt", "w", stdout); //将标准输出重定向到out.txt文件内
                                    //w模式是重新写,追加写是a+
    freopen("in.txt", "r", stdin)   //将标准输入重定向至in.txt文件内读数据
    while(cin >> n){
   
        cout << n << " " << n * n << endl;
    }
    return 0;
}

输出:从in.txt文件内读入数字,将其平方写入out.txt

3. 流操纵算子

  • cout.precision(6)cout << setprecision(6):输出六位有效数字
  • cout << setiosflags(ios::fixed) << setprecision(6):定点输出6位小数
  • cout << setiosflags(ios::fixed) << setprecision(6) << x << resetiosflags(ios::fixed) << x:取消小数定点输出模式,回归六位有效数字模式
  • cout << scientific << setprecision(3) << x << endl;:科学计数法,3位定点小数(而不是3位有效数字)
  • 对齐宽度:
int n = 1000;
cout << setw(12) << setfill('*') << showpos << right << n << endl;

解释:十二位宽度右对齐输出,空位用*填充,非负数显示正号

七、文件操作

1. 字符文件读写

#include <fstream>
#include <string>
using namespace std;
int main(){
   
    string str;
    ifstream srcFile("in.txt", ios::in);
    ofstream destFile("out.txt", ios::out);
    string x;
    while(srcFile >> x)
        destFile << x << endl;
    destFile.close();
    srcFile.close();
    return 0;
}

其中:
ifstream srcFile("in.txt", ios::in):打开一个已经存在的文件
ofstream destFile("out.txt", ios::out):创建一个不存在的文件
srcFile >> x方式:文件流的内容输入到x中,遇到空格和回车就结束本次操作。如果要读取一整行(忽略空格),请使用getline(srcFile, x)方法

2. 二进制文件读写

  • write函数:要转换成char *类型
ofstream fout("some.dat", ios::out | ios::binary);
int x = 120;
fout.write((const char *)(&x), sizeof(int));
  • 一次性读取全部数据到数组里
//假设有一个src.dat文件存放着100个int型数据
//现在将其一次性读取到int x[100]中
int x[100];
ifstream inFile("src.dat", ios::in | ios::binary);
inFile.read((char *)&x[0], sizeof(x));

八、函数模板

template <class cls1, class cl2, .....> _RtnType func(cls1 c1, cls2 c2, ...)

1. 只要函数模板的形参表或类型形参表不同,就可以重载

template <class T1, class T2> void print(T1 arg1, T2 arg2);
template <class T> void print(T arg1, T arg2);
template <class T, class T2> void print(T arg1, T arg2);

2. 函数模板和函数的匹配次序

  • 2.1 参数完全匹配的普通函数
  • 2.2 参数完全匹配的模板函数
  • 2.3 实参类型自动转换后能匹配的普通函数
  • 2.4 Error

九、类模板

template <typename cls1, typename cl2, .....>
//类型参数表
 class Name{
   
    //MemberFunc and Merber varibles
}

1. 实例化后的类模板:a1b1是不同的类

MyPair<string, int> a1("Tom", 19);
MyPair<string, int> b1("Gao", 22);

2. 函数模板作为类模板成员

template <class T, int size>    //int size 属于非类型参数
class CArray{
   
    T array[size];
public:
    CArray();
    void Print(){
   
        for(int i = 0; i <size; i++)
            cout << array[i] << endl;
    }
};
template <class T, int size>    //Constructor is written like this
CArray<T, size>::CArray(){
   
    for(int i = 0; i < size; i++){
   
        array[i] = i + 1;
    }
}
int main(){
   
    CArray <double, 10> a;  //Constructor被实例化
    CArray <int, 10> b;     //Constructor再次被实例化,a和b属于不同的类
    a.Print();
    b.Print();
    return 0;
}

3. 类模板与派生

  • 类模板从类模板派生
template <class T1, class T2>class A{
   
public:
    A();
    T1 v1;
    T2 v2;
};
template <class T1, class T2>class B: public A<T2, T1>{
   
public:
    B();
    T1 v3;
    T2 v4;
};
template <class T> class C: public B<T, T>{
   
public:
    C();
    T v5;
};
  • 类模板从模板类派生:下例自动生成A<int, double>B<char>
template <class T1, class T2> class A{
   
public:
    A(){
   
        cout << "A" << endl;
    }
    T1 v1;
    T2 v2;
};
template <class T> class B: public A<int, double>{
   
public:
    B(){
   
        cout << "B" << endl;
    }
    T v3;
};
  • 类模板从普通类派生
class A{
   
public:
    A(){
   cout << "A" << endl;}
    int v1;
};
template <class T> class B: public A{
      //所有从B实例化得到的类,都以A为基类
public:
    B(){
   cout << "B" << endl;}
    T v;
};

4. 类模板与友元函数

  • 函数模板作为类的友元
class A{
   
    T1 v;
public:
    A(T1 n): v(n){
   }
    template <class T> friend void Print(const T& p);
    //所有从template <class T> void Print(const T& p)生成的函数都是A的友元
};
  • 类模板作为类模板的友元
template <class T> class B{
   
    T v;
public:
    B(T n): v(n){
   }
    template <class T2> friend class A;
};
template <class T> class A{
   
public:
    void Func(T n){
   
        B<T> o(n);
        cout << fixed << setprecision(8) << o.v << endl;
    }
};
//任何从A模板实例化出来的类,都是任何B实例化出来的类的友元

5. 类模板和static成员

template <class T> class A{
   
private:
    static int count;
public:
    A(){
   count++;}
    ~A(){
   count--;}
    A(A&){
   count++;}
    static void PrintCount(){
   cout << count << endl;}
};
template<> int A<int>::count = 0;
//表示定义一个A<int>的全局变量count初始化为0
//A<int>::count = 0; 这样不能访问private成员
template<> int A<double>::count = 0;
int main(){
   
    //A<int> *ia; //不调用构造函数
    A<int> *ia = new A<int>;    //调用构造函数
    A<double> da;               //调用构造函数
    ia->PrintCount();
    da.PrintCount();
    return 0;
}

九、string

1. string支持流运算符

string s;
cin >> s;
cout <<s << endl;

2. string支持getline方法

如果需要输入空格时使用,遇到回车终止

string str;
getline(cin, s);

3.string使用成员函数复制

string str1("catpig"), s3;
s3.assign(s1, 1, 3);    //从s1中小标为1的字符开始复制3个字符给s3

4. string类的访问

使用at方***做范围检查,而[]不会

string s;
for(int i = 0; i < s.length(); i++)
    cout << s1.at(i) << endl;

5. 字符串的连接

append方法

string s1("good"), s2("morning");
s1.append(s2);  // 全部连接
cout << s1 << endl;
s2.append(s1, 3, s1.size());   // 下标从3开始,连接s1.size()个字符,显然超出了范围,但是会做范围检查,复制到字符串最后一个字符

6. 子串substr

string s1("hello, world"), s2;
s2 = s1.substr(4, 5);   //从下标4开始的5个字符
cout << s2 << endl;

7. 查找

string s1("hello world");
cout << s1.find("l") << endl;   //2
cout << s1.rfind("l") << endl;  //9,从后向前查找第一次出现的位置

十、STL

1. 容器:

  • 顺序容器:vectordequelist
    • 元素是非排序的,元素的插入位置同值无关
  • 关联容器:setmultisetmapmultimap
    • 元素是排序的
    • 底层使用平衡二叉树实现,插入和检索时间长都是 O ( l o g ( N ) ) O(log(N)) O(log(N))
  • 容器适配器:stackqueuepriority_queue

2. 顺序容器和关联容器共有方法:

  • begin:容器首元素的迭代器
  • end:容器末尾元素的后面一个位置的迭代器
  • rbegin:最后一个元素的迭代器
  • rend:首元素前面的位置的迭代器
  • erase:删除一个或几个元素
  • clear:从容器中删除所有元素
  • front:首元素的引用
  • back:末尾元素的引用
  • push_back:尾插
  • pop_back:删除末尾元素
  • erase:删除迭代器指向的元素或者区间删除,返回被删元素后面的那个元素的迭代器

3. “迭代器”

  • 用于指向顺序容器和关联容器中的元素
  • 容器适配器stackqueuepriority_queue,不支持迭代器
  • 类似指针,分为const和非const两种,非const还可以修改指向的元素
  • 定义容器迭代器
    • T::iterator name;
    • T::const_iterator name;
    • T::reverse_iterator r;
      • 反向迭代器,用于逆序访问
      • 用法
        • for(vector<int>::reverse_iterator r = vec.rbegin(); r != vec.rend(); r++)
    • 访问迭代器指向的元素
      • * iterator_name
    • 迭代器可以使用++指向下一个元素

4. 算法

  • 4.1 find()
    • Iterator find(Iterator begin, Iterator end, T key)
    • 查找区间为 [ b e g i n , e n d ) [begin, end) [begin,end),内部使用==判断是否相等
    • 返回值为迭代器类型,如果找到,返回的迭代器指向该元素;否则指向end(容器末尾元素的后面一个位置的迭代器)
vector<int> v;
int array[4] = {
   1, 2, 3, 4};
vector<int>::iterator p = find(v.begin(), v.end(), 3);
if(p != v.end())
    cout << *p << endl;
int *pp = find(array, array + 4, 30);   //数组名作为迭代器
  • 4.2 sort
bool operator()(const T & a1, const T & a2){
   
    //return a1 < a2; //升序排列
    return a1 >  a2;    //降序排列
    //返回为true,代表a1必须排在a2前面;否则表示a1不必须在a2前面
}

5. setmultiset

  • 5.1 内部元素有序排列
  • 5.2 除了容器共有函数,还支持下列成员函数
    • find:查找某个值的元素
    • lower_bound:查找某个下界
    • upper_bound:查找某个上界
    • equal_bound:同时查找上下界
    • count:计算等于某个值的元素的个数
    • insert:插入某一元素或区间
全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务