C++并发编程学习日记 2025/4/22
今天继续夯实C++基础内容,复习了面向对象中类和对象的部分,主要是对象成员的内容。
成员函数和成员变量
在类体外定义成员函数时,需要显式声明为inline
。
实例的内存大小取决于实例的成员变量。成员变量在变量名前加一个_
每一个对象有自己的成员变量,共享同一套成员函数。
C++编译时会给每一个成员函数加一个参数class* this
,用以在成员函数中区分不同的实例。
注意如果成员函数只执行只读操作,需要声明为const成员函数。
如果想要通过指针或者引用访问成员变量或者成员函数(必须是public),需要注意指针的类型。例如,想要访问
String
中的int size
,指针的声明和定义应当为int String::* ptr=&String::size;
内存分配
静态成员函数和非静态成员函数均存储在代码区,不占用类实例的内存。使用静态成员函数调用静态变量,编译时编译器不给静态函数提供this指针,只能调用不依赖成员变量的内容(即静态变量)。
静态成员变量在全局数据区。非静态成员变量存放类的实例中,实例的内存大小取决于非静态成员变量。注意静态成员变量必须在类外定义并初始化
特殊的,对于虚函数来说。虚函数通过存放在全局数据区的虚函数表来实现,每个实例会存放一个指向虚函数表的指针。
构造函数和析构函数
通过实现了一个顺序栈来理解构造函数和析构函数。析构函数最重要的作用就是自动释放内存,避免因忘记释放内存造成而导致内存泄露和野指针等问题。代码示例如下:
class sortStack {
public:
sortStack(int size = 10) {
_size = size;
_pstack = new int[10];
_top = -1;
}
~sortStack(){
delete[] _pstack;
_size = 0;
_top = -1;
}
bool full() {
if (_top >= _size - 1) return true;
else return false;
}
bool empty() {
if (_top == -1) return true;
else return false;
}
void push(int value) {
if (full()) resize();
_top++;
_pstack[_top] = value;
}
void pop() {
if (empty()) return;
_top--;
return;
}
int top() {
if (empty()) return;
else return _pstack[_top];
}
private:
int _top;//栈顶
int _size;//栈容量
int* _pstack;//一个数组指针,动态开辟数组
void resize() {
int* _newstack = new int[_size * 2];
for (int i = 0;i < _size;i++) {
_newstack[i] = _pstack[i];
}
delete[] _pstack;
_pstack = _newstack;
_size *= 2;
}
};
拷贝构造
浅拷贝是指直接做内存的拷贝。
浅拷贝面临内存泄漏和野指针的安全隐患,当对象的某些成员变量占用外部资源,比如有一个数组指针的成员变量指向了堆上的一个内存区域,经过浅拷贝之后,拷贝后的对象也有一个成员对象数组指针指向同一块内存区域,在执行析构函数时,会出现野指针的问题。
为了解决这个问题我们可以自定义一个拷贝构造函数,以实现一个深拷贝。深拷贝是将原对象成员变量的__内容__拷贝给新对象。
通过重载赋值运算符来实现拷贝时,需要先释放掉原来占用的内存空间。
下面通过一个代码示例来说明拷贝构造的注意事项:
sortStack::sortStack(const sortStack& src) {
_pstack = new int[src._size];
for (int i = 0;i < src._size;i++) {
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._size;
}
void sortStack::operator=(const sortStack& src) {
delete[] _pstack;//防止自拷贝
_pstack = new int[src._size];
for (int i = 0;i < src._size;i++) {
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._size;
}
或者用另一种方式重载赋值运算符:
class String {
public:
String(const char* str = nullptr) {
if (str != nullptr) {
m_data = new char[strlen(str) + 1];
for (int i = 0;i < strlen(str);i++) {
m_data[i] = str[i];
}
}
else {
m_data = new char[1];
m_data[0] = '\0';
}//如果传进来的字符串是一个空值,最好将成员变量m_data赋值成上面这种字符数组,这样后面定义其他函数就不需要判断m_data是否为空。
}
String(const String& other) {
std::cout << "拷贝构造" << std::endl;
m_data = new char[strlen(other.m_data)+1];
for (int i = 0;i < strlen(m_data);i++) {
m_data[i] = other.m_data[i];
}
}
~String() {
delete[] m_data;
}
String& operator=(const String& other) {
delete[] m_data;
m_data = new char[strlen(other.m_data) + 1];
for (int i = 0;i < strlen(m_data);i++) {
m_data[i] = other.m_data[i];
}
return *this;//返回值为String&时,需要返回一个*this
}//这种写法支持对象的连续赋值。
private:
char* m_data;
};
循环队列
更新队首和队尾时,需要保持循环特征,加一后模一个容量
size
判断是否已满:队尾加一模容量后等于队首即满
判断是否为空:队首等于队尾即空
更新容量:用一个整型量
index
。for
循环从队首开始,到队尾结束,步长是加一模容量,遍历队列拷贝到新的数组中。然后更新队尾和队首,队首为0,队尾是index
成员变量:
int front;
//队首int rear;
//队尾,指向最后一个元素后面一个位置int size;
//容量int* queue;
//动态数组成员函数:
void add(int);
//将int加入队列的队尾。加入后更新队尾。
void pop();
//弹出一个元素,弹出后更新队首。
指向成员变量的指针
主要说一下指向成员变量的指针。
假设在类CGoods
中有一个public的成员变量int shared_data=20
和一个静态成员变量static int data2=40
,我们想要通过指针来访问和修改它们。写法应当如下,注意静态成员和非静态成员之间的区别:
class CGoods {
public:
int shared_data = 20;
static int data2;
};
int CGoods::data2 = 40;
//类及其成员变量的定义
int main()
{
int CGoods::* ptr = &CGoods::shared_data;
int* ptr2 = &CGoods::data2;
std::cout << ptr << " "<<&ptr<<" " << goods1.*ptr << std::endl;
std::cout << ptr2 << " " << &ptr2 << " " << *ptr2<< std::endl;
goods1.*ptr = 30;
*ptr2 = 50;
std::cout << ptr << " " <<&ptr<<" "<< goods1.*ptr << std::endl;
std::cout << ptr2 << " " << &ptr2 << " " << *ptr2 << std::endl;
/*
*输出结果为:
*1 000000D0E5CFF878 20
*00007FF6D34AF048 000000D0E5CFF898 40
*1 000000D0E5CFF878 30
*00007FF6D34AF048 000000D0E5CFF898 50
*/
return 0;
}
注意对于非静态成员变量,我们需要声明它的作用域,这是因为如果不加作用域编译器就会将这步赋值操作理解为:int*=int& CGoods::
,这是不合法的;所以正确的写法应该是int CGoods::*ptr=&CGoods::shared_data;
。而访问的时候也应当说明指针是一个对象的指针。
对于非静态变量,则只需要对数据声明作用域来与其他变量区分就可以了,写法也类似于正常的指针赋值int* ptr=&CGoods::data2;
静态成员和非静态成员之所以有这样的区别是因为静态成员存放在全局数据区,不依赖于对象存在,而非静态成员存放在类的实例中,依赖于对象存在。
指向成员函数的指针
指向成员函数的指针和指向成员变量的指针相似,静态成员函数和非静态成员函数的指针以及指针的调用不相同。这里直接附上写法:
void (CGoods:: * ptr11)()const = &CGoods::show;
void (* ptr22)() = &CGoods::showCommon;
(goods1.*ptr11)();
*ptr22;//注意指向函数的指针的写法:函数返回值类型 (* 指针变量名) (函数参数列表);