首页 > 编程语言 >Day4 C++(运算符重载,模板与容器)(友元函数,运算符重载,赋值运算符,string字符串类,模板)

Day4 C++(运算符重载,模板与容器)(友元函数,运算符重载,赋值运算符,string字符串类,模板)

时间:2024-09-28 16:54:04浏览次数:3  
标签:友元 函数 int value 运算符 重载 Integer 模板

1. 友元 friend

1.1 概念掌握)

定义:

类实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能通过类的成员函数才能读写。如果数据成员定义为公共的,则又破坏了封装性。但是某些情况下,需要频繁读写类的成员,特别是在对某些成员函数多次调用时,由于参数传递、类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。

友元是一种定义在类外部的普通函数,但他需要在类体内进行说明,为了和该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是他能够访问类中的所有成员。

作用:

在于提高程序的运行效率,但是,他破坏了类的封装性和隐藏性,使得非成员函数能够访问类的私有成员。导致程序维护性变差,因此使用友元要慎用。

友元较为实际的应用是在运算符重载,这种应用可以提高软件系统的灵活性。

分类:

  • 友元函数
  • 友元类
  • 友元成员函数

1.2 友元函数掌握)

友元函数是一种“声明”在类内,实际在类外的普通函数。

#include <iostream>

using namespace std;

class Girl
{
private:
    int age;

public:
    Girl(int age):age(age){}

    int get_age() const
    {
        cout << &age << endl;
        return 18;
    }

    // 1. "声明"友元函数
    friend void access_true_age(Girl&);
};

// 2. 定义友元函数
void access_true_age(Girl& g)
{
    // 突破权限
    cout << &g.age << endl;
    cout << "真实年龄:" << g.age << endl;
    // 修改
    g.age = 18;
    cout << "修改后年龄:" << g.age << endl;
}

int main()
{
    Girl g(45);
    cout << g.get_age() << endl;
    // 通过友元函数访问Girl的年龄
    access_true_age(g);

    return 0;
}

需要注意的是:

  • 由于不属于类的成员函数,因此友元函数没有this指针,访问类的成员只能通过对象。
  • 友元函数在类中的“声明”可以写在类的任何部分,不受权限修饰符的影响。
  • 理论上一个友元函数可以是多个类的友元函数,只需要在各个类中分别“声明”。

1.3 友元熟悉)

当一个类B成为了另一个类A的友元类时,类B可以访问类A的所有成员。

需要注意的是:

  • 友元关系是单向的,不具有交换性。

如果类B是类A的友元类,类A不一定是类B的友元类。

  • 友元关系不具有传递性。

如果类C是类B的友元类,类B是类A的友元类,类C不一定是类A的友元类。

  • 友元关系不能被继承。

#include <iostream>

using namespace std;

class A
{
private:
    string str = "A私有";

    // “声明”友元类
    friend class B;
};

class B
{
public:
    void func(A& a)
    {
//        cout << this->str << endl; 错误:this是B对象不是A对象
        cout << a.str << endl;
        a.str =  "我改了";
        cout << a.str << endl;
    }
};

int main()
{
    A a;
//    cout << a.str << endl; 错误
    B b;
    b.func(a);

    return 0;
}

1.4 友元成员函数熟悉)

在友元类的任何成员函数中都可以访问其他类的成员,但是友元成员函数把友元范围限制在一个成员函数中。

例如,类B的某个成员函数称为了类A的友元成员函数,这样类B的该成员函数就可以访问类A的所有成员了。

#include <iostream>

using namespace std;

// 3. 因为第二步中用到了类A,提前声明类A
class A;

// 2. 编写类B,并真正声明友元成员函数
class B
{
public:
    void func(A&);
};

class A
{
private:
    string str = "A私有";

    // 1. 确定友元的函数格式并“声明”
    friend void B::func(A&);
};

// 4. 类外定义友元成员函数
void B::func(A & a)
{
    //  cout << this->str << endl; 错误:this是B对象不是A对象
    cout << a.str << endl;
    a.str =  "我改了";
    cout << a.str << endl;
}


int main()
{
    A a;
    //    cout << a.str << endl; 错误
    B b;
    b.func(a);

    return 0;
}

2. 运算符重载重点)

2.1 概念 掌握)

如果把运算符看做是一个函数,则运算符也可以像函数一样重载。

C++中预定义的运算符的操作对象只能是基本数据类型。但实际上对于很多用户的自定义类型,也需要类似的运算操作、这时可以在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型,执行特定的操作。

可以被重载的运算符:

算术运算符:+、-、*、/、%、++、--

位操作运算符:&、|、~、^(位异或)、<<(左移)、>>(右移)

逻辑运算符:!、&&、||

比较运算符:<、>、>=、<=、==、!=

赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=

其他运算符:[]、()、->、,、new、delete、new[]、delete[]

不被重载的运算符:

成员运算符“.”、指针运算符“*”、三目运算符“? :”、sizeof、作用域“::”

2.2 友元函数运算符重载重点)

#include <iostream>

using namespace std;

/**
 * @brief The Integer class 整数类
 */
class Integer
{
private:
    int value;

public:
    Integer(int value):value(value){}

    int get_value() const
    {
        return value;
    }

    // 友元函数“声明”
    friend Integer operator +(const Integer&,const Integer&);
    friend Integer operator ++(Integer&); // 前置
    friend Integer operator ++(Integer&,int); // 后置
};

Integer operator +(const Integer& i1,const Integer& i2)
{
    return i1.value + i2.value; // 隐式调用构造函数
}

Integer operator ++(Integer& i)
{
    return ++i.value; // 隐式调用构造函数
}

Integer operator ++(Integer& i,int)
{
    return i.value++; // 隐式调用构造函数
}

int main()
{
    Integer i1(1);
    Integer i2(2);
    Integer i3 = i1 + i2;
    cout << i3.get_value() << endl; // 3
    cout << (i1++).get_value() << endl; // 1
    cout << (++i1).get_value() << endl; // 3

    return 0;
}

2.3 成员函数运算符重载重点)

成员函数运算符重载与友元函数运算符重载的最大区别是:友元函数运算符重载的第一个参数,在成员函数中使用this指针代替,即使用成员函数重载的运算符相比友元函数的参数少一个。

#include <iostream>

using namespace std;

/**
 * @brief The Integer class 整数类
 */
class Integer
{
private:
    int value;

public:
    Integer(int value):value(value){}

    int get_value() const
    {
        return value;
    }

    // 声明成员函数
    Integer operator +(const Integer&); // 双目
    Integer operator ++(); // 前置

    Integer operator ++(int) // 后置
    {
        return this->value++;
    }
};

Integer Integer::operator +(const Integer& i)
{
    return this->value + i.value;
}

Integer Integer::operator ++()
{
    return ++this->value;
}


int main()
{
    Integer i1(1);
    Integer i2(2);
    Integer i3 = i1 + i2;
    cout << i3.get_value() << endl; // 3
    cout << (i1++).get_value() << endl; // 1
    cout << (++i1).get_value() << endl; // 3

    return 0;
}

2.4 赋值运算符(重点)与类型转换运算符重载熟悉)

如果程序员不写赋值运算符重载函数,编译器会为这个类自动添加一个赋值运算符重载函数,且此函数支持链式调用,因此只能使用成员函数运算符重载(不至于友元)。

结合之前内容,处理深浅拷贝时应该同时处理赋值运算符。

#include <iostream>

using namespace std;


class Value
{
public:
    int value = 0;
    // 1. 构造函数
    // 2. 拷贝构造
    // 3. 析构函数
    // 4. 赋值运算符重载
    // 编译器自动添加:
    Value& operator =(const Value& v)
    {
        value = v.value;
        return *this;
    }
};


int main()
{
    Value v; // 构造函数
    Value v2(v); // 拷贝构造
    Value v3 = v2; // 拷贝构造
    v.value = 1;
    v3 = v; // 赋值运算符
    cout << v3.value << endl; // 1

    return 0;
}

类型转换运算符与赋值运算符的符号都是=,因此类型转换运算符重载函数的格式比较特殊,以便于与赋值运算符重载进行区分,同样类型转换运算符重载函数也只支持成员函数运算符重载。

#include <iostream>

using namespace std;


class Value
{
private:
    int value;

public:
    Value(int value):value(value){}

    int get_value() const
    {
        return value;
    }

    // 类型转换运算符重载函数
    operator int()
    {
        return value;
    }
};


int main()
{
    // int → Value
    Value v = 1; // 隐式构造

    // Value → int
    int i = v; // 类型转换运算符重载函数
    cout << i << endl; // 1

    return 0;
}

2.5 注意事项(掌握)

  • 运算符重载限制在C++已有的运算符范围内,不能创建新的运算符。
  • 运算符重载不能改变运算符的优先级、结合性、操作数和语法结构。
  • 运算符重载不能改变基本类型的计算规则,只能更改包含自定义类型的计算规则。
  • 运算符重载实现的功能应该与原运算符相近。
  • 运算符重载函数不支持参数默认值。
  • 通常单目运算符使用成员函数重载,双目运算符使用友元函数重载。

3. string 字符串

#include <iostream>
#include <string.h>

using namespace std;

int main()
{
    string s; // 生成一个空字符串
    cout << "判断字符串是否为空:" <<  s.empty() << endl; // 1
    string s1 = "Thursday"; // 隐式调用构造函数,参数const char*
    string s2("Thursday"); // 显式调用上面的构造函数
    cout << (s1 == s2) << endl; // 1
    string s3 = s1; // 隐式调用拷贝构造
    string s4(s2); // 显式调用拷贝构造
    cout << (s3 == s4) << endl; // 1
    s = s4; // 赋值运算符
    cout << s << endl; // "Thursday"

    // 参数1:const char* 原字符串
    // 参数2:保留几个字符
    string s5("ABCDEFG",2);
    cout << s5 << endl; // AB

    s = "ABCDEFG";
    // 参数1:string 原字符串
    // 参数2:不保留前几个字符
    string s6(s,2);
    cout << s6 << endl; // CDEFG

    // 参数1:字符串长度
    // 参数2:字符内容
    string s7(6,'A');
    cout << s7 << endl; // AAAAAA
    // 交换
    swap(s6,s7);
    cout << s6 << " " << s7 << endl; // AAAAAA CDEFG

    s = s6+s7; // 拼接
    cout << s << endl; // AAAAAACDEFG

    // 向后追加
    s.append("123");
    cout << s << endl; // AAAAAACDEFG123

    // 向后追加单字符
    s.push_back('%'); // AAAAAACDEFG123%

    // 插入
    // 参数1:插入的位置
    // 参数2:插入的内容
    s.insert(1,"222"); // A222AAAAACDEFG123%

    // 参数1:删除的起始位置
    // 参数2:删除的字符数
    s.erase(4,10);
    cout << s << endl; // A222123%

    // 参数1:替换的起始位置
    // 参数2:替换的字符数
    // 参数3:替换的内容
    s.replace(0,3,"******");
    cout << s << endl; // ******2123%

    s.clear(); // 清空
    cout << s.length() << endl; // 0

    char c[20];
    s = "1234567890";
    // 参数1:拷贝的目标
    // 参数2:拷贝的字符数
    // 参数3:拷贝的起始位置
    s.copy(c,3,1);
    cout << c << endl; // 234

    // C → C++ 直接赋值即可
    // char* → string
    char* c1 = "Tom";
    char c2[] = "Jerry";
    string sc1 = c1;
    string sc2 = c2;
    cout << sc1 << "&" << sc2 << endl; // Tom&Jerry

    // C++ → C
    // string → char[]
    s = "abcd";
    char ch[10];
    strcpy(ch,s.c_str()); // c_str()返回值的const char*不稳定
    cout << ch << endl;

    return 0;
}

本章节主要讲解泛型编程,泛型编程(Generic Programming)最初提出时的动机很简单直接:发明一种语言机制,能够帮助实现一个通用的标准容器库。所谓通用的标准容器库,就是要能够做到,比如用一个List类存放所有可能类型的对象这样的事;泛型编程让你编写完全一般化并可重复使用的算法,其效率与针对某特定数据类型而设计的算法相同。

1. (mú) template

1.1 概念

C++重模板可以让类或函数声明一种通用类型,使得函数或类中的某些成员变量或成员变量的参数、返回值在实际上的使用中可以是任何类型。

模板可以让程序员写出与类型无关的代码,是泛型编程的基础。

模板主要分为两种实现方式:

  • 函数模板
  • 类模板

1.2 函数模板

#include <iostream>

using namespace std;

// 函数模板
template <class T>
T add(T a,T b)
{
    return a+b;
}

class Dog
{

};

int main()
{
    // 在使用的过程中T可以任何类型
    cout << add(2,3) << endl;
    cout << add(2.2,2.2) << endl;
    cout << add('0','0') << endl;
    // 这套通用算法可能不支持某些类型
    // 错误出现的原因并非不能传参,而是不能计算
    cout << add("aa","aa") << endl; // const char*
    Dog d1;
    Dog d2;
    add(d1,d2);

    return 0;
}

1.3 类模板

#include <iostream>

using namespace std;

template <typename T>
class Demo
{
private:
    T value;

public:
    Demo(T value):value(value){}

    T get_value() const
    {
        return value;
    }
};

class MobilePhone
{
private: // 私有:被修饰的成员只能在类内访问
    string brand; // 读写
    string model = "16"; // 只读
    int weight; // 只写

public:
    string get_brand() // getter:读函数
    {
        return brand;
    }

    void set_brand(string b) // setter:写函数
    {
        brand = b;
    }

    string get_model()
    {
        return model;
    }

    void set_weight(int w)
    {
        weight = w;
    }
};

int main()
{
    // 类模板在创建对象时要标注T的具体类型,以便于开辟内存
    Demo<int> d1(1);
    cout << sizeof(d1) << " " << d1.get_value() << endl; // 4 1

    Demo<long> d2(1);
    cout << sizeof(d2) << " " << d2.get_value() << endl; // 4 1

    MobilePhone mp1;
    Demo<MobilePhone> d3(mp1);
    cout << sizeof(d3) << endl; // 12

    return 0;
}

// TODO 声明定义分离

标签:友元,函数,int,value,运算符,重载,Integer,模板
From: https://blog.csdn.net/weixin_64032452/article/details/142618106

相关文章

  • C++ Practical-2 day2 运算符重载之时钟类++运算符
    系列文章目录点击直达——文章总目录文章目录系列文章目录C++Practical-2day2运算符重载之时钟类++运算符Overview1.时间类重载后缀`++`运算符来递增时间1.1.解释1.2.注意事项2.如何确保时间递增操作在多线程环境中是线程安全的?关于作者C++Practical-2day......
  • 设计模式之模板方法模式
    模板方法模式模板方法模式是一种行为型设计模式,它定义了一个操作中的算法的框架,并将一些步骤的执行延迟到子类中。通过这种方式,模板方法使得子类可以在不改变算法的结构的情况下,重定义算法中的某些特定步骤。核心组成:抽象类(AbstractClass):这个抽象类包含模板方法本身,同时也可......
  • 运算符
    短路运算publicclassOperator02{publicstaticvoidmain(String[]args){//短路运算//c<4false,不执行后面半句(c++<4),c还是5没有自增intc=5;booleand=(c<4)&&(c++<4);System.out.println(d);Syst......
  • Wincc7.5sp2使用VBA6-全局模板、项目模板和页面模板
    这一篇博客在新浪发表过,那边还在审核,为了避免关闭服务,在这里再次发一遍。那边的博客发表后审核期间,如果想修改是不允许的,审核时间比较长,有点不合理。前面的VBA练习,都是针对具体的项目的具体画面进行编程,在wincc项目还可以全局VBA编程和具体项目VBA编程。我边看技术文档边做练习,......
  • 期刊投稿|Author Agreement模板
    很多期刊要求在投稿的同时,提交AuthorAgreement,但又没给官方模板,AuthorAgreement的作用:AnAuthorAgreementisastatementtocertifythatallauthorshaveseenandapprovedthefinalversionofthemanuscriptbeingsubmitted.Theywarrantthatthearticleisthe......
  • 运算符、分支语句
    位操作符:可以直接操作二进制数位的内容;~是一个单目位操作符,它可以根据一个数字计算另外一个数字,这两个数字所有二进制数位的内容都不同(按位取反),使用的时候这个符号应该写在数字前面双目位操作符:包括按位与(&),按位或(|)以及按位异或(^),他们都可以把两个数字对应二进制数位的内容做计算......
  • C++ 容器赋值运算符
    ▲《C++Primer》P302assignlist<string>names;vector<constchar*>old_c_str{"娃哈哈","孟菲斯","HelloWold!"};names.assign(old_c_str.cbegin(),old_c_str.cend());//这个可以,拷贝构造for(constautos:names){cout......
  • pbootcms模板如何调用当前位置面包屑标签
    在PbootCMS中,如果你想在模板中调用当前位置的面包屑导航(Breadcrumb),可以通过特定的标签来实现。以下是具体的实现方法和示例代码:调用面包屑导航标签参数说明separator=*:分隔符,非必填,默认为 >>。indextext=*:首页文本,非必填,默认为“首页”。示例代码假设你希望在模板中......
  • pbootcms模板导航设置外链时新窗口打开
    在PbootCMS中,如果你想在模板导航中设置外链并在新窗口中打开,可以通过条件判断来实现这一功能。具体步骤如下:实现步骤编写条件判断代码:使用 {pboot:if} 语句来判断外链是否为空。如果外链不为空,则添加 target="_blank" 属性。整合到导航链接中:将条件判断代码嵌......
  • pbootcms网站模板首页如何调用指定栏目的子栏目
    在PbootCMS中,调用指定栏目的子栏目可以通过模板标签来实现。下面是一个详细的示例,展示如何在模板首页调用指定栏目的子栏目。示例代码假设你要调用ID为4的栏目下的子栏目,并且最多显示7个子栏目,可以使用以下模板标签:{pboot:navparent="{sort:tcode}"parent=4num=7}<a......