当前位置: 首页 > wzjs >正文

网站建设公司山西windows7优化大师下载

网站建设公司山西,windows7优化大师下载,装饰公司加盟,做网站卖电脑目录 前言 一、类的定义 1.类定义格式 2.访问限定符 3.类域 二、实例化 1.实例化概念 2.对象大小 三、this指针 四、C和C语言实现Stack对比 五、类的默认成员函数 1.构造函数 2.析构函数 3.拷贝构造函数 4.赋值运算符重载 4.1.运算符重载 4.2.赋值运算符重载 5.取地址运算符重载…

目录

前言

一、类的定义

1.类定义格式

2.访问限定符

3.类域

二、实例化

1.实例化概念

2.对象大小

三、this指针

四、C++和C语言实现Stack对比

五、类的默认成员函数

1.构造函数

2.析构函数

3.拷贝构造函数

4.赋值运算符重载

4.1.运算符重载

4.2.赋值运算符重载

5.取地址运算符重载

5.1.const成员函数

5.2.取地址运算符重载

六、再探构造函数

七、类型转换

八、static成员

九、友元

十、内部类

十一、匿名对象

十二、对象拷贝时的编译器优化

总结


前言

文章是本人学习过程中的一些记录、思考和参考大佬的文章后自己的理解。同时也希望自己以讲述的方式来呈现给大家,欢迎大家来指正交流。


一、类的定义

1.类定义格式

        先看一段代码:

#include<iostream>
using namespace std;
class Stack{int _x;int _y;void Init(int x,int y){_x = x;_y = y;}
};

 进行解释:

  • class为定义类的关键字,Stack为类的名字,{}中为类的主体,且类定义结束后要有分号(;)
  • 类里的内容称为类的成员;类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数
  • 我们习惯于给成员变量加一个特殊标识,如变量前加_等等,具体看公司要求
  • C++兼容C,C里的struct用法在C++中也得到了升级,变成了类,可定义函数了,但一般我们习惯用class定义类
  • 类里的成员函数默认为inline
struct ListNode{//用struct定义类,不需要再用typedef建立名字了,ListNode自己就可以的代表类型ListNode* _next;
};

2.访问限定符

#include<iostream>
using namespace std;
class Stack
{
private:int _x;int _y;
public:void Init(int x,int y){_x = x;_y = y;}
};

我们看到上述代码中新加了两个符号,这两个符号就是访问限定符

        我们知道类里面有各种各样的属性和方法,可并不是所有的成员都要提供外部用户使用,因此我们需要访问权限来进行限制。⼀般成员变量都会被限制为private/protected,需要给别人使用的成员函数会放再public

        在C++中,访问限定符有三种:

  • public修饰的成员在类外可以直接被访问
  • protectedprivate修饰的成员在类外不能直接被访问
  • protectedprivate是⼀样的,以后继承内容才能体现出他们的区别

        访问权限作用域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为止,若后面没有访问限定符,则到 } 为止,即类结束。

        还有一点,当class定义成员没有被访问限定符修饰时默认为private,struct默认为public。

3.类域

类定义了⼀个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 ::

用域操作符来指明成员属于哪个类域。

#include<iostream>
using namespace std;
class Stack
{
private:int _x;int _y;
public:void Init(int x,int y);
};void Stack::Init(int x,int y){_x = x;_y = y;}

        类域影响的是编译的查找规则,下面程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数。那么编译时,就会找不到_x等成员的声明/定义。指定类域Stack,就是知道Init是成员函数,当前域找不到_x等成员,就会到类域中去查找。

二、实例化

1.实例化概念

用类类型在物理内存中创建对象的过程,称为类实例化出对象。

在类没有实例化出对象之前,类里面的成员变量只是声明,没有分配空间,即类实例化出对象时,才会分配空间

我们由此可以想象出,类就是一副设计图,我们通过类这个设计图,可以生成建造出各种事物,即类实例化出对象。在没有建造生成前,设计图就只是设计图,啥也没有,类实例化出对象后,才有空间,才能住人。

咱们写个日期类来演示一下

#include<iostream>
using namespace std;
class Date
{ 
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;} 
private://只是声明,无空间分配int _year;int _month;int _day;
};int main()
{    //类实例化出对象:d1、d2Date d1;Date d2;d1.Init(2025,1,17);d2.Init(2025,2,23);
}

2.对象大小

        咱们先对类的成员进行分析一下

        类的成员分为成员函数和成员变量,当我们类实例化出对象后,每个对象都有自己的成员变量,可成员函数呢?它不是独有的,每个对象都可以调用,那成员函数还用储存在对象中吗?

        答案是不用。首先函数被编译后是⼀段指令,对象中没办法存储,这些指令存储在⼀个单独的区域(代码段)。

        调用函数被编译成汇编指令[call 地址],当编译器在编译链接时,就已经找到函数的地址,不是在运行时找的,只有动态多态是在运行时查找的,这时才需存储函数地址。

        即对象只存储成员变量,那我们就要介绍一下内存对齐的规则:

  • 第⼀个成员变量在与结构体偏移量为0的地址处。
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  • 注意:对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。VS中默认的对齐数为8
  • 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐数取 最小)的整数倍
  •  如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
  • 当类里没有成员变量,此时我们实例化出的对象大小为1,这里给1字节,纯粹是为了占位标识对象存在

详细可参看我之前的博客[C语言]--自定义类型: 结构体-CSDN博客中结构体内存对齐的内容

三、this指针

        现在我们思考一个问题,当我们用类实例化出多个对象d1、d2....当我们调用Init函数时,该函数如何判断是d1还是d2等对象调用呢?   于是C++给了⼀个隐含的this指针解决这里的问题。

        编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。比如Date类的Init的真实原型为: void Init(Date* const this, int year,int month, int day)

        类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this->_year = year;

        C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。

#include<iostream>
using namespace std;
class Date
{ 
public:
//相当于void Init(Date* const this, int year,int month, int day)void Init(int year, int month, int day){//this指针可以在函数里使用this-> _year = year;_month = month;_day = day;} 
private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2;//相当于 d1.Init(&d1,2025,1,17)d1.Init(2025,1,17);d2.Init(2025,2,23);
}

四、C++和C语言实现Stack对比

五、类的默认成员函数

默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数

⼀个类,在我们不写的情况下编译器会默认生成以下6个默认成员函数

这6个中最重要的是前4个,最后两个取地址重载不重要,我们稍微了解⼀下即可。

其次就是C++11以后还会增加两个默认成员函数,移动构造和移动赋值,这个我们后面再讲解。

1.构造函数

构造函数的作用:当对象实列化时,将对象初始化

它的本质是代替之前咱们Date类中写的Init函数,可不要因为它叫"构造",就误以为它是帮对象开空间(我们常使用的局部对象是栈帧创建时,空间就开好了)

构造函数的特点:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时系统会自动调用对应的构造函数。
  4. 构造函数可以重载
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成⼀个无参的默认构造函数,⼀旦用户显式定义构造函数,编译器将不再生成
  6. 默认构造就是不传实参就可以调用的构造。也就是说无参构造函数全缺省构造函数编译器默认生成的构造函数,都叫做默认构造函数,且这三种只能存在一个
  7. 当成员变量是内置类型的,编译器默认生成的构造函数就可以解决;当成员变量是自定义类型的,那编译器默认生成的构造函数就可能无法进行初始化,从而发生报错。要初始化自定义类型成员变量,就需要用到初始化列表

 咱们继续以Date类来说明:

class Date
{
public :// 1.⽆参构造函数Date(){_year = 1;_month = 1;_day = 1;} // 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;} // 3.全缺省构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

 注:因为默认构造函数这三种只能存在一个,所以此时1和3只能存在一个

int main(){//删除1和3Date d1;    //发生报错,没有默认构造Date d2(2025,1,15);Date d3();  //如果通过⽆参构造函数创建对象时,对象后⾯不⽤跟括号,否则编译器⽆法// 区分这⾥是函数声明还是实例化对象// warning C4930: “Date d3(void)”: 未调⽤原型函数(是否是有意⽤变量定义的?)
}

2.析构函数

析构函数的作用:析构函数不是完成对对象本身的销毁,而是完成对象中资源的清理释放。

就像Stack实现的Destroy功能,释放那些我们申请的空间,而像Date没有Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的。

析构函数的特点:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。
  3.  ⼀个类只能有⼀个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,系统会自动调用析构函数。
  5. 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定类型成员会调用他的析构函数。
  6. 还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。
  7. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如果默认生成的析构就可以用,也就不需要显示写析构,如MyQueue;但是有资源申请时,⼀定要自己写析构,否则会造成资源泄漏,如Stack。
  8. ⼀个局部域的多个对象,C++规定后定义的先析构。

3.拷贝构造函数

如果⼀个构造函数的第⼀个参数是只身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是⼀个特殊的构造函数。

拷贝构造的特点:

  1. 拷贝构造函数是构造函数的⼀个重载
  2. 拷贝构造函数的参数只有⼀个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。原因:C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参传值返回都会调用拷贝构造完成,而引用传参引用返回不需要拷贝。
  3. 若未显式定义拷贝构造,编译器会自动生成拷贝构造函数。其拷贝构造对内置类型成员变量会完成浅拷贝(指向的资源不进行拷贝,即拷贝的对象还是同一块空间),自定义类型成员变量会调用他的拷贝构造。
  4. 是自己显示定义还是直接让编译器自动生成,关键是成员变量的类型是否指向资源类成员变量都为内置类型且没有指向资源,使用自动生成的就行;                               类成员变量都为内置类型但指向了资源,就需要自己定义实现深拷贝(另起一个空间);类的成员变量主要为自定义类型,且自定义类型已经显示定义拷贝构造了,则此类不用自已显示定义了。
  5. 当⼀个类显示实现了析构并释放资源,那么他就需要显示实现拷贝构造,否则就不需要。
  6. 传值返回会产生⼀个临时对象调用拷贝构造; 传值引用返回不会产生拷贝,它会返回返回对象的别名,但当使用传值引用返回时,要注意返回对象在函数结束时是否还存在,不然会造成野引用。

        咱先解释一下使用传值返回为什么会引发无穷递归:

        接下来咱们用代码展示一下拷贝构造的内容:

class Date
{
public ://构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造 Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}  void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
void Func1(Date d)
{cout << &d << endl;d.Print();
} int main(){Date d1(2025,1,21);Func1(d1);cout << &d1 << endl;return 0;
}

当我们用传值传参时:输出的d和d1的地址是不同的,因为将d1传参需要进行拷贝构造,然后将其值赋给d,这样d1和d内容一样,但地址不一样。

也就是当我们调试时,想进入Func1中,会先进入Date(const Date& d),接着才会进入Func1。


void Func2(Date& d)
{cout << &d << endl;d.Print();
}
int main()
{Date d1(2025,1,21);Func2(d1);cout << &d1 << endl;return 0;
}

 而调用Func2时,因为引用传参不用进行拷贝,所以会直接进入Func2中。当然地址一样,内容也一样


int main()
{Date d1(2025,1,21);Date d2(d1);Date d3 = d1;return 0;
}

 这两种拷贝构造用法都行。


Date Func3() {Date d(2025, 2, 21);return d;
}
Date& Func4() {Date d(2025, 3, 21);return d;
}int main()
{Date d1(2025, 1, 21);Date d2 = Func3();Date d3 = Func4();d2.Print();d3.Print();return 0;
}

 传值返回和传值传参一样,都会进行拷贝构造,但引用返回会有个问题,函数结束后,你这个别名是否还存在,这里的d3就出现了这样的问题,产生了野指针,无法正确输出想要输出的数据。

4.赋值运算符重载

4.1.运算符重载

先简单介绍一下运算符重载:

当我们想对类类型对象经行一些运算操作时,原先的运算符已没能力处理,因为类里面有各种成员变量,而运算符只能对内置类型进行使用。

因此我们就需要对运算符进行重载,赋给它新的含义,否则就会编译报错。

运算符重载是具有特别名字的函数,他的名字是由operator后用要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。

重载运算符函数规则如下:

  • 重载运算符函数的参数个数和该运算符作用的运算对象数量⼀样多。几元运算符就有几个参数,并且从左往右依次对应第一第二等等参数,且重载运算符函数里的形参必须要有一个类类型。
  • 当⼀个重载运算符函数是成员函数时,由于每个成员函数的第一个函数是隐式的this指针,所以它的第⼀个运算对象会默认传给这个this指针,也就是说此时该函数的参数要比运算对象少一个
  • 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
  • 不能通过连接语法中没有的符号来创建新的操作符:比如operator@ ,不能通过运算符重载改变内置类型对象的含义。
  • ⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如日期类operator-可以有,但operator+就没有意义。
  • 有些运算符是不能重载的,如:   .*(成员指针操作符)   ::(作用域解析操作符)      sizeof(大小操作符)   ?:(条件操作符,也称为三元操作符)   .(成员访问操作符)  这五个不行。
  • 重载++运算符时,有前置++后置++,运算符重载函数名都是operator++,无法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分。

  • 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。

    重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置放类类型对象。

先来介绍一下.*(成员指针操作符)

class A
{ 
public:void func1(){cout << "A::func1()" << endl;}
};
typedef void(A::*PF1)(); //成员函数指针类型void func2()
{cout << "func2()" << endl;
}
typedef void(*PF2)();int main()
{PF1 pf1 = &A::func1;  // C++规定成员函数要加&才能取到函数指针A obj;(obj.*pf1)();       // 对象调⽤成员函数指针时,使⽤.*运算符PF2 pf2 = func2;(*pf2)();          //调用全局函数时,就不需要.*运算符return 0;
}

 成员函数之所以要用.*运算符,是因为成员函数都有一个隐式的this指针。调用成员函数指针时,必须明确指定一个对象(或对象的指针),因此要使用.*运算符


 重载为全局的面临对象访问私有成员变量的问题,有几种方法可以解决:

  1. 成员放公有
  2. Date提供getxxx函数
  3. 友元函数
  4. 重载为成员函数

 这里最推荐第四种方法

class Date
{ 
public:bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;} 
private:int _year;int _month;int _day;
}
int main()
{Date d1(2024, 7, 5);Date d2(2024, 7, 6);d1.operator==(d2);    // 运算符重载函数可以显⽰调⽤d1 == d2:             // 编译器会转换成 d1.operator==(d2);return 0;             //我们一般用第二种,毕竟可读性强,也简便
}

 对于+、--、<<和>>的重载

#include<iostream>
using namespace std;Class Date{friend ostream& operator<<(ostream&out,const Date& d); //友元函数下文可知
public:Date& operator++()   //前置{//...return *this;} Date operator++(int) //后置{Date tmp;//...return tmp;}
private:int _year;int _month;int _day;
}
//这里要用引用做类型是因为ostream将拷贝禁掉了,而引用无拷贝
ostream& operator<<(ostream&out,const Date& d){//....return out;
}

4.2.赋值运算符重载

赋值运算符重载是⼀个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于⼀个对象拷贝初始化给另⼀个要创建的对象。

赋值运算符重载的特点:

  1. 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const当前类类型引用,否则会传值传参会有拷贝
  2.  有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。
  3. 没有显式实现时,编译器会自动生成⼀个默认赋值运算符重载,默认赋值运算符重载行为跟默认构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。
  4. 至于是否要实现赋值运算符重载,可以参考拷贝构造函数的4、5规则

class Date
{ 
public:Date& operator=(const Date& d){if (this != &d)          //检查是否⾃⼰给⾃⼰赋值{_year = d._year;_month = d._month;_day = d._day;} return *this;            // d1 = d2表达式的返回对象应该为d1,也就是*this}
private:int _year;int _month;int _day;
}
int main()
{Date d1(2024, 7, 5);Date d2(d1);Date d3(2024, 7, 6);d1 = d3;// 需要注意这⾥是拷⻉构造,不是赋值重载// 请牢牢记住赋值重载完成两个已经存在的对象直接的拷⻉赋值// ⽽拷⻉构造⽤于⼀个对象拷⻉初始化给另⼀个要创建的对象Date d4 = d1;return 0;
}

5.取地址运算符重载

5.1.const成员函数

  • 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
  •  const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
  • 建议所有不修改成员变量的成员函数都加上const

 代码如下:

#include<iostream>
using namespace std;
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print1(){cout << _year << "-" << _month << "-" << _day << endl;}void Print2() const     //相当于  void Print(const Date* const this){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 7, 5);d1.Print1();d1.Print2();const Date d2(2024, 8, 5);d2.Print1();d2.Print2();return 0;
}

这里的 d2.Print1(); 出现错误   因为权限不能放大

error C2662: “void Date::Print1(void)”: 不能将“this”指针从“const Date”转换为“Date &”

5.2.取地址运算符重载

取地址运算符重载分为普通取地址运算符重载const取地址运算符重载,⼀般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非⼀些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现⼀份,胡乱返回⼀个地址。

代码如下:

class Date
{ 
public :Date* operator&(){return this;// return nullptr;}const Date* operator&()const{return this;// return nullptr;}
private :int _year ;int _month ;int _day ;
}

六、再探构造函数

  • 之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有⼀种方式,就是初始化列表,初始化列表的使用方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后面跟⼀个放在括号中的初始值或表达式。
  • 每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
  • 引用成员变量const成员变量没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。
  • C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
  • 尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器。C++并没有规定,对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。
  • 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。

 初始化列表总结:

无论是否显示写初始化列表,每个构造函数都有初始化列表

无论是否在初始化列表显示初始化成员变量,每个成员变量都要走初始化列表初始化;

 

 代码如下:

class Date
{
public :Date(int& x): _year(year), _month(month), _day(day), _t(12), _ref(x), _n(1){} 
private:int _year=1;    //注意这⾥不是初始化,这⾥给的是缺省值,这个缺省值是给初始化列表的int _month=1;   //如果初始化列表没有显⽰初始化,默认就会⽤这个缺省值初始化int _day=1;Time _t;        // 没有默认构造int& _ref;      // 引⽤const int _n;   // const
};

 注:初始化列表和声明时给缺省值最好只用其中之一,不然很乱

七、类型转换

  • C++支持内置类型隐式类型转换为类类型对象,也支持类类型的对象之间的转换,只是需要有相关内置类型为参数的构造函数
  • 构造函数前面加explicit就不再支持隐式类型转换

 

#include<iostream>
using namespace std;
class A
{
public :// explicit A(int a1)A(int a1): _a1(a1){}A(int a1, int a2):_a1(a1), _a2(a2){}void Print(){cout << _a1 << " " << _a2 << endl;}
int Get() const{return _a1 + _a2;}
private:int _a1 = 1;int _a2 = 2;
};
class B
{
public :B(const A& a): _b(a.Get()){}
private:int _b = 0;
};
int main()
{// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3// 编译器遇到连续构造+拷⻉构造->优化为直接构造A aa1 = 1;aa1.Print();const A& aa2 = 1;// C++11之后才⽀持多参数转化A aa3 = { 2,2 };// aa3隐式类型转换为b对象// 原理跟上⾯类似B b = aa3;const B& rb = aa3;return 0;
}

八、static成员

  • 用static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进行初始化
  • 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
  • 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针
  • 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针。
  • 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
  • 突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量静态成员函数
  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
  • 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。

 代码如下:

#include<iostream>
using namespace std;
class A
{
public :static int GetACount(){return _scount;}
private:static int _scount;	// 类⾥⾯声明
};
int A::_scount = 0;     // 类外⾯初始化

 

九、友元

  • 友元提供了⼀种突破类访问限定符封装的方式。友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到⼀个类的里面。
  • 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制,且⼀个函数可以是多个类的友元函数。
  • 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
  • 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
  • 友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
  • 虽然友元提供了便利。但是友元会增加耦合度,破坏封装,所以友元不宜多用。

总的来说就是: A想访问B,A就成为B的友元,即在B里声明A 。

友元函数:

#include<iostream>
using namespace std;
// 前置声明,都则A的友元函数声明编译器不认识B
class B;
class A
{friend void func(const A& aa, const B& bb);    // 友元声明
private:int _a1 = 1;int _a2 = 2;
};
class B
{friend void func(const A& aa, const B& bb);    // 友元声明
private:int _b1 = 3;int _b2 = 4;
};
void func(const A& aa, const B& bb)
{cout << aa._a1 << endl;cout << bb._b1 << endl;
}

 友元类:

class A
{
friend class B;   // 友元声明
private:int _a1 = 1;int _a2 = 2;
};
class B
{ 
public:void func1(const A& aa){cout << aa._a1 << endl;cout << _b1 << endl;}void func2(const A& aa){cout << aa._a2 << endl;cout << _b2 << endl;}
private:int _b1 = 3;int _b2 = 4;
}

十、内部类

  • 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
  • 内部类默认是外部类的友元类
  • 内部类本质也是⼀种封装当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。

十一、匿名对象

  • 用类型(实参) 定义出来的对象叫做匿名对象,相比之前我们定义的类型对象名(实参) 定义出来的叫有名对象
  • 匿名对象生命周期只在当前⼀行,⼀般临时定义⼀个对象当前用⼀下即可,就可以定义匿名对象。
  • 匿名对象和临时对象一样具有常性

 代码如下:

class A
{public :A(int a = 0): _a(a){} 
private:int _a;
};void func(const A& aa = A())  //用匿名对象来做类类型对象的缺省值
{
}int main()
{A aa1;//A aa1();   不能这么定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义A();      // 但是我们可以这么定义匿名对象,匿名对象的特点不⽤取名字,A(1);	  // 但是他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数const A& rel = A();    //const会延长匿名对象的生命周期,匿名对象跟着引用return 0;
}

十二、对象拷贝时的编译器优化

  • 现代编译器会为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传参过程中可以省略的拷贝。
  • 如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷贝会进行合并优化,有些更新更"激进"的编译还会进行跨行跨表达式的合并优化

这块内容当作了解,感兴趣的可以自行搜索学习。 


总结

简单介绍一些C++的知识,谢谢观看。

http://www.dtcms.com/wzjs/474485.html

相关文章:

  • 大连网站建设哪个好三叶草gw9356
  • 深圳哪家网站建设服务好软文写作范文500字
  • 无锡网站建设外包优势互联网营销培训课程
  • 四川微信网站建设seo网站查询
  • 类似稿定设计的软件有哪些网站优化及推广
  • 织梦企业网站管理系统2020国内搜索引擎排行榜
  • 湖南网站建设公司排行榜微信推广怎么做
  • 专业的移动网站建设公司四川百度推广和seo优化
  • 做公司网站用哪个空间好seo推广人员
  • 郑州手机网站建设html简单网页设计作品
  • 做app 的模板下载网站有哪些seo软件开发
  • 企业网站建设费是无形资产吗外贸企业网站设计公司
  • 网站建设方案对比百度seo引流
  • 长沙做网站优化磁力猫torrent kitty
  • 仿新闻网站源码百度seo点击
  • 广州 Wix网站开发杭州网站推广与优化
  • 软件开发项目文档怎么写seo站长常用工具
  • 广州网站制作工作室无锡网站优化公司
  • 网站建设包括哪些服务网络推广工具有哪些
  • 国内做的好看的网站刷推广链接的网站
  • 泽成seo网站排名怎么发布信息到百度
  • 上海做网站公司有哪些技成培训网
  • wordpress 家庭照片360seo排名点击软件
  • 学校网站的服务器公司营销网站建设
  • 山西大同网站建设哪家好国际重大新闻
  • 上海外贸网站google建站百度搜索排名
  • 个人网站界面模板百度收录申请入口
  • 武汉平面设计个人博客seo
  • 网站建设网页设计案例如何制作网页
  • 带搜索网站建设视频教程百度广告太多