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

【c++】类与对象详解

目录

  • 面向过程思想和面向对象思想
  • 类的定义
    • 引入类的关键字
    • 类定义的两种方式
    • 类的访问限定符
    • 类的作用域
    • 类大小的计算
    • 封装
  • this指针
  • 类的6个默认成员函数
    • 构造函数
      • 初步理解构造函数
      • 深入理解构造函数
        • 初始化列表
        • 单参数构造函数引发的隐式类型转换
    • 析构函数
    • 拷贝构造函数
    • 赋值运算符重载
      • 运算符重载
      • 赋值运算符重载
    • 取地址及const取地址操作符重载
      • const成员函数
      • 取地址及const取地址操作符重载
  • static成员
  • 友元
    • 友元函数
    • 友元类
  • 内部类
  • 什么是类与对象

面向过程思想和面向对象思想

c++作为一门高级语言,相较于c语言其引入了类和对象的概念,是一门面向对象的语言,而c语言是一门面向过程的语言。c语言作为一门面向过程的语言,面对问题更关注解决问题的过程,将问题拆解为一个个小步骤,逐个解决。而c++作为一门面向对象的语言,面对问题时更注重问题的对象,靠对象之间的交互解决问题。

类的定义

class _jiunian
{
public://……
protected://……
private://……
};

class是定义类的关键字,_jiunian是类的名字(凭喜好自己取),大括号中可以定义函数,变量,自定义类型。可以通过类访问限定符限制成员的访问条件。注意末尾分号不能省!

引入类的关键字

引入类最为常见的关键字是class和struct,两者的区别是struct定义的类默认情况下(不使用访问限定符)成员是公有的(public),即类的内部和类的外部都可以访问;而class定义的类默认情况下成员是私有的(private)(兼容c语言),即只有类的内部才可以访问。

类定义的两种方式

常见的类的定义方式有两种,一种是所有的成员全部完整定义在类的内部,一种是对于成员函数这种代码量较大的部分,采用声明定义分离,只将声明留在类中,再将类定义在头文件中。对于函数的定义,我们将其定义在源文件中,引入头文件到源文件中就行。这样做的好处就是类中的函数一行一个,找接口方便,而不是一个函数一大行,找个函数接口鼠标滚边天找不到。所以成员函数声明定义分离的定义方式是比较推荐的。

类的访问限定符

访问限定符可以限制成员的访问条件。访问限定符的效果持续到下一个访问限定符出现位置为止,如果之后没有访问限定符出现,就持续到结束位置。访问限定符有三种,分别是public、protected、private。public的效果是使成员变为公有,即类内类外都可以访问。protected和private则可以使成员变为私有,即只有类内可以访问,类外不可以访问。而其两者之间也有区别,当存在继承关系时,子类可以访问父类中的protected标识的成员,而不能访问父类中的private表示的成员。

类的作用域

c++中引入了作用域的概念,所有类的成员都得在类的作用域中。如果出现之前所说的函数的声明和定义分离的情况,那么在类外定义函数时有加上作用域解析符表明是哪个类域的。

类大小的计算

类的成员有很多种,但其实实际存储的只有类的成员变量。成员变量又称为类的属性,成员函数又称为类的方法。由这样的名字我们就能明白,因为对于同类型的类来说,它们之间的不同只有类的属性也就是成员变量。类的方法也就是成员函数都是相同的,相同的东西反复存储未免浪费空间,所以这些成员函数实际存在公共的代码段,即使定义多个相同类型的类,成员函数也就只有一组供这些类共同使用。刨去成员函数,对于这些类所占用的空间的计算方法与c语言中的结构体一样,这里不做过多赘述。需要注意的是,类的定义不会占用空间,只用真正定义了类类型的变量类才能真正实体化。即使是空类也是会占用一个字节的空间来占位的。

封装

类实际上就是对对象封装的结果,通过封装,隐藏对象的属性以及细节,控制对外开放的接口数量与对象进行交互。

this指针

class _jiunian
{
public:_jiunian(int a, int b, int c){_a = a;_b = b;_c = c;}void reset(int a, int b, int c){_a = a;_b = b;_c = c;}
private:int _a;int _b;int _c;
};int main()
{_jiunian x(1, 1, 1), y(2, 2, 2);x.reset(1, 2, 3);y.reset(1, 2, 3);return 0;
}

正如笔者之前所说的一样,类的成员函数只保存一份且存在公共代码段,所以无论是x.reset还是y.reset调用的都是同一个函数,那对于只传了数值而没有传递地址的函数是怎么识别要操作的类是哪一个的呢?实际上,c++编译器给每一个非静态成员函数隐式传递了一个this指针,这个指针是 类类型*const 类型,即指向类类型变量,其本身不能改变的指针变量。需要注意的是:this指针不能被赋值,因为被const修饰了;this指针只能再成员函数内部使用,写在除此之外的任何地方都会报错,this本身也是c++中的一个关键字,不能将变量起这个名字;this指针不会存在对象中,this指针在类的成员函数被调用时由编译器自动识别传参;this指针在现在的编译器优化下为达到最佳速度,一般是存在寄存器中,因为要反复调用;使用空指针调用类的成员函数时this指针也可以为空,不过这时函数中倘若出现对this指针解引用的操作就会报错;在成员函数中对this指针解引用的操作可以将this省略,直接写成员的名字。

类的6个默认成员函数

在类中有6个如果我们没有显式定义,编译器也会自动生成的函数。这些成员函数称为默认函数。

构造函数

class jiunian
{
public:jiunian(){_a = 1;_b = 1;_c = 1;}jiunian(int a, int b, int c){_a = a;_b = b;_c = c;}
private:int _a;int _b;int _c;
};int main()
{jiunian x;//不可以写成jiunian x(),这样会被当成函数的声明!jiunian y(1, 2, 3);return 0;
}

初步理解构造函数

构造函数是一个特殊的成员函数,他直接由类名来命名,且没有返回值,它在创建类类型对象时被编译器自动调用,保证每个成员变量都有一个合适的初始值,构造函数本身并不会起到构造对象的作用,构造对象是由系统开辟空间构造的,构造函数本身只起到对类类型对象的成员变量进行初始化的作用。构造函数本身也支持函数重载。较为常见的是重载两个函数,一个无参,一个有参,这样可以在类类型对象创建时选择自定义对象的成员变量初始值,也可以直接创建对象,这时对象的成员变量就是设置好的默认值,当然我们也可以直接通过全缺省函数完成这一操作。当我们没有显式创建构造函数时,编译器会默认自己生成一个默认构造函数以供创建类类型对象时使用,编译器生成的构造函数是默认构造函数,默认构造函数是指在没有任何参数时会调用的函数,无参构造函数或者全缺省构造函数都是默认构造函数,并不是只有编译器自己生成的才叫默认构造函数(默认构造函数只能有一个)。那么,说到这里,既然编译器自己会生成默认构造构造函数,我们是不是就不用自己显式定义了呢?其实不是,虽然编译器会自己生成,但它也不会智慧到会猜得到我们想要将成员变量设置成什么数,所以我们还是有需要手动设置的情况。具体来说,编译器自动生成的默认构造函数不会对内置类型(c++自带的类型,像int、double、short这种)进行初始化(不初始化就是随机值),因为编译器自己也不会知道究竟要初始化成什么才好,这只能由我们自己来设置。但倘若是自定义类型,编译器生成的默认构造函数会自动调用自定义类型自己的构造函数来进行初始化,毕竟只能靠这个来初始化,编译器不会有任何质疑。当然,内置类型不会默认初始化,我们又懒得显式定义时,我们可以在声明变量时加上缺省值,这样不用显式定义也能完成对内置类型的初始化。总的来说,如果一个类中的成员变量都是自定义类,我们就不用自己写构造函数,编译器自己生成的就能解决;若一个类中的成员变量中有内置类型,就需要我们自己写构造函数或者给出缺省值来解决。

深入理解构造函数

通过初步理解构造函数,其用法我们已经了然于胸,但对于构造函数本质的理解还是不够的。

初始化列表

我们在构造函数的函数名和参数的后面可以加上一个冒号,后面是以逗号分隔的成员变量列表,每个变量后面可以加上括号,括号中是想要初始化的值,通过初始化列表。我们也能完成对类的成员变量的初始化操作。

class jiunian
{
public:jiunian():_a(2),_b(2),_c(2)//初始化列表{_a = 1;_a = 2;_b = 1;_c = 1;}jiunian(int a, int b, int c){_a = a;_b = b;_c = c;}
private:int _a = 0;int _b = 0;int _c = 0;
};int main()
{jiunian x;return 0;
}

又是初始化列表又是构造函数又是缺省值的,只是初始化一下类却有这么多种方法,倘若我既设置缺省值,又给出初始化列表,在构造函数中又给出赋值,最后函数会如何处理呢。跑一下代码后我们会发现最后类中的成员变量的值是以类中的赋值为准。这是为什么呢?事实上,构造函数内的初始化,并不是真正意义上的初始化,它只是一种赋值,进一步说,是给成员变量赋初值,就像我上面的代码中一样,我可以对同一个变量多次赋初值,倘若是初始化,这种操作是不行的,因为变量只能初始化一次。而初始化列表才是真正的初始化,倘若我们在初始化列表对同一个变量多次初始化编译器便会报错。而缺省值所要真正传递给的对象其实是初始化列表,在我们没有给出初始化列表的值会接受缺省值进行初始化。下面对类类型对象创建时构成进行详细解释。在类类型对象创建时,会优先使用构造函数的初始化列表对类类型对象进行初始化,若有成员变量没有在初始化列表中出现,就会将改成员变量的缺省值传给初始化列表进行初始化(如果有缺省值的情况下)。如果没有在初始化列表中出现,也没有缺省值,这时就会分为两种情况进行处理:一种是该变量是内置类型,编译器不会对内置类型进行初始化;一种是它是自定义类型,编译器会自动调用它的构造函数进行初始化。这时我们就明白,对于内置类型和自定义类型的处理在初始化列表阶段就已经完成。在初始化列表结束之后,才会进入构造函数内部对成员变量进行初赋值,所以成员变量的初始值最终是由构造函数的内部决定的,构造函数内部没有初赋值就看初始化列表,初始化列表没有就看缺省值,都没有那就是随机值了。
初始化列表的注意事项:
1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
(1)引用成员变量
(2)const成员变量
(3)自定义类型成员(且该类没有默认构造函数时)
我们可以发现,在初始化列表中,对于自定义成员来说,有默认构造函数不写的话会自己调用,没默认构造函数则必须要写,总的来说,自定义成员必定会在初始化列表完成初始化。对于成员变量,能在初始化列表初始化就尽量在初始化列表初始化。因为对于内置类型来说,在构造函数内部初赋值,实际上是先初始化再赋值,因为即使编译器在初始化列表对没有显式初始化的内置成员变量不会进行初始化,但它本质还是被创建了出来,只不过是随机值。而如果在初始化列表初始化,就是直接初始化,效率更高。对于自定义类型就更是如此了,自定义类型必定在初始化列表初始化,这时再在构造函数内部赋值,还要调用赋值函数,占用不少资源,很亏。所以综上尽量使用初始化列表,但这并不是意味着我们可以无脑使用初始化列表,有些情况初始化列表无法完成初始化,比如:

class jiunian
{
public:jiunian():_a(2),_b(2),_c(2){int* ptr = (int*)calloc(4, sizeof(int));if (ptr == nullptr){perror("jiunian:calloc");return;}_d = ptr;}
private:int _a;int _b;int _c;int* _d;
};int main()
{jiunian x;return 0;
}

这种要为指针动态开辟空间初始化的成员变量就不能直接用初始化列表初始化,当然这只是一种简单场景,实际应用还会有个各种情况,我们应该灵活应对。最后还有一个初始化列表的小坑要说,就是初始化列表的初始化顺序并不是按照初始化列表的顺序来进行的,是成员变量在类中的定义顺序来决定的。简单来说就是,如果a变量在类中的定义位置在b变量之前,那在初始化列表中即使b的初始化写在a之前,也是a先初始化,这点要注意,避免出现以下问题:

class shinku
{
public:shinku(){std::cout << "shinku" << std::endl;}
private:int _a;
};class jiunian
{
public:jiunian():_b(2),_a(_b),//_a先定义,_b此时是随机值,_a被定义成随机值_c(2){}
private:int _a;int _b;int _c;
};int main()
{jiunian x;return 0;
}
单参数构造函数引发的隐式类型转换

在C语言中对内置类型之间的运算存在隐式类型转换,在c++中对于类类型对象也有这种操作,比如以下的代码是可以跑的起来的:

class jiunian
{
public:jiunian(int a){_a = a;}
private:int _a;
};int main()
{jiunian x = 1;return 0;
}

上面这组代码能跑的起来就是因为1被隐式类型转化成了类类型对象,再由转换而来的临时变量给x拷贝构造而来(但编译器实际上并不是这么实现的,因为这个临时变量由1隐式转换而来,在拷贝构造给x后就被销毁了,编译器会觉得很浪费,直接优化成jiunian x(1),这样就只有一次调用构造函数,节省资源,虽然最终不是这样实现的,但这种优化还是因为有隐式类型转换才得以存在)。在c++中的类只有当其构造函数可以只接受一个参数时才会引发隐式类型转换(只有一个参数,或者除了第一个参数外都有缺省值,或者是全缺省),毕竟要是可以接收多个参数的话只用一个数怎么也引发不了。如果想要防止这样的隐式类型转换,可以在构造函数的前面加上explicit来限制这种隐式类型转换。

析构函数

class jiunian
{
public:jiunian(int a, int b):_a(a),_b(b){int* ptr = (int*)calloc(4, sizeof(int));if (ptr == nullptr){perror("jiunian:calloc");}_c = ptr;}~jiunian(){_a = 0;_b = 0;free(_c);_c = nullptr;}
private:int _a;int _b;int* _c;
};int main()
{jiunian x(1, 1);return 0;
}

析构函数在对象销毁时调用,析构函数没有返回值,名字与类名一致但前面要加上~以和构造函数区分,析构函数没有参数,毕竟销毁类也不需要什么参数(this指针还是要的,但也不用自己写出)。析构函数在类对象销毁时自动调用,与构造函数一样,虽然析构函数在函数销毁时调用,但析构函数本身不会起到销毁对象的作用,只会对类中的资源进行清理。析构函数在我们没有显式定义时编译器会自动生成,自动生成的析构函数对于类中的内置类型对象不会进行处理,因为内置类型在程序结束时由系统回收处理就行,而对于自定义类型则会自动调用类的析构函数来进行资源清理。析构函数一般不用自己显示定义,但当类的构造函数向系统申请了资源时,务必要显示定义析构函数,系统自动生成的析构函数不会识别释放你申请的资源,不写会造成内存泄漏。

拷贝构造函数

class jiunian
{
public:jiunian(int a, int b):_a(a),_b(b){int* ptr = (int*)calloc(4, sizeof(int));if (ptr == nullptr){perror("jiunian_i_i:calloc");return;}_c = ptr;}jiunian(jiunian& x){std::cout << "jiunian" << std::endl;_a = x._a;_b = x._b;int* ptr = (int*)calloc(4, sizeof(int));if (ptr == nullptr){perror("jiunian_j:calloc");return;}_c = ptr;memcpy(_c, x._c, sizeof(_c));}~jiunian(){_a = 0;_b = 0;free(_c);_c = nullptr;}
private:int _a;int _b;int* _c;
};

拷贝构造函数,刨去拷贝,本质还是构造函数。拷贝构造函数是构造函数的一种重载形式。拷贝函数被用于用已有的类类型对象创建新的类类型对象时由编译器自动调用使用。

jiunian x(1, 1);
jiunian y(x);
jiunian y = x;

使用这两种方法都可以对y进行拷贝初始化,第一种是直接传参调用,一种是用一个类类型对象初始化另一个。此外,我们还应注意的是,事实上在c++中,在函数传参时,参数是会被拷贝一份传进函数中的,这些局部变量会在函数结束时销毁,所以类在函数传参时也会调用拷贝构造函数,这也是为什么我们不能在拷贝函数传参时直接传值调用的原因,如果对拷贝构造函数传值调用就会先去调用拷贝构造,然后因为要传值所以又会调用,这会引发无穷递归,没完没了了,所以不行。拷贝函数如果我们不去显式定义,编译器会自动生成。编译器自动生成的拷贝构造是浅拷贝,又称值拷贝,会对拷贝的对象按存储空间逐字节。这样的拷贝方式对大部分情况都很合适,所以大部分情况拷贝函数可以不用写,但是,老样子,对于需要向系统申请空间的类,拷贝函数需要自己写,不然会造成两个同类型的类中的指针指向同一块空间,程序结束类类型对象销毁时重复释放同一块空间造成报错。

赋值运算符重载

运算符重载

class jiunian
{
public:jiunian(int a, int b):_a(a),_b(b){}jiunian(jiunian& x){std::cout << "jiunian" << std::endl;_a = x._a;_b = x._b;}bool operator<(jiunian y){if (_a < y._a && _b < y._b){return true;}return false;}~jiunian(){_a = 0;_b = 0;}
private:int _a;int _b;
};int main()
{jiunian x(1, 1);jiunian y(2, 2);if (x < y)std::cout << "work" << std::endl;return 0;
}

c++为了增加类相关的代码的可读性,引入了运算符重载,运算符重载本质是一种特殊的函数,关键字是operator接要重载的运算符,注意中间没有空格。函数定义的格式是:返回值类型 operator操作符(参数列表)。对于运算符重载函数,需要注意的是:
(1)不能通过连接其他符号来创建新的操作符:比如operator@
(2)重载操作符必须有一个类类型参数
(3)用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
(4)作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
(5).* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

对于运算符重载函数,遵循以上规定就能对于大部分双目操作符进行定义,但对于单目操作符++(或–)这种前置后置有细微差别的操作符该怎么去定义呢?

	jiunian& operator++(){++_a;++_b;return *this;}jiunian& operator++(int){jiunian* const ret = this;++_a;++_b;return *ret;}

对于前置++和后置++,想要实现他们之间细微的不同毫无疑问要靠函数重载,但怎么重载成了问题,毕竟函数名一样,函数参数也一样,参数还只有一个this指针,根本没法重载,这时c++特别规定在后置++函数的参数中加入int类型参数以跟前置++区分,这个int类型参数本身在函数中没有任何作用,所以可以省略参数名。

赋值运算符重载

	jiunian& operator=(jiunian& y){if(this == &y){_a = y._a;_b = y._b;}return *this;}

与运算符重载函数不同的是,他是类的6个默认函数之一,即使不自己显式定义,编译器也会自己生成。因为是对已有的类类型对象进行操作,不必考虑局部变量销毁产生野引用的问题,放心大胆的将参数和返回值都设置成引用,减少拷贝构造函数的调用,节省资源。if语句检查是否是自己给自己赋值,进一步优化函数。因为内置类型的=运算符会返回赋值后的结果,这里的=重载也是同理。需要注意的是,由于赋值运算符重载是类的6个默认构造函数,所以如果要显式定义,务必在类内定义(其他的运算符重载是可以在类外实现,成员变量访问冲突也可以通过友元函数解决),因为如果在类外面定义,即使将类成员变量设置成公有,此时类内因为没有显示定义,会默认生成一个,这样在函数调用时会发生冲突,所以不行。编译器默认生成的赋值重载函数与拷贝构造函数有些类似,都是按存储空间逐字节拷贝,因为两者本质都是拷贝,但拷贝构造是先初始化再拷贝,赋值运算符重载是直接拷贝。既然是拷贝,当然就要注意申请空间的情况,务必显式定义处理这种情况。当我们看见类和类之间用=相连也别一棒子打死说就是赋值运算符重载,也可以是拷贝构造的。

	jiunian x(1, 1);jiunian y(x);//拷贝构造jiunian z = x;//拷贝构造jiunian a(1,1);jiunian b(2, 2);x = y;//赋值运算符重载

要看清是初始化还是赋值。

取地址及const取地址操作符重载

const成员函数

bool operator<(jiunian y) const//const加在后面
{if (_a < y._a && _b < y._b){return true;}return false;
}

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。关于const成员函数有几个经典问题:
(1)const对象可以调用非const成员函数吗?
(2) 非const对象可以调用const成员函数吗?
(3)const成员函数内可以调用其它的非const成员函数吗?
(4)非const成员函数内可以调用其它的const成员函数吗?
对于const,我们只需要明白一个原则:权限可以平移,缩小,但不能放大。const对象意味着不能改变,而非const成员函数可能会改变const对象,所以不能调用。非const对象意味着可以改变,但不变也不会有问题,所以可以调用const成员函数。const成员函数意味着不能改变参数,而非const成员函数可能会改变参数,所以不能。非const成员函数意味着能改变参数,但也可以不改变,所以能。

取地址及const取地址操作符重载

Date* operator&(){return this ;
比特就业课
}const Date* operator&()const{return this ;}

代码如上,这两个默认成员函数很少会自己显式定义,极特别情况才会,比如别人想要取地址,你调皮就不想让别人取,一般编译器默认生成的就够用了。

static成员

class jiunian
{
public:jiunian(){_a = 1;_b = 1;}static int print(){std::cout << "print" << std::endl;}
private:int _a;int _b;static int _c;
};int jiunian::_c = 0;int main()
{jiunian x;x.print();return 0;
}

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的
成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。静态成员变量有以下特性:
(1)静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区(所以不在类里面定义)
(2)静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明(不能给缺省值,因为不走初始化列表)
(3)类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问(静态成员函数可以不通过对象访问,直接用作用域解析符访问)
(4)静态成员函数没有隐藏的this指针,不能访问任何非静态成员(非要访问也能自己传)
(5)静态成员也是类的成员,受public、protected、private 访问限定符的限制(初始化时可以破一次例)

友元

using namespace std;class jiunian
{friend ostream& operator<<(ostream& _cout, const Date& x);friend istream& operator>>(istream& _cin, Date& x);
public:jiunian(){}private:int _a;int _b;int _c;
};
ostream& operator<<(ostream& _cout, const Date& x)
{_cout << d._a << "-" << d._b << "-" << d._c;return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._a;_cin >> d._b;_cin >> d._c;return _cin;
}
int main()
{jiunian a;cin >> a;cout << a << endl;return 0;
}

友元为类外的函数和类提供了一种访问类内部成员的方式,但友元破坏了封装,增加了耦合度,所以友元不能频繁使用。

友元函数

友元函数可以直接访问声明过的类的私有和保护成员,声明时记得加关键字friend,友元函数定义在类外,不能属于任何类。关于友元函数应该注意:
(1)友元函数可访问类的私有和保护成员,但不是类的成员函数
(2)友元函数不能用const修饰(是指放在后面修饰this指针,放在前面修饰返回值还是可以的)
(3)友元函数可以在类定义的任何地方声明,不受类访问限定符限制
(4)一个函数可以是多个类的友元函数
(5)友元函数的调用与普通函数的调用原理相同

友元类

和友元函数一样,友元类可以直接访问声明过的类的私有和保护成员,但是有以下几点需要注意:
(1)友元关系是单向的,不具有交换性。例如A是B的友元,意味着A可以访问B的私有或者保护成员,但不意味着反过来B是A的友元,B也不能访问A的私有或者保护成员。
(2)友元关系不能传递.例如A是B的友元,C是A的友元,但C不是B的友元。
(3)友元关系也不能继承。

内部类

class jiunian
{
public:class thx{public:thx():_x(1){b = 1;//不用作用域解析符也不用类类型对象,直接访问}private:int _x;};
private:int _a;static int _b;
};int jiunian::_b = 0;int main()
{jiunian m;jiunian::thx n;//作用域解析符,thx被包在jiunian的类域中了return 0;
}

一个类定义在另一个类的内部,这个类就叫内部类。内部类是一个独立的类,不属于外部类,无法通过外部类类型对象去访问内部类,外部类类型对象的存储空间大小的计算也不会加上内部类,外部类对内部类没有任何访问权限上的优越。但内部类是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员,但是外部类不是内部类的友元。内部类有以下几点要注意:
(1)内部类可以定义在外部类的public、protected、private都是可以的。
(2)注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
(3)sizeof(外部类)=外部类,和内部类没有任何关系。
(4)不能再内部类中定义外部类的类型对象,会报错说不允许使用不完整的类型,因为外部类的定义还没有结束,编译器无法为内部类的外部类类型对象计算大小。
(5)定义内部类类型对象时不要忘记用作用域解析符。

什么是类与对象

类是面向对象编程中对具有相同属性和行为的一组事物的抽象描述,它定义了数据成员(属性)和成员函数(行为),是创建对象的模板而对象是类的具体实例,依据类的定义在内存中被创建,拥有类所规定的属性和行为,且每个对象的属性值可以不同,能通过调用成员函数来执行特定操作

相关文章:

【c++】类与对象详解

目录 面向过程思想和面向对象思想类的定义引入类的关键字类定义的两种方式类的访问限定符类的作用域类大小的计算封装 this指针类的6个默认成员函数构造函数初步理解构造函数深入理解构造函数初始化列表单参数构造函数引发的隐式类型转换 析构函数拷贝构造函数赋值运算符重载运…...

LabVIEW如何有效地进行数据采集?

数据采集&#xff08;DAQ&#xff09;是许多工程项目中的核心环节&#xff0c;无论是测试、监控还是控制系统&#xff0c;准确、高效的数据采集都是至关重要的。LabVIEW作为一个图形化编程环境&#xff0c;提供了丰富的功能来实现数据采集&#xff0c;确保数据的实时性与可靠性…...

Golang 并发机制-4:用Mutex管理共享资源

并发性是Go的强大功能之一&#xff0c;它允许多个线程&#xff08;并发线程&#xff09;同时执行。然而&#xff0c;权力越大&#xff0c;责任越大。当多个例程并发地访问和修改共享资源时&#xff0c;可能会导致数据损坏、竞争条件和不可预测的程序行为。为了解决这些问题&…...

如何用微信小程序写春联

​ 生活没有模板,只需心灯一盏。 如果笑能让你释然,那就开怀一笑;如果哭能让你减压,那就让泪水流下来。如果沉默是金,那就不用解释;如果放下能更好地前行,就别再扛着。 一、引入 Vant UI 1、通过 npm 安装 npm i @vant/weapp -S --production​​ 2、修改 app.json …...

从零开始:用Qt开发一个功能强大的文本编辑器——WPS项目全解析

文章目录 引言项目功能介绍1. **文件操作**2. **文本编辑功能**3. **撤销与重做**4. **剪切、复制与粘贴**5. **文本查找与替换**6. **打印功能**7. **打印预览**8. **设置字体颜色**9. **设置字号**10. **设置字体**11. **左对齐**12. **右对齐**13. **居中对齐**14. **两侧对…...

LLMs之OpenAI o系列:OpenAI o3-mini的简介、安装和使用方法、案例应用之详细攻略

LLMs之OpenAI o系列&#xff1a;OpenAI o3-mini的简介、安装和使用方法、案例应用之详细攻略 目录 相关文章 LLMs之o3&#xff1a;《Deliberative Alignment: Reasoning Enables Safer Language Models》翻译与解读 LLMs之OpenAI o系列&#xff1a;OpenAI o3-mini的简介、安…...

DeepSeek-R1 低成本训练的根本原因是?

在人工智能领域&#xff0c;大语言模型&#xff08;LLM&#xff09;正以前所未有的速度发展&#xff0c;驱动着自然语言处理、内容生成、智能客服等众多应用的革新。然而&#xff0c;高性能的背后往往是高昂的训练成本&#xff0c;动辄数百万美元的投入让许多企业和研究机构望而…...

C语言:结构体

一&#xff0c;结构体 C语⾔已经提供了内置类型&#xff0c;如&#xff1a;char、short、int、long、float、double等&#xff0c;但是只有这些内置类型还是不够的&#xff0c;假设我想描述学⽣&#xff0c;描述⼀本书&#xff0c;这时单⼀的内置类型是不⾏的。 描述⼀个学⽣需…...

java练习(5)

ps:题目来自力扣 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这…...

【高等数学】贝塞尔函数

贝塞尔函数&#xff08;Bessel functions&#xff09;是数学中一类重要的特殊函数&#xff0c;通常用于解决涉及圆对称或球对称的微分方程。它们在物理学、工程学、天文学等多个领域都有广泛的应用&#xff0c;例如在波动方程、热传导方程、电磁波传播等问题中。 贝塞尔函数的…...

贪吃蛇实现

1.资料来源 https://learn.microsoft.com/zh-cn/windows/console/getstdhandle 2.前言 简介 贪吃蛇是久负盛名的游戏&#xff0c;和俄罗斯方块、扫雷等游戏位列于经典游戏的行列。 《贪食蛇》中玩家控制一条不断移动的蛇&#xff0c;在屏幕上吃掉出现的食物。每吃掉一个食物…...

Windows电脑本地部署运行DeepSeek R1大模型(基于Ollama和Chatbox)

文章目录 一、环境准备二、安装Ollama2.1 访问Ollama官方网站2.2 下载适用于Windows的安装包2.3 安装Ollama安装包2.4 指定Ollama安装目录2.5 指定Ollama的大模型的存储目录 三、选择DeepSeek R1模型四、下载并运行DeepSeek R1模型五、使用Chatbox进行交互5.1 下载Chatbox安装包…...

在C++中,成员变量必须在对象构造完成前初始化,但初始化的方式有多种...

在C中&#xff0c;成员变量必须在对象构造完成前初始化&#xff0c;但初始化的方式可以有多种&#xff0c;具体取决于成员变量的类型和设计需求。以下是C中成员变量初始化的规则和相关机制&#xff1a; 1. 成员变量必须初始化 如果成员变量是基本类型&#xff08;如 int、doub…...

maven mysql jdk nvm node npm 环境安装

安装JDK 1.8 11 环境 maven环境安装 打开网站 下载 下载zip格式 解压 自己创建一个maven库 以后在idea 使用maven时候重新设置一下 这三个地方分别设置 这时候maven才算设置好 nvm 管理 npm nodejs nvm下载 安装 Releases coreybutler/nvm-windows GitHub 一键安装且若有…...

算法随笔_37: 交替合并字符串

上一篇:算法随笔_36: 复写零-CSDN博客 题目描述如下: 给你两个字符串 word1 和 word2 。请你从 word1 开始&#xff0c;通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长&#xff0c;就将多出来的字母追加到合并后字符串的末尾。 返回 合并后的字符串 。 示例…...

w188校园商铺管理系统设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…...

(2025 年最新)MacOS Redis Desktop Manager中文版下载,附详细图文

MacOS Redis Desktop Manager中文版下载 大家好&#xff0c;今天给大家带来一款非常实用的 Redis 可视化工具——Redis Desktop Manager&#xff08;简称 RDM&#xff09;。相信很多开发者都用过 Redis 数据库&#xff0c;但如果你想要更高效、更方便地管理 Redis 数据&#x…...

【Block总结】Shuffle Attention,新型的Shuffle注意力|即插即用

一、论文信息 标题: SA-Net: Shuffle Attention for Deep Convolutional Neural Networks 论文链接: arXiv 代码链接: GitHub 二、创新点 Shuffle Attention&#xff08;SA&#xff09;模块的主要创新在于高效结合了通道注意力和空间注意力&#xff0c;同时通过通道重排技…...

解锁豆瓣高清海报(一) 深度爬虫与requests进阶之路

前瞻 PosterBandit 这个脚本能够根据用户指定的日期&#xff0c;爬取你看过的影视最高清的海报&#xff0c;然后使用 PixelWeaver.py 自动拼接成指定大小的长图。 你是否发现直接从豆瓣爬取下来的海报清晰度很低&#xff1f; 使用 .pic .nbg img CSS 选择器&#xff0c;在 我…...

【机器学习与数据挖掘实战】案例11:基于灰色预测和SVR的企业所得税预测分析

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数据集中发现模式、关联…...

聚簇索引、哈希索引、覆盖索引、索引分类、最左前缀原则、判断索引使用情况、索引失效条件、优化查询性能

聚簇索引 聚簇索引像一本按目录排版的书&#xff0c;用空间换时间&#xff0c;适合读多写少的场景。设计数据库时&#xff0c;主键的选择&#xff08;如自增ID vs 随机UUID&#xff09;会直接影响聚簇索引的性能。 什么是聚簇索引&#xff1f; 数据即索引&#xff1a;聚簇索引…...

克隆OpenAI(基于openai API和streamlit)

utils.py&#xff1a; from langchain_openai import ChatOpenAI from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationChain import osdef get_chat_response(api_key,prompt,memory): # memory不能是函数的内部局部变量&…...

DeepSeek技术深度解析:从不同技术角度的全面探讨

DeepSeek技术深度解析&#xff1a;从不同技术角度的全面探讨 引言 DeepSeek是一个集成了多种先进技术的平台&#xff0c;旨在通过深度学习和其他前沿技术来解决复杂的问题。本文将从算法、架构、数据处理以及应用等不同技术角度对DeepSeek进行详细分析。 一、算法层面 深度学…...

完全卸载mysql server步骤

1. 在控制面板中卸载mysql 2. 打开注册表&#xff0c;运行regedit, 删除mysql信息 HKEY_LOCAL_MACHINE-> SYSTEM->CurrentContolSet->Services->EventLog->Application->Mysql HKEY_LOCAL_MACHINE-> SYSTEM->CurrentContolSet->Services->Mysql …...

2025年大年初一篇,C#调用GPU并行计算推荐

C#调用GPU库的主要目的是利用GPU的并行计算能力&#xff0c;加速计算密集型任务&#xff0c;提高程序性能&#xff0c;支持大规模数据处理&#xff0c;优化资源利用&#xff0c;满足特定应用场景的需求&#xff0c;并提升用户体验。在需要处理大量并行数据或进行复杂计算的场景…...

机器学习优化算法:从梯度下降到Adam及其实验改进

机器学习优化算法&#xff1a;从梯度下降到Adam及其实验改进 在机器学习和深度学习领域&#xff0c;模型的训练过程本质上是一个优化问题。优化算法的作用是通过调整模型参数&#xff0c;使得模型在给定的数据 集上实现最优性能。而优化算法的效率和效果直接决定了模型的收敛速…...

在 Ubuntu 中使用 Conda 创建和管理虚拟环境

Conda 是一个广泛使用的包管理和环境管理系统&#xff0c;尤其适用于数据科学和 Python 开发。本文将指导你如何在 Ubuntu 系统中安装 Conda 并创建基于 python3.11 的虚拟环境。 1. 安装 Miniconda 或 Anaconda 方法 1&#xff1a;下载并安装 Miniconda Miniconda 是一个轻量…...

【深度学习】搭建卷积神经网络并进行参数解读

第一步 导包 import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F from torchvision import datasets,transforms import matplotlib.pyplot as plt import numpy as np %matplotlib inline transforms 模块是 torchvision 库的…...

稀疏进化训练:机器学习优化算法中的高效解决方案

稀疏进化训练&#xff1a;机器学习优化算法中的高效解决方案 稀疏进化训练&#xff1a;机器学习优化算法中的高效解决方案引言第一部分&#xff1a;背景与动机1.1 传统优化算法的局限性1.2 进化策略的优势1.3 稀疏性的重要性 第二部分&#xff1a;稀疏进化训练的核心思想2.1 稀…...

Vue - Suspense的使用

在 Vue 3 中&#xff0c;Suspense 是一个用于处理异步组件的 API。它允许在加载异步组件时提供一个后备内容&#xff08;例如加载指示器&#xff09;&#xff0c;从而改善用户体验。在加载期间&#xff0c;可以在页面上显示一个占位符&#xff0c;而不是让用户看到一个空白或错…...

在K8S中,pending状态一般由什么原因导致的?

在Kubernetes中&#xff0c;资源或Pod处于Pending状态可能有多种原因引起。以下是一些常见的原因和详细解释&#xff1a; 资源不足 概述&#xff1a;当集群中的资源不足以满足Pod或服务的需求时&#xff0c;它们可能会被至于Pending状态。这通常涉及到CPU、内存、存储或其他资…...

【算法】回溯算法专题② ——组合型回溯 + 剪枝 python

目录 前置知识进入正题小试牛刀实战演练总结 前置知识 【算法】回溯算法专题① ——子集型回溯 python 进入正题 组合https://leetcode.cn/problems/combinations/submissions/596357179/ 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以…...

理解红黑树

简介&#xff1a;红黑树是一种自平衡二叉查找树&#xff0c;由鲁道夫贝尔&#xff08;Rudolf Bayer&#xff09;在1972年发明&#xff0c;最初称为“对称二叉B树”。它的设计旨在解决普通二叉查找树在频繁插入和删除操作时可能退化为链表的问题&#xff0c;从而保持高效的查找、…...

从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(OLED设备层封装)

目录 OLED设备层驱动开发 如何抽象一个OLED 完成OLED的功能 初始化OLED 清空屏幕 刷新屏幕与光标设置1 刷新屏幕与光标设置2 刷新屏幕与光标设置3 绘制一个点 反色 区域化操作 区域置位 区域反色 区域更新 区域清空 测试我们的抽象 整理一下&#xff0c;我们应…...

大模型能力评估数据集都有哪些?

大模型能力的评估数据集种类繁多,涵盖了语言理解、推理、生成、代码能力、安全性和鲁棒性等多个方面。以下是一些主要的评估数据集及其特点: 通用能力评估数据集: MMLU:多模态大规模多语言任务理解数据集,覆盖从基础教育到高级专业水平的57个科目,用于评估模型的知识储备…...

论文阅读(二):理解概率图模型的两个要点:关于推理和学习的知识

1.论文链接&#xff1a;Essentials to Understand Probabilistic Graphical Models: A Tutorial about Inference and Learning 摘要&#xff1a; 本章的目的是为没有概率图形模型背景或没有深入背景的科学家提供一个高级教程。对于更熟悉这些模型的读者&#xff0c;本章将作为…...

《OpenCV》——图像透视转换

图像透视转换简介 在 OpenCV 里&#xff0c;图像透视转换属于重要的几何变换&#xff0c;也被叫做投影变换。下面从原理、实现步骤、相关函数和应用场景几个方面为你详细介绍。 原理 实现步骤 选取对应点&#xff1a;要在源图像和目标图像上分别找出至少四个对应的点。这些对…...

【16届蓝桥杯寒假刷题营】第2期DAY4

【16届蓝桥杯寒假刷题营】第2期DAY4 - 蓝桥云课 问题描述 幼儿园小班的浩楠同学有一个序列 a。 他想知道有多少个整数三元组 (i,j,k) 满足 1≤i,j,k≤n 且 ai​aj​ak​。 输入格式 共2行&#xff0c;第一行一个整数 n&#xff0c;表示序列的长度。 第二行 n 个整数&#x…...

用 HTML、CSS 和 JavaScript 实现抽奖转盘效果

顺序抽奖 前言 这段代码实现了一个简单的抽奖转盘效果。页面上有一个九宫格布局的抽奖区域&#xff0c;周围八个格子分别放置了不同的奖品名称&#xff0c;中间是一个 “开始抽奖” 的按钮。点击按钮后&#xff0c;抽奖区域的格子会快速滚动&#xff0c;颜色不断变化&#xf…...

【人工智能学习笔记 一】 AI分层架构、基本概念分类与产品技术架构

新的一年2025要对AI以及LLM有个强化的学习&#xff0c;所以第一篇先对整体有个大概的认知&#xff0c;一直分不清LLM和AI的关系&#xff0c;在整个体系里的位置&#xff0c;以及AIGC是什么东西&#xff0c;AI AGENT类似豆包等和大语言模型的具体关系是什么&#xff0c;整个AI的…...

windows10 配置使用json server作为图片服务器

步骤1&#xff1a;在vs code中安装json server, npm i -g json-server 注意&#xff1a;需要安装对应版本的json server&#xff0c;不然可能会报错&#xff0c;比如&#xff1a; npm i -g json-server 0.16.3 步骤2&#xff1a;出现如下报错&#xff1a; json-server 不是…...

【Elasticsearch 基础入门】Centos7下Elasticsearch 7.x安装与配置(单机)

Elasticsearch系列文章目录 【Elasticsearch 基础入门】一文带你了解Elasticsearch&#xff01;&#xff01;&#xff01;【Elasticsearch 基础入门】Centos7下Elasticsearch 7.x安装与配置&#xff08;单机&#xff09; 目录 Elasticsearch系列文章目录前言单机模式1. 安装 J…...

【MySQL】语言连接

语言连接 一、下载二、mysql_get_client_info1、函数2、介绍3、示例 三、其他函数1、mysql_init2、mysql_real_connect3、mysql_query4、mysql_store_result5、mysql_free_result6、mysql_num_fields7、mysql_num_rows8、mysql_fetch_fields9、mysql_fetch_row10、mysql_close …...

【零拷贝】

目录 一&#xff1a;了解IO基础概念 二&#xff1a;数据流动的层次结构 三&#xff1a;零拷贝 1.传统IO文件读写 2.mmap 零拷贝技术 3.sendFile 零拷贝技术 一&#xff1a;了解IO基础概念 理解CPU拷贝和DMA拷贝 ​ 我们知道&#xff0c;操作系统对于内存空间&…...

四、GPIO中断实现按键功能

4.1 GPIO简介 输入输出&#xff08;I/O&#xff09;是一个非常重要的概念。I/O泛指所有类型的输入输出端口&#xff0c;包括单向的端口如逻辑门电路的输入输出管脚和双向的GPIO端口。而GPIO&#xff08;General-Purpose Input/Output&#xff09;则是一个常见的术语&#xff0c…...

qt-Quick3D笔记之官方例程Runtimeloader Example运行笔记

qt-Quick3D笔记之官方例程Runtimeloader Example运行笔记 文章目录 qt-Quick3D笔记之官方例程Runtimeloader Example运行笔记1.例程运行效果2.例程缩略图3.项目文件列表4.main.qml5.main.cpp6.CMakeLists.txt 1.例程运行效果 运行该项目需要自己准备一个模型文件 2.例程缩略图…...

IM 即时通讯系统-01-概览

前言 有时候希望有一个 IM 工具&#xff0c;比如日常聊天&#xff0c;或者接受报警信息。 其实主要是工作使用&#xff0c;如果是接收报警等场景&#xff0c;其实DD这种比较符合场景。 那么有没有必要再创造一个DD呢&#xff1f; 答案是如果处于个人的私有化使用&#xff0…...

二叉树——429,515,116

今天继续做关于二叉树层序遍历的相关题目&#xff0c;一共有三道题&#xff0c;思路都借鉴于最基础的二叉树的层序遍历。 LeetCode429.N叉树的层序遍历 这道题不再是二叉树了&#xff0c;变成了N叉树&#xff0c;也就是该树每一个节点的子节点数量不确定&#xff0c;可能为2&a…...

Baklib构建高效协同的基于云的内容中台解决方案

内容概要 随着云计算技术的飞速发展&#xff0c;内容管理的方式也在不断演变。企业面临着如何在数字化转型过程中高效管理和协同处理内容的新挑战。为应对这些挑战&#xff0c;引入基于云的内容中台解决方案显得尤为重要。 Baklib作为创新型解决方案提供商&#xff0c;致力于…...

MP4基础

一、什么是MP4&#xff1f; MP4是一套用于音频、视频信息的压缩编码标准&#xff0c;由国际标准化组织&#xff08;ISO&#xff09;和国际电工委员会&#xff08;IEC&#xff09;下属的“动态图像专家组”&#xff08;Moving Picture Experts Group&#xff0c;即MPEG&#xff…...