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
Comments 4 条评论
博主 BZcloud
博主 BZcloud
(⌒▽⌒)
博主 bzcloud
博主 BZcloud
???