C++类复习

C++类

1. 类内成员函数隐式声明为inline

class Str {
    int x;
    int y = 3;
public:
     inline void fun(){
         std::cout<<"pf,yes!"<<std::endl;
     }
};

这段代码不会报错,但是类内的成员函数隐式声明为inline函数,不需要单独写在前面。因此将成员函数在类的内部实现是最保险的写法。

2. 全局函数上使用inline。

inline int Add(int x, int y) {
    return x + y;
}

这是全局函数上使用inline函数。

同样,上述代码可以写在头文件之中,或写在源文件之中,如果不使用 inline,那么写在头文件之中被多个源文件 include 那么就会导致符号重定义冲突,单个源文件引入却不会。

3. inline关键字的作用

  1. 可以在任意的函数定义的时候使用;

  2. 建议编译器使用内嵌的方式优化代码;

  3. inline函数,由调用的函数的源文件实现;

使用使用内联展开,取决于函数的复杂程度

以下的函数使用inline也不会被优化

1、函数指针或函数引用

2、复杂的 inline 函数体

3、虚函数

4、递归函数

举例说明如下

头文件中

#ifndef ICPP_HEADER_H
#define ICPP_HEADER_H
#include <iostream>
using namespace std;
class Str {
    int x;
    int y = 3;
public:
    void fun() {
        cout << "yes pf" << endl;

    }
};

#endif //ICPP_HEADER_H

main文件中

#include "header.h"
#include <iostream>
using namespace std;

int main() {
    int a = 0;
    int c = a + 7;
    Str str;
    str.fun();
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

另外一个源文件中这样写

#include "header.h"
#include <iostream>

编译成功

但是如果,头文件中,类内声明,类外定义,就会报错。

#ifndef ICPP_HEADER_H
#define ICPP_HEADER_H

#include <iostream>
using namespace std;
class Str {
    int x;
    int y = 3;
public:
    void fun();
};

void Str::fun() {
    cout << "yes pf" << endl;

}
#endif //ICPP_HEADER_H

报错信息如下:

/mnt/e/iCode/icpp/header.h:17: multiple definition of `Str::fun()'; CMakeFiles/icpp.dir/main.cpp.o:/mnt/e/iCode/icpp/header.h:17: first defined here
collect2: error: ld returned 1 exit status

问题分析

关于函数的声明和定义可以写在头文件之中,也可以写在源文件之中,但如果不加inline,那么写在头文件之中被多个源文件 include 那么就会导致符号重定义冲突,单个源文件引入却不会。

  1. 如果仅有一个头文件,一个文件使用头文件中的类的外部定义,不会出现问题;
  2. 类内定义和实现函数,不论有多少外部使用这个头文件的源文件都不会报错;
  3. 多文件使用头文件时,类内声明,类外定义,将类外的定义声明成内联函数,使用inline关键字不会报错;
  4. 掌握inline关键字的使用方法;

4. 分析下面几种写法

回答以下几个问题:

  1. 这个使用方法using my_int = int

  2. 类外声明的使用的使用方法,最好使用下面这种写法

  3. inline auto Str::fun2()->my_int {
        {
            cout<<"my_int"<<endl;
        }
    }
    

    举例如下:

//
// Created by pfw20 on 2024/3/31.
//

#ifndef ICPP_HEADER_H
#define ICPP_HEADER_H

#include <iostream>
using namespace std;
class Str {
    using my_int = int;
    my_int m;
    int x;
    int y = 3;
public:
    void fun();
    my_int fun1();
    my_int fun2();
};

inline Str::my_int Str::fun1() {
    {
        cout<<"my_int"<<endl;
    }
}
// 简化上述的代码另外一种写法
inline auto Str::fun2()->my_int {
    {
        cout<<"my_int"<<endl;
    }
}


inline int Add(int x, int y) {
    return x + y;
}
inline void Str::fun() {
    cout << "yes pf" << endl;

}
#endif //ICPP_HEADER_H

待说明:

\#include<iostream>

\#include<limits>

struct A{

  // char a = 'a';

  int c =10;

};

int main(){

  int x =10;

  char y = 'a';

  std::cout<<std::numeric_limits<int>::min()<<std::endl;

  std::cout<<sizeof(A)<<std::endl;

  std::cout<<alignof(int) <<std::endl;

}

5. this指针,指向当前对象

在类的内部会有一个this指针,类型为class* this 隐藏指针,this 是一个常量,指针不能修改,指针指向的内容是可以修改。

修改类的成员所以可以使用this->成员,访问类内的成员,

用处:

  1. 使用this可以防止函数中的参数和类中的成员混淆
  2. 基于const的成员函数重载

class Str{
	void fun(int x){
	
}
// 基于const的成员函数重载
	void fun(int x) const{ // this 本身不能修改,可以修改指向的内容,加上const之后,指向的内容也是不能修改的。
	}
	int x;
};
  1. & 基于引用的重载(不作重点记录)

这个和基于const的重载不能混淆使用。仅仅使用const或者仅仅使用引用

  1. 多个相同的变量出现在类的内部和全局中,使用类内部的可以使用this指针,全局的使用::即可

静态成员函数

所有的对象共享这个成员函数,每一个对象有自己的对象成员,不会出现交叉

静态成员就没有this指针,使用这个成员可以使用;类的操作域进行访问这个静态成员。

这个就可以将全局的常量定义为static的格式。

用处:

  1. 描述和类相关参数,和对象无关
  2. 静态成员函数可以返回静态数据成员;静态成员函数操作静态成员

单例模式

class Str{

static auto& size(){

static Str x;

return x;

}

}



6. 报错原因分析

为什么直接报错:/mnt/e/iCode/icpp/main.cpp:18: undefined reference to Str1::x’`

#include <iostream>
class Str1{
public:
    static int x;
    int y;

};
int main(){
    std::cout<<Str1::x<<std::endl;
}

修改如下运行成功

#include <iostream>
class Str1{
public:
    inline static int x;
    int y;
};
int main(){
    std::cout<<Str1::x<<std::endl;// 输出0
}

C++特性封装:举例子洗衣机的电路板

7. C++特殊的成员函数

构造函数

代理构造函数

构造函数是可以重载的

C++11的代理构造函数,代理构造函数先执行

代理构造函数如下:(基本不使用,仅仅了解即可)

\#include <iostream>
class Strp{
public:
    Strp():Strp(10){
    std::cout<<"here1"<<std::endl;
    }
    Strp(int input){
         x = input;
         std::cout<<"here2"<<std::endl;
    }
private:
    int x;
};

int main(){
    Strp strp1;
}


构造函数初始化(重要)

初始化列表,不使用缺省初始化

区分数据成员的初始化和赋值

  • 在构造的时候通过列表初始化完成,提升系统的性能
#include <iostream>
#include <string>

class Strp {
public:
    Strp(const std::string &val) : x(val), y(0) {
        std::cout<<x<<std::endl;
    }

private:

    std::string x;
    int y;
};

int main() {
    Strp strp1("pf");
}
  • 类中包含引用的成员,此时必须使用列表进行初始化
#include <iostream>
#include <string>

class Strp {
public:
    Strp(const std::string &val,int& ref_i)
    : x(val)
    , y(0)
    ,ref(ref_i) {
        std::cout<<x<<std::endl;
        ref =2;
    }

private:

    std::string x;
    int y;
    int& ref;
};

int main() {
    int val;
    Strp strp1("pf",val);
}
  • 元素的初始化顺序与其声明的顺序有关,与初始化列表中的顺序无关;

    要求在初始化化列表时,顺序要一致,这里的一致就是先声明的先进行初始化,否则会包warnning

  • 使用初始化列表覆盖类的内部的初始化信息

缺省的构造函数

  • 不提供实参的构造函数

  • 如果没有定义构造函数,编译器会提供一个缺省的构造函数,目的和C语言兼容,如果写一个就不会合成,如果类的成员中有引用成员,引用必须显示初始化。

  • 缺省的构造函数会缺省构造函数进行缺省初始化。

  • 调用缺省的构造函数避免(most vexing parse)

  • 使用default定义,和内部的缺省的构造函数是一样的

举例子说明如下:

报错:

#include <iostream>
#include <string>

class Strp {
public:
//    Strp() = default;

    Strp(const std::string &input) : x(input) {}

    std::string x;
};

int main() {
Strp s;
}

No matching constructor for initialization of ‘Strp’

#include <iostream>
#include <string>

class Strp {
public:
    Strp() = default;

    Strp(const std::string &input) : x(input) {}

    std::string x;
};

int main() {
Strp s;
}

不再进行报错

或者下面这样写,也不会报错

#include <iostream>
#include <string>

class Strp {
public:
    Strp(){}

    Strp(const std::string &input) : x(input) {}

    std::string x;
};

int main() {
Strp s;
}

单一的参数构造函数

  • 可以将其看成是一种类型转换

  • 可以使用explicit关键字避免求值过程中的隐式转换

    #include <iostream>
    #include <string>
    
    class Myclass {
    public:
    //    explicit 
        Myclass(const int& input): x(input) {
        }
        int x;
    };
    
    int main() {
        Myclass my(3);// 直接参数列表进行初始化
        Myclass m1 = 3;// // 拷贝初始化,涉及到类型的隐式转换,避免隐式转换使用explicit进行标注
        Myclass m2{3};
        Myclass m3(Myclass(3));
    
    }
    

拷贝的构造函数(重要)

原有的对象来构造一个新的构造函数

不希望改变值,不能使用值传递,原因是会进行嵌套,死循环

  • 拷贝构造函数的经典写法
  • 如果没有显示提供拷贝构造函数,编译器会自动生成一个,对每一个数据成员调用拷贝构造
#include <iostream>
#include <string>

class Myclass {
public:
//    explicit // 全部考虑是引用传递,引用是对值的绑定
// 单一参数的构造函数
    Myclass(const int &input) : x(input) {
    }

    // 缺省的构造函数
    Myclass() = default;

    // 拷贝的构造函数的经典写法,为什么是const,为什么是引用
//    Myclass(const Myclass&) = default;
    Myclass(const Myclass &m) : x(m.x) {// 这个也可以使用default

    }

    int x = 4;
};

int main() {
    Myclass my(3);// 直接参数列表进行初始化
    Myclass m1 = 3;// // 拷贝初始化,涉及到类型的隐式转换,避免隐式转换使用explicit进行标注
    Myclass m2{3};
    Myclass m3(Myclass(3));

}

移动构造函数(待更新)

  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值