AI 摘要

抱歉,我无法生成摘要而没有内容。请提供文章内容,以便我可以创建摘要。谢谢!

1 结构体与对象聚合

1.1 结构体

#include <iostream>

//结构体的声明
struct Str;

//结构体的定义,后面要有分号
struct Str
{
    int x;
    int y;
};//可以写别名

int main()
{
    Str m_str;
    m_str.x = 3;
    std::cout << m_str.x << std::endl;
    return 0;
}

上面这个结构体和C语言兼容

#include <iostream>

//结构体的声明,不完全类型
struct Str1;

//结构体的定义,后面要有分号
struct Str
{
    int x;
    int y;
};//可以写别名

int main()
{
    Str m_str;
    Str1* m_str1;
    return 0;
}

不完全类型可以声明指针

结构体在一个翻译单元遵循一处定义原则,但是不同翻译单元可以分别定义

1.2 数据成员(数据域)

#include<iostream>

struct Str
{
    int x;//数据成员的声明
    int y;
};

int main()
{


}

结构体的定义包含数据成员的声明

#include<iostream>

struct Str
{
    decltype(3) x;
    int y;
};

int main()
{
    Str m_str;

}

C++11开始可以使用decltype来声明,不能使用auto

数据成员可以使用const声明

C++11开始数据成员可以类内初始化

#include<iostream>

struct Str
{
    int x;
    int y;
};

int main()
{
    Str m_str{3,4};
    std::cout << m_str.x << std::endl;

}

和数组类似,结构体可以聚合初始化,但是这种方式不利于调整!

#include<iostream>

struct Str
{
    int x;
    int y;
};

int main()
{
    Str m_str{.x = 3,.y = 4};
    std::cout << m_str.x << std::endl;

}

C++20标准,可以使用上述方式初始化,基本可以保证程序正确

#include<iostream>

struct Str
{
    mutable int x = 0;
    int y = 1;
};

int main()
{
    const Str m_str;
    m_str.x = 3;

}

结构体声明为const,则结构体不可修改

如果想修改结构体内部元素,可在声明数据成员时使用mutable

1.3 静态成员

多个对象之间共享数据成员

#include<iostream>

struct Str
{
    static int x;
    int y = 1;
};
int Str::x;
int main()
{
    Str m_str1;
    Str m_str2;
    m_str1.x = 100;
    std::cout << m_str2.x << std::endl;
    //res:0
    return 0;

}
#include<iostream>

struct Str
{
    const static int array_size = 100;
    int a[array_size];
};

int main()
{
    Str m_str;
    return 0;

}

C++98类外定义const静态成员类内初始化(如果没有类外定义可以运行,但是编译器替换,实际没有开辟内存)

C++17:在.h文件里加内联静态inline static定义,并且这种方式可以使用auto推导类型

#include<iostream>

struct Str
{
    static int array_size;
};
int Str::array_size = 3;
int main()
{
    Str m_str;
    std::cout << Str::array_size << std::endl;

    return 0;

}

静态数据成员可以直接加类名访问,可以不是对象名

#include<iostream>

struct Str
{
    static Str x;
};
Str Str::x;
int main()
{
    Str m_str1;


    return 0;

}

上述代码去掉static会报错

2 成员函数

也可称之为成员方法

对内操作数据,对外提供接口

在C++中将数据和方法放在一起,就是类的概念,需要使用关键字class

类是抽象数据结构,类形成域,叫类域

2.1 成员函数的声明与定义

类内定义是隐式内联

类内声明+类外定义

#include<iostream>

class Str
{
public:
    void fun()
    {
        std::cout << x << std::endl;
    }
    int x;
};

int main()
{
    Str m_str1;
    return 0;
}

上述代码不会报错,原因:编译期两遍处理,先忽略函数定义,把类处理完之后再处理函数定义内部逻辑。

类的尾随返回类型会更智能

成员函数调用时默认会传一个隐藏的this指针,指向类的地址,this不能被赋值

#include<iostream>

class Str
{
public:
    void fun(int x)
    {
        x = x;
        std::cout << x << std::endl;
    }
    int x;
};

上面的代码,调用的x会认为是传入的参数x,而不是类内的x,需要使用this->x来区分,函数内部名称会隐藏类的成员名称,类内名称会隐藏类外名称

在函数声明后面加const会保证函数不能修改类内成员

2.2 静态成员函数

返回、操作静态数据成员,不能调用对象的成员

调用 类名::函数(...)

2.3 成员函数重载

可以基于const重载(可以理解为传入的隐藏this指针参数不一样)(C++98)

基于引用限定符的重载(C++11)*

两种重载不能混用


3 访问限定符与友元

使用public、private、protected去限定访问权限

结构体缺省访问权限为public

类缺省访问权限为private

#include<iostream>

int main();
class Str
{
private:
    friend int main();
    int x;
};

int main()
{
    Str m_str;
    std::cout << m_str.x << std::endl;

    return 0;

}

关键字friend声明函数为类的友元函数(或者声明类为类的友元)可以打破访问限制,友元声明放在public还是private或者protected没区别

首次声明友元函数或者友元类,会视为声明,可以通过编译,但是加了限定符::,则不认为是声明,必须在前面有该函数的声明

友元函数可以在类内定义,也可以在类外定义

#include<iostream>

int main();
class Str
{
    int y;
    friend void fun()
    {
        Str val;
        std::cout << val.y << std::endl;

    }
};

int main()
{
    Str m_str;
    fun();

    return 0;

}

上述代码的fun作用域是全局域,虽然在内部定义的fun函数,但是还是会报错,因为类内视为定义,但作用域是全局,全局没有声明。(隐藏友元:常规名称查找无法找到,可以防止误用)

#include<iostream>

int main();
class Str
{
    int y;
    friend void fun(const Str& val)
    {
        std::cout << val.y << std::endl;

    }
};

int main()
{
    Str m_str;
    fun(m_str);

    return 0;

}

这个代码可以通过,因为除了常规查找,传的参数类内也会查找,可以发现fun的定义,好处可以减轻编译器负担,减小搜索范围


4 构造、析构与复制成员函数

构造函数名和类名相同,没有返回类型(隐式的返回类型是类)

4.1 默认构造函数

#include<iostream>

class Str
{
public:
    Str()
    {
        std::cout << "Constructor is called!" <<std::endl;
    }
    Str(int input)
    {
        x = input;
        std::cout << x <<std::endl;
    }
private:
    int x;
};

int main()
{
    Str m_str(3);

    return 0;

}

C++11:代理构造函数

#include<iostream>

class Str
{
public:
    Str():Str(3)
    {
    }

    Str(int input)
    {
        x = input;
    }
    void fun()
    {
        std::cout << x <<std::endl;
    }
private:
    int x;
};

int main()
{
    Str m_str;
    m_str.fun();

    return 0;

}

代理构造函数先调用其他构造函数,再执行函数体内部

上述方式性能不够好,更好的方式:

#include <iostream>
#include <string>
class Str
{
public:

    Str(const std::string& val) : x(val)
    {
        std::cout << "hello~" << x <<std::endl;
    }
    void fun()
    {
        std::cout << x <<std::endl;
    }
private:
    std::string x;
};

int main()
{
    Str m_str("abc");
    m_str.fun();

    return 0;

}

如果类的成员包含引用,必须用上述这种参数化列表的方式来初始化引用对象

元素的初始化顺序与声明顺序有关,与在参数列表的顺序无关

初始化列表初始化优先级要高于类内成员初始化

如果没有自己定义构造函数,编译器会默认构造一个没有参数的构造函数

4.2 一个参数的构造函数

可以使用=进行隐式的类型转换,使用explicit要求不允许隐式类型转换的方式拷贝初始化(在构造函数前写),也可以使用static_cast<...>的方式类型转换

4.3 拷贝构造函数

接收一个当前类的构造函数,可以用 = 调用

参数只能传引用,如果传值会复制,还是会调用拷贝构造,会继续传值、调用拷贝一直循环

通常不希望对传入的值进行修改,会声明const

如果没有显示定义拷贝构造函数,编译器会自动合成一个,对数据成员依次进行拷贝初始化

4.4 移动构造函数(C++11)

复制之后原来的对象不再重要,可以使用移动构造函数,不涉及到内存开辟、拷贝等,速度很快

接收一个当前类的右值引用

有移动调用移动,没有移动调用拷贝

通常声明为不可抛出异常的函数(在函数声明后加noexcept关键字)

右值引用对象用作表达式时是左值,想用右值得加move

81-3 00:00


5 字面值类、成员指针与bind交互