首页 > 编程语言 >从C向C++5——友元和string

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

时间:2024-01-27 19:31:43浏览次数:27  
标签:友元 const string 成员 C++ 字符串 构造函数 函数

一.对象特性(续)

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 指针。

标签:友元,const,string,成员,C++,字符串,构造函数,函数
From: https://blog.51cto.com/u_16150223/9444409

相关文章

  • c++实现一门计算机语言到手撸虚拟机实战200节
    1对于编程语言实现原理提供了实战。2学习之后对于JAVA,PHP,PY等语言的实现原理提供了经验平移参考3对JAVA等语言的虚拟机实现原理提供了实战参考。4加深对编程语言的驾驭和深度认知。5虚拟机是计算机系统中非常重要的组成部分,理解了虚拟机的原理和实现方式,从而更好地理解计算......
  • 如果在循环中不改变vector的大小,C++编译器是否会将.size()优化为常数?
      在C++中,可以使用以下代码计算vector<int>中所有元素的和:vector<int>v={1,3,7,9};sums=0;for(inti=0;i<v.size();i++){sums+=v[i];}  这是一段很普通的代码,问题在于:在这段代码中,v.size()会在循环开始前仅计算一次?还是会在每次循环中都计算一次......
  • C转C++速成浅入浅出系列——STL之bitset
    本系列为应付考研复试用,知识浅入浅出,很多地方不深究细节原理;如有谬误,欢迎大家指出。bitset【bitset:位集,比特集】理解为比特集。特点是①只能存入0与1②小端存储(可参阅计算机组成原理知识,表现为按b[i]增序输出时会倒序输出)需提供头文件#include<bitset> 创建注:①存储时......
  • c++重载
    函数或运算符重载是指在同一作用域内定义多个具有相同名称但参数类型或数量不同的函数或运算符。重载允许使用相同的名称执行不同的操作,具体的操作根据传递给函数或运算符的参数类型或数量而定。(和Java重载一样直接和Java重载联系到一起)大致分为两类函数和运算符的重载函数重载:......
  • 【C++入门到精通】C++入门 —— list (STL)
    @TOC前言文章绑定了VS平台下std::list的源码,大家可以下载了解一下......
  • 在ubuntu22上使用C++20
    Linux系统ubuntu22.04安装最新版的gcc13.1.0编译器,支持c++20、23_gcc-13.1.0.tar.gz下载-CSDN博客ubantu20安装多个版本的gcc/gc++编译器_ubuntu安装多个gcc-CSDN博客5步在Ubuntu22上使用C++201.安装build-essentialsudoaptinstallbuild-essential安装完检查/us......
  • C++教程——初识c++(循环,判断,跳转语句)
    在程序设计中,循环语句的使用十分重要,不同的需求需要用到不同的循环语句,对各种循环语句的熟练使用是学好程序设计的关键。接下来就来介绍循环语句及其使用。对于while循环来说,注意判断条件的使用,do...while语句要注意,它至少会执行一次do中的代码块,这是需要注意到的,对于for循环来说,括......
  • KY188 哈夫曼树C++
    用(优先队列)小根堆,先构建哈夫曼树,然后在递归遍历输出WPL。 #include<iostream>#include<queue>usingnamespacestd;structnode{intdata;structnode*left;structnode*right;};typedefstructnodetree;booloperator<(treeleft,treeright){......
  • 【C++】前置声明导致的代码含义改变
    真的有这么离谱的事哈哈哈哈。//F.hstructF{};structS:F{};//User.h#include<iostream>structF;structS;structUser{voidf(F*){std::cout<<"F"<<std::endl;}voidf(void*){std::cout<<"void"<......
  • 建立一个1个单位长度的字符串string c(1,str[i])
    https://www.luogu.com.cn/problem/P1765?contestId=155201`include<bits/stdc++.h>usingnamespacestd;strings[10][4]={{},{},{"a","b","c"},{"d","e","f"},{"g","h&q......