C++类相关知识总结

1、类和对象
类就是对对象的描述,主要从属性和行为两个方面描述。
对于属性一般作成private , 行为作为public
函数 (1)构造函数,初始化所有的成员变量,系统自动调用,可以重载
(2)析构函数,在对象生命周期结束的时候自动被调用调用,不准重载
构造函数和析构函数都是系统自动调用的,析构函数可以通过对象调用
A a;
a.A(); //error 构造函数是不能手工调用的
a.~A(); //right 手工调用析构函数时,会被当作一个普通的成员函数调用,其中的代码会被执行,对象不被销毁
(3)get,set方法 用于访问私有的成员变量的,外界访问变量的唯一通道
(4)类本身的行为 是我们在编码时要下功夫的地方
2、类的组成
(1)数据部分
(2)构造函数和析构函数
(3)get & set方法
(4)业务方法

3、栈的实现(存储int类型的栈)
要想实现,需要一个数组做容器,保存用户插入的数据,用指针实现; int *p;
还需要一个变量,保存栈顶位置 int top; //top从0开始
对于栈不能无限制的存数据,所以需要一个int类型的变量来记载数组的最大长度 int max;
(1)构造函数
当不给参数的时候,栈的长度是默认值Stack();也可以用户指定长度Stack(int)
(2)析构函数
一定要有,因为要在堆中申请空间
(3)为保护栈数据的安全,对p不能提供get,set 方法
对于top,max的值,取决于插入的数据,所有只能有get方法,不能设置,不需要有set方法
(4)int push(int); 插入,当栈满的时候,返回-1,表示插入失败
int pop(); 删除栈顶的元素,如果正确删除一个数据,返回数据的值,若没有正确删除,返回-1,表示栈空了
void disp(); 现实数据
(5)实现的时候,注意:
在函数名前加 " Stack:: "
(6)在栈中插入数据,就是在top的位置插入数据
删除数据,就是把栈顶下移一个位置

4、继承
(1)继承表现一种" is a " 的关系。Teacher extend Person
其中Teacher是子类,Person是父类
子类继承父类的所有属性和行为
(2)class Teacher:public Person{};
表示Teacher类继承Person类

在extends_sample2中,子类可以直接使用父类的teach函数,但是不能使用父类private的name属性。name属性实际上存在在子类中,但是不能被子类直接使用,因为是private属性。private属性只能被本对象的函数访问,public的属性可以被子类直接使用,但是外部函数也可以访问public权限的属性。既想被子类使用又不想让外部函数访问,怎么办哪?使用protected修饰属性就可以了。

 下面是一个类的属性具有不同的访问控制修饰符在不同的对象中的可见性i(可见性就是可以访问):    
             本对象  子类对象  其他函数 
 private属性      可见    不可见     不可见 
 protected属性    可见     可见      不可见 
 public属性       可见     可见       可见 
在继承关键字extends前面也有三种不同的访问控制修饰符号,被不同的继承方式继承后访问控制权限会发生变化。可以把继承方式理解成城门。无论外边的人多么胖,想通过不同宽度的城门只能减肥到相应的宽度才可以。

                  public extends       protected extends       private extends 
父类的private属性       不能访问               不能访问                  不能访问 
父类的protected属性  变成protected                 不变             变成private,子类可以访问,子类的子类不能访问 
父类的public属性          不变               变成protected          变成private,子类可以访问,子类的子类不能访问 
构造函数不能被继承。因为构造函数只是适合于本类的产生方式。

如extends_sample4,创建子类的时候需要首先创建父类。怎么理解哪?
考虑子类的构成,就像cs中的匪徒用的46是由ak47添加上瞄准镜组成的。

创建子类的时候会首先调用父类的构造函数,因为继承的时候没有指定继承时使用的父类的构造函数。
构造函数有很多种,因为没有指定构造函数,就会默认使用无参的构造函数。如果父类没有无参的构造函数,那么就会出现编译错误。

这是问题的产生,如何解决哪?
可以在父类中添加无参构造函数。如果我们不是父类的设计者,就应该在子类继承的时候指定使用父类的那个构造函数。
如在写子类构造函数时,使用这种形式Teacher(char* name, int age, double salary):Person(name,age){......},就可以指定使用父类的有参构造函数。

构造时候是先父类后子类。析构函数哪?析构函数不能被继承。子类释放的时候,首先调用自身的析构函数,再调用父类的析构函数。这与构造函数的调用顺序相反。

5、在子类中可以修改父类的行为,叫方法的覆盖
(1)在子类中的函数名必须与父类的一样
(2)隐藏,无法产生多态
class Base{
public:
void fn( int a ){
cout<<"Base a = " << a << endl;
}
};

    class Sub:public Base{
    public:
          void fn(  ){
	            cout<<"Sub b = 20" << endl;
          }
    };
(3)调用父类的方法
      a.Base::fn(10);  //可以有针对性的调用父类的函数

6、函数的高内聚,低耦合
高内聚:函数的功能尽量单一,这样的代码可维护性高
低耦合:避免修改函数的时候,产生连锁反映。

7、多态
(1)什么是多态?
一个int类型的指针,只能指向一个int类型的变量
对于对象来讲Person类型的指针 ,能指向Person类型的对象
Person类型的指针能指向一个Teacher(extend Person)类型的对象

(2)多态的特征:
父类的指针可以指向子类的对象
通过父类指针只能调用父类中声明的方法
通过指针调用函数的时候,若函数是个虚函数,表现出来的行为是对象自身的行为

    Person *p = new Teacher ;
    编译时类型     运行时类型

(3)子类的指针不能指向父类对象
因为子类中会有子类特有的函数,运行时不能通过指针调用子类特有的函数

(4)" virtual "父类函数的返回值前加此关键字,则为虚函数

(5)产生多态的必要前提:
继承,方法覆盖,虚函数

8、虚函数的实现原理
在每个对象中都有一个虚函数列表的指针,虚函数列表是一个栈。
在构造对象时,根据先构造父类,再构造子类的原则,父类的函数先入栈,在子类中覆盖的函数放在上面。
等对象构造完毕时,子类的函数在最上面,根据栈后进先出的原则,先调用的是子类函数

9、在释放资源的时候
delete p ;
只会调用Person类的析构函数,因为指针的类型是Person的,这样会造成子类的空间得不到及时的释放,会造成内存泄露
把析构函数也写成虚函数,这样就会先调用子类的析构函数,再析构父类的析构函数

在继承关系中,父类的析构函数必须是虚函数!!!

10、多态的使用

 #include <iostream>
 using namespace std;
		
		class Person{
		public:
			virtual double buy(){          
				return 2 ;
			} 
		};
		
		class Teacher : public Person{
		public:
			virtual double buy(){
				return 1 ;
			}
		};
		
		class Student : public Person{
		public:
			virtual double buy(){
				return 0.5 ;
			}
		};
		
		class CEO : public Person{
		public:
			virtual double buy(){
				return 1000 ;
			}
		};
		void shoufei( Person * p ){
			cout<< p->buy() << endl;
		}
		
		int main(){
			Person p ;
			Teacher t ;
			Student s ;
			CEO c ;
		
			shoufei( &p ) ;    //通过传入不同对象的地址,调用相应的函数
			shoufei( &t ) ;    //与if...else对比
			shoufei( &s ) ;    //写程序要尽量少改动写好的源码,这样实现代码的通用及低耦合
			shoufei( &c ) ;
		
			return 0 ; 	
		}           

11、 本对象 子类对象 其他函数
private属性 可见 不可见 不可见
protected属性 可见 可见 不可见
public属性 可见 可见 可见

                public extends           protected extends              private extends 
 父类的private属性       不能访问               不能访问                  不能访问 
 父类的protected属性  变成protected                 不变             变成private,子类可以访问,子类的子类不能访问 
 父类的public属性          不变               变成protected          变成private,子类可以访问,子类的子类不能访问 

12、 构造函数有很多种,因为没有指定构造函数,就会默认使用无参的构造函数。如果父类没有无参的构造函数,那么就会出现编译错误。
可以使用这种形式Teacher(char* name, int age, double salary):Person(name,age){…},指定使用父类的有参构造函数。

13、多态的特征:
父类的指针可以指向子类的对象
通过父类指针只能调用父类中声明的方法
通过指针调用函数的时候,若函数是个虚函数,表现出来的行为是对象自身的行为

14、产生多态:(1)指针
(2)引用
父类的引用可以引用一个子类对象
通过父类引用只能调用父类函数
调用一个父类被覆盖了的,虚函数,能调用子类的函数

15、一个子类继承一个父类 — 单继承
一个子类继承多个父类 — 多继承

class SpiderMan : public Spider , public Person{…}

16、菱形继承,解决重复元素的冲突
让两个父类同时虚继承一个超类,把多继承中的重复元素放在超父类中
当有多个子类同时虚继承一个父类的时候,只有一个子类真正的构造父类
class Spider : vertual public Animal{…};
class Person : vertual public Animal{…};
class SpiderMan :public Person , public Spider{…};

多继承尽量不要使用三层以上

17、抽象类
只有函数声明,没有函数实现
纯虚函数:没有实现的函数 virtual void writeLog(char*)=0;
若不写" =0 “,则系统会认为是函数声明,会试图去别的” .cc "文件中去找函数实现
含有纯虚函数的类称为抽象类,是抽象数据类型,不能创建对象
抽象类型就是为了被别人继承的,子类覆盖纯虚函数,提供函数实现
通过父类规范子类的用法

如果子类没有完全实现抽象父类的所有纯虚函数,则认为子类还是一个抽象数据类型

用到virtual的地方:
(1)继承
(2)多继承
(3)纯虚函数

抽象类的规范很重要,在工程中,对于项目的并行开发很重要
而且对于项目中的改动,能方便的应付
用指针指向相应的子类对象,方便的调用子类的函数

18、友员
针对类来说,自身的私有变量,不能被别的类访问,但是,如果授权给一个类为自己的友员,就可以访问他的私有属性
可以作为友员的东西:另一个类,一个全局函数。

实现友员的授权:
class Girl;
class Person{

friend class Girl; //友员类的声明–>授权给Girl类,成为自己的友员,可以访问自己的私有变量了
}

  class Girl;
  class  Person{
       ........
       friend void fn();  //友员函数的声明-->授权给fn函数,成为自己的友员,可以访问自己的私有变量了
  }
  
友员不是类的一部分
若不是互为友员,则不能访问友员类的私有变量

友员的使用:
       Bus把售票员作为自己的友员,访问自己的私有变量,即装载乘客的数组
       
友员在项目中的使用

19、静态数据
在类中定义一个静态数据 (实际上就是一种全局变量)
(1)不依赖于对象,在对象不存在之前就已经存在了
(2)所有对象共享

与全局变量的区别:
(1)使用的类中的静态变量,必须通过类名使用
(2)而且受访问控制符号的限制
(3)静态变量在类中声明的时候不能赋值,要在类外初始化
class A{
public :
static int a;
};
int A::a = 100; //此时才分配空间

    int main(){
         cout << A::a <<endl;    //静态变量的用法,不依赖对象,直接使用
    }
    
与成员变量的区别
(1)成员变量依赖与对象,类对象不存在,成员变量也不存在
     静态变量不依赖于对象,只要有类声明,就存在
(2)所有对象共享一份静态变量

21、静态函数
在函数前加static
不依赖于对象,没有对象依然可以通过类名调用静态函数
A::fn();

在类声明的时候,静态函数和静态变量就存在了
静态函数只能使用静态变量,不能使用成员变量

22、拷贝构造函数
#include
using namespace std;

class Person{
public:
Person(){ cout<<“Person()”<<endl; }
~Person(){ cout<<"~Person() "<<endl;}
void speak(){ cout<<“hello”<<endl; }
};

void fn( Person p ){ //这里的传递时值传递,形参是值传递,这里的形参的创建是使用拷贝构造函数
p.speak();
}

int main(){
Person p ;
fn( p ) ;
return 0 ;
}
输出结果:
Person()
hello
~Person()
~Person() //2次析构,因为其中调用了的系统提供的拷贝构造函数,构造出一个新对象

拷贝构造函数,以一个已存在的对象为模版创建一个新对象
声明方法: Person(const Person & p){…} //即节省空间,又保证模版不会被修改

默认拷贝构造函数,执行的就是简单的内存拷贝 — 浅拷贝

(1)浅拷贝
只拷贝地址,而不是对指针指向空间的拷贝,会造成2个指针指向同一个空间
(2)深拷贝
为指针创建新空间,拷贝指针指向空间的内容

调用拷贝构造函数的时机:
(1)在值传递的时候
(2)A a; //默认构造函数
A a1 = a; //在声明的时候,用一个对象赋值,使用的是拷贝构造函数
(3)A a1;
A a2(a1); //显示的调用拷贝构造函数,传的参数是个对象

什么时候需要自己写拷贝构造函数?
     使用了动态内存,就会面临浅拷贝的现象,需要自己写拷贝构造函数

23、运算符重载
a1 = a2;
系统调用了函数 operator=
相当于执行了这样的操作: a1.operator=(a2);

 Student& operator= (const Student &a);
 函数返回左值,返回值为引用
 函数返回右值,返回值为本身
 
 /*
 *student a1;
 *student a2;
 *a1=a2;  <=>  a1.operator=(a2);
 */
 Student& operator= (const Student &a){
      age = a.age;
      id = a.id;
      strcpy(name , a.name);   //不用new出一块空间,因为在声明a1和a2的时候,两个指针都指向一块自己的空间,
                                 把指针指向的变量拷贝过去,即完成赋值
      return *this;
 }
 当在堆中申请空间,则覆盖赋值运算符(" = ")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值