2022.3.22
类成员,类的组合,友元
-
常对象只能调用它的常成员函数,而不能调用普通的成员
#include<iostream> //例3.40-2 using namespace std; class Sample{ private: int n; public: int m; Sample(int i,int j){ m=i; n=j; } void setvalue(int i){ n=i; } void disply() {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; } }; int main() {const Sample a(10,20); a.setvalue(40); a.m=30; a.disply(); return 0;}
所以setvalue()方法不能使用
同时,常对象即便是public数据成员也不能直接修改,
-
常函数的说明格式:
类型说明符 函数名(参数表)const;
声明带const , 使用不需要,同时,常成员函数不能更新对象的数据成员的值
#include<iostream> //例3.40-2 using namespace std; class Sample{ private: int n; public: int m; Sample(int i,int j){ m=i; n=j; } void setvalue(int i){ n=i; } void disply() const {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; } }; int main() {const Sample a(10,20); a.disply(); return 0;}
#include<iostream> //例3.40-2 using namespace std; class Sample{ private: int n; public: int m; Sample(int i,int j){ m=i; n=j; } //void setvalue(int i){ n=i; } void disply() ; void disply() const ; }; void Sample::disply() { cout<<"another"; } void Sample::disply() const {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; } int main() {const Sample a(10,20); a.disply(); Sample b (20,30); b.disply(); return 0;}
注意下,class最后花括号后面要加个分号
-
使用const说明的数据成员称为常数据成员。
如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化, 而不能采用在函数中直接赋值的方法#include<iostream> //例3.40-2 using namespace std; class Sample{ private: const int n,a,b; public: const int m; //void setvalue(int i){ n=i; } Sample(int n,int a,int b,int m); void disply() ; void disply() const ; }; Sample::Sample(int n, int a, int b, int m):n(n),a(a),b(b),m(m) {} void Sample::disply() { cout<<"another"; } void Sample::disply() const {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; } int main() {const Sample a(10,20,30,40); a.disply(); Sample b (10,90,20,30); b.disply(); return 0;}
才发现,c++参数列表貌似就是java中this.a=a这个操作,像下面三种,第一种完全达到目的,第二种第三种都得到垃圾值
#include<iostream> //例3.40-2 using namespace std; class Sample{ private: const int n,a,b; public: const int m; //void setvalue(int i){ n=i; } Sample(int n,int a,int b,int m); void disply() ; void disply() const ; }; Sample::Sample(int n, int a, int b, int m):n(n),a(a),b(b),m(m) {} void Sample::disply() { cout<<"another"; } void Sample::disply() const {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; } int main() {const Sample a(10,20,30,40); a.disply(); Sample b (10,90,20,30); b.disply(); return 0;}
#include<iostream> //例3.40-2 using namespace std; class Sample{ private: const int n,a,b; public: const int m; //void setvalue(int i){ n=i; } Sample(int n,int a,int b,int m); void disply() ; void disply() const ; }; Sample::Sample(int n, int a, int b, int m) { n=n; m=m; a=a; b=b; } void Sample::disply() { cout<<"another"; } void Sample::disply() const {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; } int main() {const Sample a(10,20,30,40); a.disply(); Sample b (10,90,20,30); b.disply(); return 0;}
#include<iostream> //例3.40-2 using namespace std; class Sample{ private: int n,a,b; public: int m; //void setvalue(int i){ n=i; } Sample(int n,int a,int b,int m); void disply() ; void disply() const ; }; Sample::Sample(int n, int a, int b, int m) { n=n; m=m; } void Sample::disply() { cout<<"another"; } void Sample::disply() const {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; } int main() {const Sample a(10,20,30,40); a.disply(); Sample b (10,90,20,30); b.disply(); return 0;}
-
总结一下,
普通成员函数 常成员函数 普通数据成员 可以访问,也可以改变值 可以访问,但不可以改变值 常数据成员 可以访问,但不可以改变值 可以访问,但不可以改变值 常对象的数据成员 不允许访问和改变值 可以访问,但不可以改变值 -
定义静态数据成员的格式如下:
static 数据类型 数据成员名;
#include<iostream> //例3.40-2 using namespace std; class Sample{ static int c; private: int n,a,b; public: int m; //void setvalue(int i){ n=i; } Sample(int n,int a,int b,int m); void disply() ; void disply() const ; }; int Sample:: c =79898; Sample::Sample(int n, int a, int b, int m):n(n),a(a),b(b),m(m) {} void Sample::disply() { cout<<"another"; } void Sample::disply() const {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; cout<<"c="<<c<<endl; } int main() {const Sample a(10,20,30,40); a.disply(); Sample b (10,90,20,30); b.disply(); return 0;}
-
静态数据成员的初始化要在main()函数之前,类定义之外
-
值得注意,下面这种写法没什么问题(我个人觉得挺离谱的),因为对象的声明的
#include<iostream> //例3.40-2 using namespace std; class Sample{ private: int n,a,b; public: static int c; int m; //void setvalue(int i){ n=i; } Sample(int n,int a,int b,int m); void disply() ; void disply() const ; }; Sample::Sample(int n, int a, int b, int m):n(n),a(a),b(b),m(m) {} void Sample::disply() { cout<<"another"; } void Sample::disply() const {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; cout<<"c="<<c<<endl; } int Sample:: c =79898; const Sample a(10,20,30,40); int main() { cout<<Sample::c; a.disply(); Sample b (10,90,20,30); b.disply(); return 0; }
#include<iostream> //例3.40-2 using namespace std; class Sample{ private: int n,a,b; public: static int c; int m; //void setvalue(int i){ n=i; } Sample(int n,int a,int b,int m); void disply() ; void disply() const ; }; Sample::Sample(int n, int a, int b, int m):n(n),a(a),b(b),m(m) {} void Sample::disply() { cout<<"another"; } void Sample::disply() const {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; cout<<"c="<<c<<endl; } int Sample:: c =7000000; const Sample a(10,20,30,40); int main() { Sample:: c =79898; cout<<Sample::c; a.disply(); Sample b (10,90,20,30); b.disply(); return 0; }
我稍微修改了下,静态数据成员能这么写估计是因为public了,所以声明对象以后又将c的值修改了下,不过这种延后感觉有点不太协调.
-
若静态成员函数需要访问非静态成员,静态成员函数只能通过对象名(对象指针或引用)访问该对象的非静态成员
这个其实挺好理解的,因为静态成员函数在对象声明前存在,所以必须得有引用或者指针或者对象才可以调用函数.(注意下,对象我指的是直接在函数里初始化得到的一个,而指针引用我指的其实是在形参列表处的)
-
如何给组合类中的对象成员初始化
class X { 类名1 对象成员名1; 类名2 对象成员名2; … 类名n 对象成员名n; };
X∷X(参数表0):对象成员名1(参数表1),对象成员名2 (参数表2),…,对象成员名n(参数表n) { 类X的构造函数体 }
表现形式
class A { //.. .. }; class B { //.. .. }; class C { //.. .. }; class D { A a; B b; C c; public: D(参数表0):a(参数表1),b(参数表2),c(参数表3) { // …构造函数体 } } ;
-
如何用友元函数
#include<iostream> //例3.40-2 using namespace std; class Sample{ private: int n,a,b; public: static int c; int m; //void setvalue(int i){ n=i; } Sample(int n,int a,int b,int m); void disply() ; void disply() const ; friend void fun(Sample &w); }; Sample::Sample(int n, int a, int b, int m):n(n),a(a),b(b),m(m) {} void fun(Sample & w){ cout<<w.c; } void Sample::disply() { cout<<"another"; } void Sample::disply() const {cout<<"m="<<m<<endl; cout<<"n="<<n<<endl; cout<<"c="<<c<<endl; } int Sample:: c =79898; int main() { Sample b (10,90,20,30); fun(b); return 0; }
注意下,友元函数不能用const变成常成员函数,也就是友元函数不能处理常对象
友元函数最大的特点就是,不需要对象或者类的格式就可以直接使用,比较像c语言,至于破坏封装,我其实我现在还没感觉到
一个函数可以是多个类的友元函数。当一个函数需要访问多个类时,友元函数非常有用,不过这样会非常混乱,改bug可以玩死你,修改个数据也会死的挺惨的
-
将其它类成员函数声明为本类友元函数
一个类的成员函数也可以作为另一个类的友元,这种成员函数称为友元成员函数。
定义友元成员函数目的:使两个或多个类相互合作、协调工作,完成某一任务。成员函数可以访问自己类的对象,不需要引用,然后访问别的类的对象时,则需要
-
友元类的说明方法是在另一个类声明中加入语句:
friend 类名;
class Y { … }; class X { … friend Y; … };
友元类的一个特点就是Y中的所有函数对于X都是友元函数
-
string类的直接复制是深拷贝,复制的字符串修改并不影响前面被拷贝的字符串
#include <string> #include <iostream> using namespace std; int main(){ std::string a ="abcd"; std::string b =a; cout<<&a<<endl; cout<<&b<<endl; std::cout<<a<<std::endl; cout<<b<<endl; b[0]='h'; cout<<&a<<endl; cout<<&b<<endl; cout<<a<<endl; cout<<b<<endl; }
-
一段有意思的代码
#include <string> #include <iostream> using namespace std; class A { private: int a; public: A(int a); void display(); }; A::A(int a) :a(a){ cout<<"first"<<endl; } void A::display() { cout<<a; } int main(){ A a =10; A b = a; a.display(); b.display(); return 0; }
2022.4.12
访问规则
- 声明为什么的派生类以后,当派生类继承父类的属性和方法以后,会将它高出派生类访问权限的属性和方法变为其声明的访问权限
- 私有成员与方法派生类不能直接访问,只能通过方法进行访问
派生类的构造函数和析构函数
- 派生类名(参数总表):基类名(参数表0),对象成员名1(参数表1),…,对象成员名n (参数表n)
{
//派生类新增成员的初始化语句 …
}
调整基类成员在派生类中的访问属性
-
class X{ public: int f() }; class Y:public X{ public: int f(); int g(); }; void Y::g( ){ f(); } void main( ){ Y obj; obj.f(); }
下面是我模拟的正确的写法
#include <bits/stdc++.h> using namespace std; class X{ public: int first(); }; class one :public X{ public: int first(){}; void fun(); }; void one::fun() { first(); } int main(){ one a; a.fun(); return 0; }
-
如何访问父类被覆盖的成员
#include <bits/stdc++.h> using namespace std; class X{ public: int first(){ cout << "X" << endl; }; int x=9; }; class one : public X{ public: int first(){ cout << "one" << endl; cout << X::x << endl; }; void fun(); int x = 10; }; void one::fun() { X::first(); first(); } int main(){ one a; a.fun(); return 0; }
-
访问声明
#include<iostream> using namespace std; class A{ int x; public: A(int x1){x=x1;} void print( ){cout<<"x="<<x;} }; class B: private A{ int y; public: B(int x1,int y1):A(x1){y=y1;} A::print;//(没有括号) }; int main( ) { B b(10,20); b.print( ); return 0; }
-
诡异的写法
#include<iostream> using namespace std; class A{ int x; public: A(int x1){x=x1;} void print( ){cout<<"x="<<x;} void print(int a = 0) { cout << "a=" << a; } }; int main( ) { A b(10); b.print(); return 0; }
这种写法会导致第一个第一个print无法访问,必须传入一个参数(因为不传参数会产生二义性)
多重继承
-
使用成员名限定可以消除二义性,例如:
obj.X∷f();//调用类X的f() obj.Y∷f();//调用类Y的f()
-
多重继承的构造器
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表) { 派生类新增成员的初始化语句 }
-
多个基类构造函数的执行顺序,则严格按照派生类声明时从左到右的排列顺序来执行。
#include<iostream> // 练习题 using namespace std; class B1{ public: B1(){cout<<"constructor B1."<<endl;}}; class B2{ public: B2(){cout<<"constructor B2."<<endl;}}; class A :public B2, public B1{ public: A(){cout<<"constructor A."<<endl;}}; int main() { A aa; return 0; } //输
问题
-
#include<iostream> using namespace std; class base { int x; public: base(int i){ x=i; cout<<"基类的构造函数\n";} ~base( ) { cout<<"基类的析构函数\n"; } void show( ){ cout<<" x=" <<x<<endl;} }; class derived:public base { base d; //d为基类对象,作为派生类的对象成员 public: derived(int i):base(i),d(i) { cout<<"派生类的构造函数\n"; } ~derived( ){ cout<<"派生类的析构函数\n"; } }; int main( ) { derived obj(5); obj.show( ); return 0; }
为什么结果是这个
基类的构造函数 基类的构造函数 派生类的构造函数 x=5 派生类的析构函数 基类的析构函数 基类的析构函数
因为这里d对象又进行了一次调用构造函数
2022.4.15
虚基类
-
个人对虚基类的理解,它其实有点像java中的单继承机制,因为我看了看,正常的c++多继承会导致有多个可能同名的不同含义的a出现,而在java中没有出现这个问题最主要的一个原因也是单继承保证了成员变量的意义
-
我改造了下老师给的代码
#include<iostream> //例4.17-1 using namespace std; class B { public: int a; B(int sa) { a=sa; cout<<"Constructing B"<<endl; } }; class B1:virtual public B{ int b; public: B1(int sa,int sb):B(sa) { a += 20; b=sb; cout<<"Constructing B1"<<endl; } }; class B2:virtual public B{ int c; public: B2(int sa,int sc):B(sa) { a += 10; c=sc; cout<<"Constructing B2"<<endl; } }; class D:public B1,public B2 { int d; public: D(int sa,int sb,int sc,int sd): B(sa),B1(sa,sb),B2(sa,sc) { d=sd; cout<<"Constructing D"<<endl; cout << a << endl; cout << B1::a << endl; cout << B2::a << endl; } }; int main() { D obj(2,4,6,8); return 0; }
通过这个可以看出,我们没有用即便是使用了B1,B2的空间,内存中也只有一个a,而不是在不同空间下不同的a,这和我之前想的其实还是有点不一样的,我最开始想的其实是不同空间下的a还是保留有他的意义的
-
不过还是有点问题的 ,也就是个人感觉其实不同空间下的东西其实还是不一样的,因为这里空间占用说不了慌
#include<iostream> //例4.17-1 using namespace std; class B { public: int a; B(int sa) { a=sa; cout<<"Constructing B"<<endl; } }; class B1:virtual public B{ int b; public: B1(int sa,int sb):B(sa) { a += 20; b=sb; cout<<"Constructing B1"<<endl;} }; class B2:virtual public B{ int c; public: B2(int sa,int sc):B(sa) { a += 10; c=sc; cout<<"Constructing B2"<<endl; } }; class D:public B1,public B2 { int d; public: D(int sa,int sb,int sc,int sd): B(sa),B1(sa,sb),B2(sa,sc) { d=sd; cout<<"Constructing D"<<endl; cout << a << endl; cout << B1::a << endl; cout << B2::a << endl; } }; int main() { D obj(2,4,6,8); cout << sizeof(B) << endl; cout << sizeof(B1) << endl; cout << sizeof(B2) << endl; cout << sizeof(D); return 0; }
-
如果在虚基类中定义有带形参的构造函数,并且没有定义默认形式的构造函数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员
-
虚基类构造函数的调用顺序:
若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数;
对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下;
对于非虚基类,构造函数的执行顺序仍是先左后右,自上而下;
基类与派生类对象之间的赋值兼容关系
-
感觉这个挺原始的,其实就是将数据成员赋给基类而已,不过是公有的派生类才有那个用处,私有派生会导致范围变小,进而数据不可以通过默认的赋值构造方法进行赋值,下面给个例子
#include<iostream> //例4.19-1 using namespace std; class Base{ public: int i; Base(int x) { i=x; } void show(){ cout<<"Base "<<i<<endl; }}; class Derived:public Base{ public: Derived(int x):Base(x){ }; void show() { cout<<"Derived "<<i<<endl;} }; int main() { Base b1(11); b1.show();//① Derived d1(22); b1=d1; b1.show(); //② Derived d2(33); Base& b2=d2; b2.show();//③ Derived d3(44); Base* b3=&d3; b3->show();//④ d3.show(); return 0; }
在这段代码中,子类的方法仍然并没有赋值给基类