从C向C++5——友元和string

一.对象特性(续)

1.空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针。

如果用到this指针,需要加以判断保证代码的健壮性。

如果调用的成员函数不访问成员属性,那么空指针可以调用对应的成员函数,如果该函数涉及了成员属性,那么就相当于涉及了

this指针,空指针的话调用涉及成员属性的函数,需要进行this的判断加以保证代码的健壮性。

if(this==NULL)
    return;

2.const修饰成员函数

const 成员变量的用法和普通 const 变量的用法相似,只需要在声明时加上 const 关键字。初始化const成员变量只有一种方法,就是通过构造函数的初始化列表,这点在前面已经提过了。

常函数:

  • 成员函数后加const后我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数

需要强调的是,必须在成员函数的声明和定义处同时加上const关键字。

最后再来区分一下 const 的位置:

  • 函数开头的 const 用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改,例如const char * getname()
  • 函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值,例如char * getname() const

二.友元

​ 生活中你的家有客厅(Public),有你的卧室(Private)。客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去。在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。

::: tip 总结

友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键字为 friend

:::

友元函数的三种实现方式:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

1.全局函数作友元

#include<iostream>
using namespace std;
#include<string>

class Building {
    friend void func(Building* p);
public: 
    string bighall;
private:
    string bedroom;
public:
    Building() {
        bighall = "大厅";
        bedroom = "卧室";
    }
};


//全局函数
void func(Building *p) {
    cout << "好闺蜜正在访问" << p->bighall << endl;
    cout << "好闺蜜正在访问" << p->bedroom << endl;
}

void test01() {
    Building *p1 = new Building();
    func(p1);
}
int main() {
    test01();

    return 0;
}

2.友元类

#include<iostream>
using namespace std;
#include<string>

class Building {
    friend class Goodgay; //声明友元类
public:
    string bighall;
private:
    string bedroom;
public:
    Building();
};

class Goodgay {
public:
   Goodgay();
   void visit(Building *p);
   Building * building;
};



Goodgay::Goodgay() {
   building = new Building;
}

Building::Building() {
    bighall = "大厅";
    bedroom = "卧室";
}

void Goodgay::visit(Building* p) {
    cout << "好闺蜜正在访问" << p->bighall << endl;
    cout<< "好闺蜜正在访问" << p->bedroom << endl;
}

void test01() {
    Building *p1 = new Building();
    Goodgay gf1;
    gf1.visit(p1);
}
int main() {
    test01();

    return 0;
}

3.成员函数做友元

#include<iostream>

using namespace std;

#include<string>

class Goodgay;

class Building;

class Building {
    friend void Goodgay::visit();

public:
    string bighall;
private:
    string bedroom;
public:
    Building();

};

class Goodgay {
public:
    Goodgay();

    void visit();

    Building *building;
};


Goodgay::Goodgay() {
    building = new Building;
}

Building::Building() {
    bighall = "大厅";
    bedroom = "卧室";
}

void Goodgay::visit() {
    cout << "好闺蜜正在访问" << building->bighall << endl;
    cout << "好闺蜜正在访问" << building->bedroom << endl;
}

void test01() {
    Building *p = new Building;
    Goodgay gf1;
    gf1.visit();
}

int main() {
    test01();

    return 0;
}

注意:成员函数作友元时,必须把先定义的类的成员函数作为后定义的类的友元,如果二者调换顺序会报错。

三.类的补充理解

1.作用域

​ 类其实也是一种作用域,每个类都会定义它自己的作用域。在类的作用域之外,普通的成员只能通过对象(可以是对象本身,也可以是对象指针或对象引用)来访问,静态成员既可以通过对象访问,又可以通过类访问,而typedef定义的类型只能通过类来访问。

2.classstruct的区别

​ C++中保留了C语言的 struct 关键字,并且加以扩充。在C语言中,struct 只能包含成员变量,不能包含成员函数。而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。

C++中的 struct class 基本是通用的,唯有几个细节不同:

  • 使用 class 时,类中的成员默认都是 private 属性的;而使用struct时,结构体中的成员默认都是 public 属性的。
  • class 继承默认是 private 继承,而 struct 继承默认是 public 继承.。
  • class 可以使用模板,而 struct 不能。

四.string类型

1.string定义

string 是 C++ 中常用的一个类,它非常重要,我们有必要在此单独讲解一下。

使用 string 类需要包含头文件<string>,下面的例子介绍了几种定义 string 变量(对象)的方法:

#include <iostream>
#include <string>
using namespace std;

int main(){
    string s1;
    string s2 = "c plus plus";
    string s3 = s2;
    string s4 (5, 's');
    return 0;
}

变量 s1 只是定义但没有初始化,编译器会将默认值赋给 s1,默认值是"",也即空字符串。

变量 s2 在定义的同时被初始化为"c plus plus"。与C风格的字符串不同,string 的结尾没有结束标志'\0'

变量 s3 在定义的时候直接用 s2 进行初始化,因此s3的内容也是"c plus plus"

变量 s4 被初始化为由 5 个's'字符组成的字符串,也就是"sssss"

从上面的代码可以看出,string 变量可以直接通过赋值操作符=进行赋值。string 变量也可以用C风格的字符串进行赋值,例如,s2 是用一个字符串常量进行初始化的,而 s3 则是通过 s2 变量进行初始化的。

与C风格的字符串不同,当我们需要知道字符串长度时,可以调用 string 类提供的 length() 函数。

​ 虽然 C++ 提供了 string 类来替代C语言中的字符串,但是在实际编程中,有时候必须要使用C风格的字符串(例如打开文件时的路径),为此,string 类为我们提供了一个转换函数 c_str(),该函数能够将 string 字符串转换为C风格的字符串,并返回该字符串的const指针(const char*)。请看下面的代码:

string path = "D:\\demo.txt";
FILE *fp = fopen(path.c_str(), "rt");
  • string 字符串也可以像C风格的字符串一样按照下标来访问其中的每一个字符。string 字符串的起始下标仍是从 0 开始。

2.字符串拼接

有了 string 类,我们可以使用++=运算符来直接拼接字符串,非常方便,再也不需要使用C语言中的 strcat()、strcpy()、malloc() 等函数来拼接字符串了,再也不用担心空间不够会溢出了。

+来拼接字符串时,运算符的两边可以都是 string 字符串,也可以是一个 string 字符串和一个C风格的字符串,还可以是一个 string 字符串和一个字符数组,或者是一个 string 字符串和一个单独的字符。

#include<iostream>
using namespace std;
#include<string>


int main() {
    string s1 = "hello ";
    string s2 = "word! ";
    char s3[] = "friend ";
    cout << s1 + s2 << endl;
    cout << s1 + s3 << endl;
    cout << s1 + s2 + s3 << endl;


    return 0;
}

3.插入字符串

insert() 函数可以在 string 字符串中指定的位置插入另一个字符串,它的一种原型为:

string& insert (size_t pos, const string& str);

pos 表示要插入的位置,也就是下标;str 表示要插入的字符串,它可以是 string 字符串,也可以是C风格的字符串。

int main(){
    string s1, s2, s3;
    s1 = s2 = "1234567890";
    s3 = "aaa";
    s1.insert(5, s3);
    cout<< s1 <<endl;
    s2.insert(5, "bbb");
    cout<< s2 <<endl;
    return 0;
}

4.删除字符串

erase() 函数可以删除 string 中的一个子字符串。它的一种原型为:

string& erase (size_t pos = 0, size_t len = npos);

pos 表示要删除的子字符串的起始下标,len 表示要删除子字符串的长度。如果不指明len的话,那么直接删除从pos到字符串结束处的所有字符。

int main(){
    string s1, s2, s3;
    s1 = s2 = s3 = "1234567890";
    s2.erase(5);
    s3.erase(5, 3);
    cout<< s1 <<endl;
    cout<< s2 <<endl;
    cout<< s3 <<endl;
    return 0;
}

5.提取子字符串

substr() 函数用于从 string 字符串中提取子字符串,它的原型为:

string substr (size_t pos = 0, size_t len = npos) const;

pos 为要提取的子字符串的起始下标,len 为要提取的子字符串的长度。

6.字符串查找

6.1find函数

find() 函数用于在 string 字符串中查找子字符串出现的位置,它其中的一种原型为:

size_t find (const string& str, size_t pos = 0) const;

第一个参数为待查找的子字符串,它可以是 string 字符串,也可以是C风格的字符串。第二个参数为开始查找的位置(下标);如果不指明,则从第0个字符开始查找。

find() 函数最终返回的是子字符串第一次出现在字符串中的起始下标。如果没有查找到子字符串,那么会返回 string::npos,它是 string 类内部定义的一个静态常成员,用来表示 size_t 类型所能存储的最大值。

6.2rfind函数

rfind()find()很类似,同样是在字符串中查找子字符串,不同的是 find() 函数从第二个参数开始往后查找,而rfind()函数则最多查找到第二个参数处,如果到了第二个参数所指定的下标还没有找到子字符串,则返回 string::npos

6.3find_first_of() 函数

find_first_of() 函数用于查找子字符串和字符串共同具有的字符在字符串中首次出现的位置。

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s1 = "first second second third";
    string s2 = "asecond";
    int index = s1.find_first_of(s2);
    if(index < s1.length())
        cout<<"Found at index : "<< index <<endl;
    else
        cout<<"Not found"<<endl;
    return 0;
}

运行结果:
Found at index : 3

本例中 s1 s2共同具有的字符是‘s’,该字符在 s1 中首次出现的下标是3,故查找结果返回3。

五.对象知识点总结

类的成员有成员变量和成员函数两种。

成员函数之间可以互相调用,成员函数内部可以访问成员变量。

私有成员只能在类的成员函数内部访问。默认情况下,class 类的成员是私有的,struct 类的成员是公有的。

可以用“对象名.成员名”、“引用名.成员名”、“对象指针->成员名”的方法访问对象的成员变量或调用成员函数。成员函数被调用时,可以用上述三种方法指定函数是作用在哪个对象上的。

对象所占用的存储空间的大小等于各成员变量所占用的存储空间的大小之和(如果不考虑成员变量对齐问题的话)。

定义类时,如果一个构造函数都不写,则编译器自动生成默认(无参)构造函数和复制构造函数。如果编写了构造函数,则编译器不自动生成默认构造函数。一个类不一定会有默认构造函数,但一定会有复制构造函数。

任何生成对象的语句都要说明对象是用哪个构造函数初始化的。即便定义对象数组,也要对数组中的每个元素如何初始化进行说明。如果不说明,则编译器认为对象是用默认构造函数或参数全部可以省略的构造函数初始化。在这种情况下,如果类没有默认构造函数或参数全部可以省略的构造函数,则编译出错。

对象在消亡时会调用析构函数。

每个对象有各自的一份普通成员变量,但是静态成员变量只有一份,被所有对象所共享。静态成员函数不具体作用于某个对象。即便对象不存在,也可以访问类的静态成员。静态成员函数内部不能访问非静态成员变量,也不能调用非静态成员函数。

常量对象上面不能执行非常量成员函数,只能执行常量成员函数。

包含成员对象的类叫封闭类。任何能够生成封闭类对象的语句,都要说明对象中包含的成员对象是如何初始化的。如果不说明,则编译器认为成员对象是用默认构造函数或参数全部可以省略的构造函数初始化。

在封闭类的构造函数的初始化列表中可以说明成员对象如何初始化。封闭类对象生成时,先执行成员对象的构造函数,再执行自身的构造函数;封闭类对象消亡时,先执行自身的析构函数,再执行成员对象的析构函数。

const 成员和引用成员必须在构造函数的初始化列表中初始化,此后值不可修改。

友元分为友元函数和友元类。友元关系不能传递。

成员函数中出现的 this 指针,就是指向成员函数所作用的对象的指针。因此,静态成员函数内部不能出现 this 指针。成员函数实际上的参数个数比表面上看到的多一个,多出来的参数就是 this 指针。

相关推荐

  1. CC++5——string

    2024-01-31 13:36:04       51 阅读
  2. C++】函数

    2024-01-31 13:36:04       62 阅读
  3. C++

    2024-01-31 13:36:04       43 阅读
  4. c++ 函数

    2024-01-31 13:36:04       32 阅读
  5. C++friend

    2024-01-31 13:36:04       32 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-31 13:36:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-31 13:36:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-01-31 13:36:04       82 阅读
  4. Python语言-面向对象

    2024-01-31 13:36:04       91 阅读

热门阅读

  1. 【python】数据结构

    2024-01-31 13:36:04       51 阅读
  2. Android 平台代码、版本与API级别对应关系

    2024-01-31 13:36:04       74 阅读
  3. leetcode-完全二叉树的节点个数

    2024-01-31 13:36:04       65 阅读
  4. RK3399 去掉HDMI音频

    2024-01-31 13:36:04       65 阅读
  5. vue中父组件直接调用子组件方法(通过ref)

    2024-01-31 13:36:04       61 阅读
  6. Mybatis

    2024-01-31 13:36:04       55 阅读