C++回顾 Day5

自实现string完整版

my_string.h

cpp 复制代码
using namespace std;
class mystr{
	public:
		//mystr();
		mystr(const char * new_str = nullptr);
		mystr(const mystr & another);
		char * c_str();
		~mystr();
		mystr& operator=(const mystr & another);
		mystr operator+(const mystr & another);
		bool operator==(const mystr & another);
		bool operator<(const mystr & another);
		bool operator>(const mystr & another);
		char &operator[](int index);
		char at(int index);
	private:
		char * str;
		int len;
};

my_string.cpp

cpp 复制代码
#include "my_string.h"
#include <string.h>
using namespace std;

/*
mystr::mystr()
{
	str = new char[1];
	*str = '\0';
}
*/

mystr::mystr(const char * new_str)
{
	if(new_str == nullptr)
	{
		len = 0;
		str = new char[len + 1];
		*str = '\0';
	}
	else
	{
		len = strlen(new_str);
		str = new char[len + 1]; 
		strcpy(str,new_str);
	}
}

mystr::mystr(const mystr & another)
{
	//深拷贝
	this->len = strlen(another.str);
	this->str = new char[this->len + 1];
	strcpy(this->str,another.str);
}

char * mystr::c_str()
{
	return str;
}

mystr::~mystr()
{
	delete []str;
} 

mystr& mystr::operator=(const mystr & another)
{
	//深赋值
	if(this == &another)
	{
		return *this;
	}
	else
	{
		delete []this->str;
		this->len = strlen(another.str);
		this->str = new char[this->len + 1];
		strcpy(this->str,another.str);
		return *this; 
	}
	
}
mystr mystr::operator+(const mystr & another)
{
	mystr new_str;
	delete []new_str.str;
	new_str.len = strlen(this->str) + strlen(another.str);
	new_str.str = new char[new_str.len + 1];
	strcpy(new_str.str,this->str);
	strcat(new_str.str,another.str);
	return new_str;
}
bool mystr::operator==(const mystr & another)
{
	if(strcmp(this->str,another.str) == 0)
		return true;
	else
		return false;
}
bool mystr::operator<(const mystr & another)
{
	if(strcmp(this->str,another.str) < 0)
		return true;
	else
		return false;
}
bool mystr::operator>(const mystr & another)
{
	if(strcmp(this->str,another.str) > 0)
		return true;
	else
		return false;
}
char & mystr::operator[](int index)
{
	return this->str[index];
}
char mystr::at(int index)
{
	return this->str[index];
}

main.cpp

cpp 复制代码
#include <iostream> 
#include "my_string.h"
using namespace std;

int main()
{
	
	mystr str1;
	mystr str2("abcdefg");
	mystr str3(str2);
	mystr str4("abcdeffg");
	
	cout << "str1:" << str1.c_str() << endl;
	cout << "str2:" << str2.c_str() << endl;
	cout << "str3:" << str3.c_str() << endl;
	cout << "str4:" << str4.c_str() << endl;
	
	if(str2 < str4)
	{
		cout << "str2 < str4" << endl;
	}
	else if(str2 == str4)
	{
		cout << "str2 == str4" << endl;
	}
	else
	{
		cout << "str2 > str4" << endl;
	}
	
	cout << "str2[2] = " << str2[2] << endl;
	cout << "str4[4] = " << str4[4] << endl;
	
	return 0;
}

注意:

  1. 在写默认构造参数的时候一定要带上无参的那部分,不然在构造对象数组的时候就会有麻烦;

  2. 在有新对象并且默认构造参数有new的步骤就要先delete

  3. 如果函数会产生一个新对象就不能用引用作为返回类型,因为新对象在栈上,我们不能返回一个栈上的引用

  4. 如果只是在原本的对象上进行修改,一定要加上引用,这样才能反馈回原对象上

  5. 对于非拷贝构造函数的其他进行赋值的函数(重载=或者重载+等符号),一定要先delete作为最终容器的对象的内存,不然原来指向的那块内存会出现double delete的问题

对于成员函数,不单独为每个对象中的成员函数都分配独立空间而是公共空间,在调用函数时实际上还传入了该对象的指针------this

const关键字

在修饰成员变量的时候,这个变量的初始化只能在构造函数的参数列表里面

要修饰函数本身,const要放在声明之后,实现体之前 e.g. void myfunc()const{}

const修饰的函数和对象承诺:可以保证对象内的数据成员不会被修改;但只能访问const类的成员函数

const修饰函数本身也能构成重载

const修饰函数,是从函数的角度不能修改数据成员,只能调用const成员函数;

const修饰对象,是从对象的角度不能修改数据成员,只能调用const成员函数;

static关键字

用来修饰类内的数据成员时会使其作为共享数据,其内存与成员函数一样的存储规则,可以由簇类对象共同访问

static类成员在类声明的时候就已经开辟了空间

static类成员必须类内定义,类外初始化

static是属于类的,也是属于对象的,但终归是属于类的

在类还未实例化的时候就可以通过类名去访问static类数据成员 cout << A::share << endl;

static修饰的成员函数用来管理成员变量,其是属于类的,也是属于对象的,但终归是属于类的

static修饰的成员函数也只能访问static类的数据成员及static类的成员函数(与const不同,const多出一个可以访问非const成员变量,但是static不可以)

static const成员要就地初始化 e.g. static const int a = 8;

指向类成员变量的指针

bash 复制代码
变量类型 类名:: *指针名 = &类名::变量名
cpp 复制代码
#include <iostream>
#include <string.h>
using namespace std;

class stu
{
	public:
		stu(int perid,string pername):id(perid),name(pername){}
		~stu(){}
		
		int id;
		string name;
};

int main()
{
	
	stu stu1(07,"bob");
	string stu::*p = &stu::name;
	cout << stu1.*p << endl;
	return 0;
}

q:string stu::*p = &stu::name;解释一下

a:假设我们有一个名为stu的类,这个类中有一个成员变量name。这行代码的目的是创建一个指向stu类成员name的指针。

让我们分解这行代码:

  • string stu::*:这部分声明了一个指针,这个指针不是指向普通的变量或对象,而是指向stu类的某个成员。由于成员name的类型是string(假设stringstd::string的简写,即C++标准库中的字符串类型),因此这个指针的类型是string stu::*,意味着它指向stu类的一个string类型的成员。

  • p:这是指针变量的名称。

  • = &stu::name;:这部分将指针p初始化为指向stu类的name成员。&stu::name是获取成员name的地址的方式,但这里的地址不是普通的内存地址,而是成员在类中的相对地址或者说是成员的偏移量。这是因为类的成员在内存中的实际位置取决于对象的起始地址和成员在类中的定义顺序。

综上所述,string stu::*p = &stu::name;这行代码创建了一个名为p的指针,它指向stu类的name成员。这样的指针允许你在拥有stu类对象的情况下,通过指针访问或修改该对象的name成员,即使你不知道对象的实际类型(在支持多态性的情况下)或者你想以一种通用的方式处理多个对象的相同成员。

指向类成员函数的指针

bash 复制代码
函数返回类型 类名:: *函数指针名(参数列表) = &类名::成员函数名
cpp 复制代码
#include <iostream>
#include <string.h>
using namespace std;

class stu
{
	public:
		stu(int perid,string pername):id(perid),name(pername){}
		~stu(){}
		void print()
		{
			cout << id << ' ' << name << endl;
		}
		
		int id;
		string name;
};

int main()
{
	stu stu1(7,"bob");
	string stu::*p = &stu::name;
	void (stu::*pf)()= &stu::print;
	(stu1.*pf)();
	return 0;
}

这里也是一样的,右边是对stu作用域下的函数print进行取地址,由于print是void类型且在stu作用域下,左边就要用对应类型void stu::*进行接收;

此函数指针只是对于类的偏移量,要调用先进行实例化

又因为()优先级太高,会和print优先结合,故此前面需要加()

q:指向一个类中的成员函数的指针是这个样子void (stu:: * pf)()= &stu::print;,那指向一个类中的静态成员函数的指针是不是void (stu:: * pf)()= stu::print;

a:指向类中静态成员函数的指针的语法与指向普通成员函数的指针有所不同。具体来说,静态成员函数不属于类的任何特定对象实例,因此指向静态成员函数的指针的类型不需要包含类对象指针。

假设有一个类 stu 和一个静态成员函数 print,定义如下:

cpp 复制代码
class stu {
public:
    static void print() {
        // 静态成员函数实现
    }
};

指向静态成员函数的指针应该定义为一个函数指针,其类型与静态成员函数匹配,而不是像指向普通成员函数的指针那样需要类对象指针。指向 stu 类中静态成员函数 print 的指针定义如下:

cpp 复制代码
cpp复制代码

void (*spf)() = &stu::print;

这里 spf 是一个指向静态成员函数 print 的指针。注意,这里使用的是普通的函数指针语法,而不是指向成员函数的指针语法。这是因为静态成员函数不依赖于任何类的实例

总结一下:

  • 指向类中普通成员函数的指针:返回类型 (类名::* 指针名)(参数列表) = &类名::成员函数名;

  • 指向类中静态成员函数的指针:返回类型 (* 指针名)(参数列表) = &类名::静态成员函数名;

借用指向类中静态成员函数的指针来调用对应函数可以(*pf)()也可以pf()

当创建对象时使用动态内存管理

cpp 复制代码
#include <iostream>
#include <string.h>
using namespace std;

class stu
{
	public:
		stu(int perid,string pername):id(perid),name(pername){}
		~stu(){}
		void print()
		{
			cout << id << ' ' << name << endl;
		}
		
		int id;
		string name;
};

int main()
{
	string stu::*p = &stu::name;
	void (stu::*pf)()= &stu::print;
	stu *stu2 = new stu(18,"dick");
	cout << stu2->*p << endl;
	(stu2->*pf)();
	return 0;
}

由于使用的是new,此时的stu2是一个指针,故之前的.* 运算符变成了->*运算符

注意:非静态成员函数想要调用必须实例化,因为其可能改变成员变量的值,未实例化连成员变量都没有,是不对的

对指向成员函数的指针的应用

cpp 复制代码
#include <iostream>
using namespace std;

class func
{
	public:
		func()
		{
			fun_array[0] = &func::print_f;
			fun_array[1] = &func::print_g;
			fun_array[2] = &func::print_i;
			fun_array[3] = &func::print_j;
		}
		void usefun(int index)
		{
			(this->*fun_array[index])();
		}
	private:
		void print_f()
		{
			cout << "void print_f()" << endl;
		}
		void print_g()
		{
			cout << "void print_g()" << endl;
		}
		void print_i()
		{
			cout << "void print_i()" << endl;
		}
		void print_j()
		{
			cout << "void print_j()" << endl;
		}
		void (func::*fun_array[4])() = {nullptr};
};



int main()
{
	func test;
	test.usefun(2);
}

对于取成员函数的地址,接收和取地址时一定要带上类作用域

函数指针数组固定定义格式:返回类型 (类名::* 数组名[数组大小])() = {nullptr}

对于存放函数指针的值来说,解引用只能变为函数名,想要调用就要*函数指针()

对于使用了类作用域而指向了类内的成员(成员函数或者成员变量),只能使用-> * 和 .*使用

又因为成员函数指针只是对于类的偏移量,要调用先进行实例化,所以必须带this

q:void* * fun_array = new void *[4]{nullptr}; 这个的意思不是说要new一个数组,这个数组里面存储的数据类型都是void *吗,要接收一个void * 类型的数组的指针才是void * * ,然后再void print_f(){};fun_array[0] =&print_f;这不是在取void函数的地址也就是void *存到存放void *类型的数组里面吗?

a:

  1. 关于 void** fun_array = new void*[4]{nullptr};

    这行代码确实是在动态分配一个包含4个元素的数组,数组的类型是 void* 的指针(即每个元素都是一个指向 void 的指针)。而整个数组由一个指向 void* 的指针 fun_array 来管理。这里的 void** 表示的是一个指向 void* 的指针,也就是一个指针的指针,它指向的是一个 void* 类型的数组。

  2. 关于 void print_f(){};fun_array[0] = &print_f;

    • void print_f(){}; 定义了一个返回类型为 void 的函数 print_f,它不接受任何参数。

    • fun_array[0] = &print_f; 这行代码试图将函数 print_f 的地址赋给 fun_array[0]。但是,这里有一个类型不匹配的问题:&print_f 是一个指向 void()(即返回 void 且不接受参数的函数)的指针,而 fun_array[0] 是一个 void* 类型的指针。在 C++ 中,函数指针和 void* 指针是不兼容的,不能直接赋值。

    如果您想要存储函数指针,您需要确保函数指针的类型与数组元素的类型相匹配。由于 print_f 是一个返回 void 的函数,其指针类型应该是 void (*)()(一个指向返回 void 且不接受参数的函数的指针)。

q:不是说函数名就是地址吗?为啥不能fun_array[0] = func::print_f;?

a:在 C++ 中,确实有一个常见的说法"函数名就是地址",这指的是函数名在大多数情况下可以用作指向该函数的指针。然而,这个说法有几个重要的限制和上下文需要注意:

  1. 静态成员函数与非静态成员函数:对于静态成员函数,函数名确实可以直接用作函数指针。但是,对于非静态成员函数,情况就不同了。非静态成员函数需要一个类的实例来调用,因此它们不能简单地赋值给一个普通的函数指针。相反,它们需要特殊的成员函数指针类型。

  2. 成员函数指针的语法:在 C++ 中,成员函数指针的声明和使用比普通函数指针更复杂。成员函数指针不仅需要指向函数,还需要知道它是哪个类的成员函数。

友元

函数可以做友元,称为友元函数

当友元函数是是一个全局函数时

cpp 复制代码
#include <iostream>
using namespace std;


class A
{
	public:
		A(int a = 5,int b = 10):a(a),b(b){}
		~A(){}
		friend void show(A &my_A);
	private:
		int a;
		int b;
};

void show(A &my_A)
{
	cout << my_A.a << ' ' << my_A.b << endl;
}

int main()
{
	A myA1;
	A myA2(10,20);
	show(myA1);
	show(myA2);
}

友元关键字friend最好写到public里面

当友元函数在其他类中充当成员函数时

cpp 复制代码
#include <iostream>
using namespace std;

class A;	//进行先前声明(这种声明只能让编译器理解A类型的引用和指针)

class B
{
	public:
		void show(A &my_A);		//函数实现体里面出现了A的成员,那就要写在A完整定义之后
};


class A
{
	public:
		A(int a = 5,int b = 10):a(a),b(b){}
		~A(){}
		friend void B::show(A &my_A);	//谁的作用域在其他类里面出现了谁就要先完整定义
	private:
		int a;
		int b;
};

void B::show(A &my_A)
{
	cout << my_A.a << ' ' << my_A.b << endl;
}


int main()
{
	A myA1;
	A myA2(10,20);
	B my_B; 
	my_B.show(myA1);
	my_B.show(myA2);
}

友元类

cpp 复制代码
#include <iostream>
#include <string.h>
using namespace std;


class son
{
	public:
		son(int id = 0,char nannv = 'm',string name = "bob"):id(id),nannv(nannv),name(name){}
		~son(){}
		friend class father;
	private:
		int id;
		char nannv;
		string name;
};

class father
{
	public:
		void getid(son &myson)
		{
			cout << myson.id << endl;
		}
		void getnannv(son &myson)
		{
			cout << myson.nannv << endl;
		}
		void getname(son &myson)
		{
			cout << myson.name << endl;
		}
};

int main()
{
	son a1;
	son a2(7,'w',"dick");
	father b;
	b.getid(a1);
	b.getnannv(a1);
	b.getname(a1);
	b.getid(a2);
	b.getnannv(a2);
	b.getname(a2);
}

注意:

  1. 友元关系不能被继承

  2. 友元关系是单向的,a是b的友元,a可以访问b的private类成员,但是b不可以访问a的private类成员

  3. 友元关系不具有传递性,b是a的友元,c是b的友元,c不一定是a的友元

对于于是运算符的重载函数,写成友元函数和成员函数在运行时有本质区别

cpp 复制代码
#include <iostream>
using namespace std;

class sett
{
	public:
		sett(int x = 0,int y = 0,int z = 0):x(x),y(y),z(z){}
		//sett operator+(const sett& another);
		friend sett operator+(const sett &a,const sett& b);
		void show();
		
	private:
		int x;
		int y;
		int z;
};

//sett sett::operator+(const sett& another)//成员函数自带this指针所以只需一个额外参数
//{
//	sett t;
//	t.x = this->x + another.x;
//	t.y = this->y + another.y;
//	t.z = this->z + another.z;
//	return t;
//}

sett operator+(const sett &a,const sett& b)//友元函数要传两个
{
	sett t;
	t.x = a.x + b.x;
	t.y = a.y + b.y;
	t.z = a.z + b.z;
	return t;
}

void sett::show()
{
	cout << x << ' ' << y << ' ' << z << endl;
}

int main()
{
	sett a(1,2,3);
	sett b(2,3,4);
	sett c = a + b;//如果是友元函数,就是sett c = operator+(a,b);
    				//如果是成员函数,就是sett c = a.operator+(b);
	a.show();
	b.show();
	c.show();
	return 0;
}

对于+加法运算符来说,(a + b) = c是不可以的,但是如果是上面的写法就是可以的,不管是sett operator+(const sett &a,const sett& b)还是sett operator+(const sett& another)都是在栈上创建一个新对象,(a + b) = c相当于在栈上创建了一个sett类的对象,使其等于a+b的结果,然后又将c付给了这个新对象,紧接着这个栈就消失了,这个新对象也随之消失,为了使得对应让(a + b) = c也不合法,便将重载完的函数返回类型加上const变为const sett operator+(const sett &a,const sett& b)

但对于=赋值运算符而言,(a = b) = c是可以的,我们sett operator=(const sett& another)的返回类型便不加const

所以运算符的重载不能改变语义

同理对于+=运算符而言,a+=b+=c是可以的,同样的sett operator+=(const sett& another);或者sett operator+=(const sett& a ,const sett& b);都是可以满足的,但是对于(a+=b)+=c,基本数据类型是可以的,是先a += b,再a += c,但是对于我们的类来说,这是不可以的,因为a += b,返回的新的a在栈上,马上就会消失,再执行a += c时a已经是之前的a了,所以要使用&,使得a的变化被接收到,改为sett &operator+=(const sett& another);或者sett& operator+=(const sett& a ,const sett& b);

更深层次的理解:

如果 operator+ 返回的是非 const 对象,那么表达式 (a + b) 返回的临时对象是可修改的,因此 (a + b) = c 将被视为合法的赋值操作。但这并不符合内置类型(C++ 语言本身提供的基本数据类型)的语义,因为内置类型的加法运算通常返回一个右值(不允许对其赋值)。为防止这种情况,我们通常将 operator+ 的返回类型声明为 const,这样返回的临时对象就变成 const,(a + b) = c 就会因为尝试修改一个 const 对象而编译错误,从而符合预期的语义。

相关推荐
纪元A梦20 分钟前
华为OD机试真题——荒岛求生(2025A卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
java·c语言·javascript·c++·python·华为od·go
夏子曦1 小时前
C#——NET Core 中实现汉字转拼音
开发语言·c#
爱吃涮毛肚的肥肥(暂时吃不了版)1 小时前
仿腾讯会议——创建房间&加入房间
c++·qt·面试·职场和发展·腾讯会议
꧁坚持很酷꧂2 小时前
Qt天气预报系统绘制温度曲线
开发语言·qt
Kidddddult2 小时前
力扣刷题Day 37:LRU 缓存(146)
算法·leetcode·力扣
电商数据girl2 小时前
【Python爬虫电商数据采集+数据分析】采集电商平台数据信息,并做可视化演示
java·开发语言·数据库·爬虫·python·数据分析
海尔辛2 小时前
学习黑客Bash 脚本
开发语言·学习·bash
生信碱移3 小时前
TCGA数据库临床亚型可用!贝叶斯聚类+特征网络分析,这篇 NC 提供的方法可以快速用起来了!
人工智能·python·算法·数据挖掘·数据分析
小白学大数据3 小时前
分布式爬虫去重:Python + Redis实现高效URL去重
开发语言·分布式·爬虫·python
可可乐不加冰3 小时前
QT生成保存 Excel 文件的默认路径,导出的文件后缀自动加(1)(2)等等
开发语言·qt