(十二)类

1、类的介绍:

       封装:把数值(数据)和函数打包到一个对象中,称封闭。数据和函数分别为成员,类中有数据成员和成员函数。

       隐藏:OOP是根据对象来编程,不是根据组成对象的位来编程,因此隐藏数据从本质上有也有好处。

       继承:基类、继承、派生。根据一个类型定义另一个类型。为类的前进锦上添花。

       多态:总与指针和引用有关。一组继承性相关的类通过基类的指针或引用来操作或传送。

       ------------------------------------------------------------------------------------------------------------------------

        本质上类,就是一种数据类型。如同结构一样内部为成员:数据成员,成员函数(有时称方法)。由类定义的变量称对象,亦称类的实例、实例化。

       默认情况下:类的成员是私有的,而结构的成员是公共的。这就是为什么一般可以访问结构的成员而不能访问类的成员的原因


2、类如结构类型一样,可以用花括号进行赋值。仍按在公用成员中依次赋值。(但类中不能再有构造函数,否则会出现重定义错误)


3、构造函数。目的:确保对象的所有数据成员都设置为合适的值。

      类的构造函数可有多个(重载),只要有一个,其对象不能用花括号进行初始化(会提示重定义错误)。

      类内定义的函数(包括构造函数)若有代码,隐式声明为内联函数(但不一定都实现为内联函数,由编译器根据函数特性决定),类外可以显示加上inline来显示声明为内联函数。

      若没有显示声明构造函数,编译器会提供一个默认的构造函数,用于创建类的对象。默认构造函数没有参数。显示的构造函数可以没有参数,也可有参数,有参数时还可以指定默认的值。注意,无参数和有参(带默认值)应注意不要发生错误。

      如两个构造函数:Box(); 

                                      Box(double x=1.0,double y=1.0);//注意默认值在头文件中声明中写出,实现cpp中不能再写(否则会提示出错)

                  这两个构造会提示候选出错,因为,当不带参数时,这两个构造函数都符合条件,编译器无法给出旗帜鲜明有答案,只有你自己去修改决定。


4、默认构造函数,在调用时不须带参数,也可以不带括号(最常见,如:Box  firstBox; //此时就不带括号)

      默认构造函数缺点:不能初始化非类类型的数据成员

     其实代码初始化时(CPP实现中)有两种:

                一、用纯代码来进行初始化;

               二、用初始化列表初始化;(加冒号,紧跟参数及括号(括号内为值),参数间用逗号分隔)

              如:Box::Box(double x,double y):x(1.0),y(3.2){}

      用一:首先创建数据成员,再执行赋值语句;用二:数据成员创建时就直接初始化,二比一更高效,特别是数据成员是类实例(对象)时。


5、显式声明:Explicit     (用于阻止隐式转换,只能显式地使用)

      当构造函数只有一个参数时,最易发生隐式转换,为了阻止这情况,必要时可显示声明。

      class cube{ public:

                                double side;

                                cube{double side};

                                double volume(cube  a);

                            };

        cube  A(3.0),B=3.0;  //这里,3.0会隐匿地转换到cube类型对象,不会出错。

        cout<<A.volume(5.3)<<endl;//同样,这里5.3会隐式地转换到cuble类型对象进行调用。

       要想避免,可以上面构造函数前加上 explicit即:

                                 explicit  cube(double side);//这样不能隐匿的转换了,上面的两个就会出错,只有显示地用cube对象进行赋值才不会出错。


6、访问限定符:public,private,protected

      其作用范围为下一个访问限定符出现之前,若一直没出现,则一直是该访问限定。

     类的公共成员一般为函数,又称“类的接口”。

     无论成员函数定义在什么地方,都可以在类中访问全部成员,当然成员函数也可放在私有中(当本功能实现公有中某功能,但又不对外开放时,由公有成员函数进行调用实现。

     

7、实例化一个对象时,必须要一个构造函数,哪怕是声明。如:Box  firstBox;//如果类中没有匹配的构造函数,将会出错。


8、accessor函数(访问函数):私有数据成员常不可见,可用仅有成员函数进行访问以显示它,这类函数称访问函数(accessor函数)

      mutator函数(设置函数):同样,因不可见,不易改变,可以通过公有成员函数进行修改其值,这类函数称设置函数(mutator函数)

     在vb.net中,构造类时,常 用set来代替mutator函数,用get来代替accessor函数。


9、副本构造函数,又称“拷贝构造函数、复制构造函数”

     当用一个已有对象去新创建另一个新的完全一样的对象时,就会用到拷贝构造函数。

     拷贝构造函数没有显示定义时编译器会有一个默认的拷贝构造函数,它会复制已有对象中的“每个成员”,以创建“新的对象”。

     创建新的对象时不必显示调用拷贝构造函数,只须把对象作参数“按值”传送“给函数即可。它适用于”简单的类“,复杂的须要手工进行定制拷贝构造函数,否则会出现严重错误。在第13章讨论。

     与构造函数的区别:两个都有默认的函数,都有本身的限制,都可以手工定制。

                     构造函数是一般是对象有了,进行赋值,拷贝构造函数一般是另一个对象没有,用已有的对象创建一个新的一样的对象。


10、友元:顾名思意,就是来访朋友的成员,它当然可以访问本类中的成员,只不过访问有点特殊,须通过该类的对象进行引用。

        友元其实不是本类的成员,它的到来,允许它访问类对象中非公共的成员,就象是类的成员一样。

        友元可分两类:

           一、函数为友元。注意函数可以是全局函数,也可以是另一个类中的函数;

           二、另一个类是本类的友元。同函数友元一样也可以访问本类。

       友元可以访问“类对象”的任意成员,无论这些成员的访问指定符是什么。

      声明时,可将friend加在到访问类的函数上(表明这是一个外面来的朋友函数:友元),具体另一处的本体定义不用加上friend。

      class A{public:

                            friend  void show();

                            ..........

                     };

       void show() {...........}   //不用类的限定,但如果是另一个类的函数是友元,其前有限定符,但限定符是另一个类的,而不是本类的。


11、友元函数:全局友元函数,类成员友元函数。

       由于友元并不是本类的成员,所以不能用本类的访问指定符进行限定。当函数需要访问两个不同对象的内部时,可把类中成员指定为另一个类中的友元。

      同样,由于友元并不本类的成员,不可以直接引用本类中成员,必须用本类的对象名来限定。

       友元也是类的接口。


12、友元类:整个类是另一个类的友元。同样友元类的所有成员函数都可以不受限制地访问原类的成员。

        class A {public:

                                friend class B;//B为A的友元。

                               ............

                      };

         注意:友元是单向的且不传递。即B是A的友元,但A不是B的友元,说明友元不是互惠的,只有单向。

                    同样:A是B的友元,B是C的友元,但是类A却不是C的友元,即友元不具有传递性。

         友元类,常用于链表


13、强大的this指针:   在执行任何类成员函数时,该函数都会自动包含一个隐藏的指针,即this

        尽管我们访问本类成员时不用指针什么,但实际上它通过对象的this指针来指针本对象所属的成员。只不过它一般为隐式。

        用得较多的如:成员函数的参数与类中数据成员同名,比如都是a,这个时候可用this->a来限定是类中数据成员而不是参数。

        还有就是返回值。比如:Box*  p=new Box(1,3,4);

                                                   p->setheight(3)->setwidth(5);//这里只须将设置函数的返回值为this指针,就可这样连续设置值了。


14、this同样只能说明这个对象的指针,并不能说明这个对象相关成员的性质。因此:

         class Box{public:

                                  double volume(){return length*width*height;}  //返回体积

                                  int comparevolume(const Box& otherBox){return (volume()>otherBox.volume())?1:0;}  // 这里会出错

                                  ..........

                            };

        出错原因:声明为const说明不能修改它,otherBox通过this指针传送给函数,但并不能保证函数不能修改这个对象(因为this没有const也不能有关于const的信息),因此错误消息是“不能转换this指针”,编译器在默认情况下是不能改变对象的const性质。所以要人为进行修改,把成员函数volume()后加const,这样保证了*this指向的对象是const性质,与函数参数一致。

        注意在声明和实现中都必须把对应的函数后面加上const,这与inline的声明与实现有点区别。

         所以为了更大范围地使用成员函数,一般后面都加上const.

        为什么要如此呢?

         例如: 当一个对象A 以引用的方式或指针的方式传入了多个函数 如果都没有const保护, 那么一旦出错 根本不知道哪个函数改变了A的数据。怎么办呢?如果在不需要改变A的数据的函数参数前 加上const, 那么就能确定在这个函数中不会改变A的数据, 对于排错非常有用。当然const还有其它好处。



15、const对象、const成员函数。

         本质:const对象:它只能调用const成员(数据或函数),若调用不是const的成员,将会出错。就是14所说那样。

                     const成员函数:不能修改“类对象”的数据成员,即在其代码内数据成员不能作为左值。

          如果在const成员函数中修改本类的数据成员,将出错提示”只读“:[Error]  error: assignment of data-member `Box::length' in read-only structure

          即使是通过this指针进行调用也一样。因此,两个成员函数完全一样,仅后面const不一样时,它表明是两个函数,是可以进行重载的。

         如果const对象调用非const成员函数会提示:error: passing `const Box' as `this' argument of `double Box::volume()' discards qualifiers,意思是丢失类型限定,因为常量对象传给函数使用this指针(默认)来指定,但this没有办法把const这个性质带过来,所以提示这个错误。

       

16、豁免权:mutable数据成员(可变数据成员)

       由于const对象只能调用const成员函数,const成员函数中不能修改数据成员,但有时必须修改,为此产生一个专门的豁免权的mutable性质,它来说明const成员函数中的数据成员是可以修改的(不必只读)。比如远端服务传到本地数据,其数据只读,有时要更新这个缓冲区,即使它声明是const,这时就要用到mutable.

       class  A{ public:    bool islocked() const { time=getcurrentTime(); //常成员函数,一般是不能修改数据成员的,但因后面有mutable限定,可作左值

                                                                                 return lockstate();

                                                                               }

                                        .............

                         private:   mutable int time;//指定当为const时是可以修改这个数据成员的,否则会提示”只读“

                                        ............

                       };

        

17、常量的强制转换:目的:只是为了参数的匹配

         const_cast<类型>(表达式)    //注意,这个”表达式“结果类型只能是常量const

        强制把const转换为其它类型,只是为了匹配参数的一致,或其它使用情况,使用它应确保不能破坏原常量的性质(修改),即使修改也是无效的。                                    

void changeconst(const int* p){
	int* newp=const_cast<int*>(p);//强制把const属性去掉 
	*newp=150;   //新的指针试图去修改 
}
int main(int argc, char *argv[]){
	const int a=30;
	changeconst(&a);
	cout<<a<<endl;//结果还是30,并不是150,因为它“固执地认定为30。修改无效 
	return 0;
}
          可以看到想通过取巧的方式去修改原参数,结果修改无效。


18、对象数组:同其它一样,可以设置对象的数组,如:Box  boxes[10];//然后单独引用每个元素如同单个对象一样

        注意:每个数组元素都会调用构造函数一次。


19、边界对齐:有些机器因性能原因,两字节的变量必须放在2的倍数的地址中,同样4字节变量必须放在4的位置的地址中,同理8字节。。这样不同值在内存中留下一定的空隙。比如:在类中有两个数据成员:int a; double b;  //int占4字节, double占8字节,int占后,不满足8的倍数,须空出4字节,再紧跟8字节,这样实际上就占用了16字节,而不是12字节。也许你会说,int和double换个位置不就行了?不行!!!!因为类还要为对象考虑数组的情况(对象数组),对象数组要求每个对象都放在8字节的倍数的地址上。

        注意:类对象一般只考虑类中数据成员的大小(注意,它不会计算静态成员数据,因为静态数据不属于对象,它只属于类)。

                    不管是成员函数,还是非成员函数,静态的和非静态的,函数不会因为对象的多少而存在不同数量的副本,也就是,代码只有一份。

         数据成员却因对象的不同有对应的副本,即有3个对象即3个副本。静态数据成员只有一个副本。


                                        

20、特权成员:静态成员:它只记录类的属性,不是记录各对象的属性。因此它独立于任何对象,只属于类,不属于对象(从计算对象大小可以看出,没有静态成员的大小)

        类对象的大小不包含静态数据成员的大小;在创建类对象前静态成员已经存在(初始化)并可调用;

        非静态函数成员都有this指针,指向调用函数的当前对象,但静态函数成员不包含this指针。

       声明须在类内中前加static,定义则必须在类外(或类的实现中),且前面不能再加static(否则将被认定为另一个全局变量)

       注意:因静态数据成员不属于对象,所以对于常量成员函数,它是可以修改静态数据成员的(其它的除mutable外是不能修改的)

                  同样为了定位一个可以比较的标准,也可以把静态数据成员设定为常量:const static int a;//这样就不可修改它。


21、静态成员函数:只须在成员函数前加static即可定义。

         因为静态成员函数是没有this指针,因此,它是不能声明为const(const的实质是不能修改对应对象的数据成员),所以同样它也不能访问调用它的对象。

        另外静态成员中参数如果是对象,这个对象在静态函数中是可以任意访问私有和公有成员的,但这没意义,一般都是通过对象的成员函数进行访问私有。

        class A{public:   static int show(A&  d){ return d.a;}  //注意这里a是对象d的私有。因为静态是可这样用的

                       private:   int a,b;

                      };

         A *p=new A(3,4);

         cout<<p->show(*p)<<endl; //可以用指针

          尽管静态成员是没有this指针的,但对象的指针仍然可以访问。或者直接用对象加点号来引用它们。

          同样静态成员不属于特定对象和this指针,因此不能用它“直接”访问调用它的对象,只能用传递参数的形式,如上面的例子。

         为什么静态成员函数中的对象可以访问私有呢?因为静态函数是属于类的,类中的成员是可以任意访问其它成员(仅类外对象可访问公有),于是它就赋予了函数内代码全权访问权限。一句话,静态成员函数具有全部访问权限。再换句通俗的解释,因为静态函数是属于类,它是类的成员,因此它是可以访问类的任意的成员(包括私有),但是因为这个是静态函数,它不能明确到到底是哪个对象的东西,前面加上对象是限定指明这个成员(私有成员)是那一个对象的,而不是别的。如果你不指明,静态成员就没法明确了。





为了实现猫十二,可以使用Python和深度学习框架TensorFlow。 首先,需要收集带有标签的猫的图像数据集。可以使用公共的数据集,例如ImageNet,也可以自己手动收集和标记数据集。 其次,需要将数据集分为训练集、验证集和测试集。训练集用于训练模型,验证集用于调整模型超参数和防止过拟合,测试集用于评估模型效果。 接下来,需要对图像数据进行预处理,例如缩放、裁剪、旋转、翻转等。这有助于提高模型的准确性和鲁棒性。 然后,可以使用卷积神经网络(CNN)来构建模型。可以使用现有的CNN模型,例如ResNet、VGG等,也可以自己构建CNN模型。 最后,可以使用训练集对模型进行训练,并使用验证集进行调整和评估。最终,可以使用测试集对模型进行最终评估。 以下是一个简单的Python代码示例,用于实现猫十二: ```python import tensorflow as tf from tensorflow.keras.preprocessing.image import ImageDataGenerator # 定义数据生成器 train_datagen = ImageDataGenerator(rescale=1./255) val_datagen = ImageDataGenerator(rescale=1./255) test_datagen = ImageDataGenerator(rescale=1./255) # 加载数据集 train_dataset = train_datagen.flow_from_directory( 'train/', target_size=(180, 180), batch_size=32, class_mode='categorical') val_dataset = val_datagen.flow_from_directory( 'val/', target_size=(180, 180), batch_size=32, class_mode='categorical') test_dataset = test_datagen.flow_from_directory( 'test/', target_size=(180, 180), batch_size=32, class_mode='categorical') # 定义模型 model = tf.keras.Sequential([ layers.Conv2D(32, (3, 3), activation='relu', input_shape=(180, 180, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Conv2D(128, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Conv2D(128, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(512, activation='relu'), layers.Dense(12, activation='softmax') ]) # 编译模型 model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) # 训练模型 model.fit(train_dataset, epochs=10, validation_data=val_dataset) # 评估模型 test_loss, test_acc = model.evaluate(test_dataset) print('Test accuracy:', test_acc) ``` 其中,代码中的`train/`、`val/`和`test/`目录分别包含训练集、验证集和测试集的图像数据集。模型使用了包含四个卷积层和两个全连接层的CNN结构。在训练期间,使用了Adam优化器和交叉熵损失函数。模型在训练集上进行了10个时期的训练,并在测试集上进行了评估。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值