2020.12.c++类和对象

c++面向对象

一.内存分析

1.内存分区模型

内存分析

2.程序运行前

  • 未执行程序前分为两个区域,代码区和全局区
  • 代码区是共享的,其目的是对于频繁被执行的程序,只需要在内存中有一份代码就行了。
  • 代码区是只读的,防止程序被意外地修改了
  • 全局区存放全局变量(在int main()函数或者其他函数外面,定义的变量)、静态变量(变量前面+static的变量)、常量(字符串常量和全局常量量
  • 注意:局部变量和局部常量,即在int main()函数里面或者其他函数里面,定义的普通变量,不在全局区

3.程序运行后

  • 栈区:由编译器自动分配和释放,存放函数的参数值(形参)、局部变量
  • 注意:函数最后,不要返回局部变量的地址,因为栈区在程序运行后会释放。
  • 堆区:由程序员分配释放,若程序员不释放,程序结束后由操作系统自己回收
  • 注意:c++中主要利用new关键字开辟内存

4.new关键字

  • new返回的是该数据类型的指针
int* p = new int(10);
int* arr=new int[10];//arr为数组首地址
  • 可以利用delete释放空间,delete+指针名(delete p; delete[] arr)
  • 释放数组时:要加一个中括号,代表释放数组

二.引用

1.给变量起别名

  • 语法:数据类型 &别名=原名;

  • eg:

int a=10;
int &b=a;
//a=10,b=10

注意:程序修改原变量值或者别名变量的值,数据都会改变,因为别名和原名的地址是相同的

2.使用引用的注意事项

  • 必须要初始化,且初始化后,就不可以更改。
int a=10;
int &b=a;
int c=20;
b=c;//赋值操作,不是修改别名,这里将20赋给b,a和b指向同一内存
//a=20,b=20,c=20

3.引用传递

//引用传递
#include <iostream>;
using namespace std;
void swap3(int& m, int& n) {
	int temp = m;
	m = n;
	n = temp;
	cout << "swap3:a =" << m << endl;
	cout << "swap3:b =" << n << endl;
}
int main() {
	int a = 10;
	int b = 20;
	//swap(a, b);
	//swap2(&a,&b);
	swap3(a, b);
	cout<<"a ="<< a<<endl;
	cout<<"b ="<< b<<endl;
}
/*swap3:a =20
swap3:b =10
a =20
b =10
*/
  • 引用传递:形参也会改变实参,原因是&m&n分别是ab的别名,两个指向的是相同的地址

4.引用做函数的返回值

  • 注意:函数最后,不要返回局部变量的引用,因为栈区在程序运行后会释放。
  • 前面也说到,函数最后,不要返回局部变量的地址,因为栈区在程序运行后会释放。
  • 函数引用调用可以作为左值存在
#include <iostream>
#include <String>
using namespace std;
//**注意:函数最后,不要返回局部变量的地址或者引用,因为栈区在程序运行后会释放。**
//***注意:函数引用调用可以作为左值存在
int& back1() {
	int m = 10;
	return m;
}

int& back2() {
	static int m = 10;
	return m;
}

int main() {
	int& a = back1();
	cout << a << endl;
	cout << a << endl;//a是别名,也可以打印原名back1()
	//第一次有值,因为编译器进行了保留,第二次乱码,因为栈区执行完该函数,释放了内存


	int& b = back2();
	cout << b << endl;
	cout << b << endl;//b是别名,也可以打印原名back2()

	back2() = 100;
	cout << b << endl;
	cout << b << endl;//b是别名,也可以打印原名back2()
}
/*10
2067384816
10
10
100
100
*/

5.引用的实质

  • 引用的实质就是指针常量(指针的指向不可以改,但是指针的值可以改),这就解释了为什么引用不可以改。
int a =10;
int &b=a;//自动转化为int *const b=&a;
b=20;//,内部发现b是引用,自动帮我们转化为*b=20;

6.常量引用

  • 用于修饰形参,防止误操作,修改了形参的值导致实参的值发生改变

    void show(const int &val){
        //val=100;报错,不能修改
        cout<<"val= "<<val<<endl;
    }
    int main(){
        int a=10;
        show(a);
    }
    

三.函数的加深

1.函数默认参数

  • 定义语法:返回值类型 函数名(返回值类型 参数名(形参)=默认参数){}
  • 注意1:在调用函数时,如果我们自己传入数据,就用自己数据,如果没有,就用默认值
  • 注意2:在定义函数时,如果某个位置已有默认参数,那么从这个位置往后,都必须有默认参数
  • 注意3:在声明函数时,如果有了默认参数,则在定义函数时,就不能有默认函数,声明函数和默认函数之间只能有一个有默认函数
#include <iostream>
using namespace std;

int add1(int a, int b, int c) {
	return a + b + c;
}

int add2(int a, int b=10, int c=100) {
	return a + b + c;
}
/*
int add2(int a, int b = 10, int c ) {
	return a + b + c;
}函数定义,若有默认值,从左到右都必须有默认值,否则会报错!!!!!!!!!!!!!!!!
*/


int add3(int a, int b = 50, int c = 500);//函数声明
int add3(int a, int b , int c) {
	return a + b + c;
}
/*
int add3(int a, int b = 50, int c = 500) {
	return a + b + c;
}函数定义在再有默认值,会报错!!!!!!!!!!!!!!!!
*/


int main() {
	cout << add1(1, 2, 3) << endl;
	cout << add2(1)<<endl;
	cout << add2(1,2)<<endl;//调用时,有值用调用值,没值用默认值
	cout << add3(1)<<endl;
}
/*6
111
103
551
*/

2.函数占位参数

  • 定义语法:返回值类型 函数名(返回值类型){}
  • 占位参数不用写参数名,代表有一个位置,在调用参数时,()内需传入返回值对应的类型数据
  • 注意:占位参数也可以传入默认参数

3.函数重载

  • 1)函数重载满足的条件:同一作用域下,函数名称相同,函数参数列表不同(参数类型、个数、顺序不同等)

  • 2)引用作为重载的条件

    函数定义时,const 修饰形参和不修饰形参,是不同的函数,可以重载

  • 3)函数重载遇到默认值

​ 防止有歧义,少使用默认值

#include <iostream>
using namespace std;

//*****************************1.引用作为函数重载
//int &a=10;不合法
void printing(int& m) {
	cout << "printing(int& m) " << endl;
}
//const int &a=10;合法
void printing(const int& m) {
	cout << "printing(const int& m) " << endl;
}

//******************************2.当重载遇到默认值
int add(int a) {
	return a  ;
}

int add(int a, int b = 10) {
	return a + b ;
}

int main() {
	int a = 10;
	printing(a);//运用的函数是 printing(int& m)
	printing(10); //运用的函数是 printing(const int& m)
    cout<<add(5, 10);
	//add(10);有歧义,可以调用上面两个重载的函数
	//只能尽量避免,重载函数少用默认值
}
/*printing(int& m)
printing(const int& m)
15
*/

四.类和对象

1.封装

  • 1)概念:

    将属性和行为作为一个整体,写在一起,表现为一个事务

  • 2)语法

    class 类名{ 访问权限:属性/行为};

    创建对象----实例化:类名 具体名称;

class Student{
    public:
    //.....
};

int main(){
    Student s1;//实例化
}
  • 3)一些专有名词

    类中的属性和成员我们统称为---成员

    属性:成员属性,成员变量

    行为:成员函数,成员方法

  • 4)访问权限

    类在设计时,可以把属性和行为放在不同权限下,加以控制

    • public--->成员 类内可以访问 类外可以访问
    • protected--->成员 类内可以访问 类外不可以访问,子类可以访问父类保护的内容
    • private--->成员 类内可以访问 类外不可以访问,子类不可以访问父类私有的内容
  • 5)结构体struct和类的区别class

    • 默认访问权限不同

    struct默认权限是 公共---public

    class默认权限是 私有------private

    • 调用函数、方法

    struct不能自动调用方法

    class能调用方法

  • 6)成员属性设置为私有

    • 可以有效控制读写权限
    • 对于写权限而言,可以检测数据的有效性,如判断年龄不符合
    #include <iostream>
    #include <String>
    using namespace std;
    
    class Student {
    public:
    	void setName(string name) {
    	 s_name= name;
    }
    
    	  string  getName() {
    		  return s_name;
    	  }
    
    	  void setAge(int age) {
    		  if (age < 0 || age>150) {
    			  s_age = 0;
    			  cout << "别骗人了!!!!" << endl;
    			  return ;
    		  }
    		  else {
    			  s_age = age  ;
    		  }
    	  }
    
    	  int getAge() {
    		  return s_age;
    	  }
    
    	  string getSex() {
    		  s_sex = "男";
    		  return s_sex;
    	  }
    	//private访问的数据类外不能访问,所以就弄一个set/get函数得到他们
    	//从而实现读写权限
    private:
    	string s_name;//可读可写
    	int s_age;//可读可写
    	string s_sex;//可读
    };
    
    int main() {
    	Student s1;
    	s1.setName("aaa");
    	s1.setAge(1000);
    	cout<<s1.getName()<<endl;
    	cout<<s1.getAge()<<endl;
    	cout<<s1.getSex()<<endl;
    }
    /*别骗人了!!!!
    aaa
    0
    男
    */
    

2.对象的初始化和清理

  • 1)基本概念

构造函数:主要作用在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用

析构函数:主要作用在对象销毁前系统自动调用,执行一些清理工作

构造函数和析构函数

注意:构造函数和析构函数都是必须实现的,如果我们不提供,编译器会自己提供一个空实现的构造和析构,对于拷贝构造函数会是一个值拷贝

  • 2)构造函数的分类及调用

    •    按照参数分类:无参构造(默认构造)和有参构造
      
    •    按照类型分类:普通构造和拷贝构造
      
    •    构造函数的调用方法:括号法、显示法
      
#include <iostream>
using namespace std;
 
class Person {
public:
	//!!!!!!!!!!!!!!!!!!!!构造函数!!!!!!!!!!!!!!!!
	Person() {
		cout<<"Person的无参构造函数"<<endl;
	}
	Person(int a) {	
		age = a;
	    cout << "Person的有参构造函数" << endl;
	}
	//将传入的人身上的所有属性拷贝到我(新的人)身上
	Person(const Person &a) {
		age = a.age;
		cout << "Person的拷贝构造函数" << endl;
	}
	//除了这种是拷贝构造,其余均是普通构造
	//!!!!!!!!!!!!!!!!!!!!析构函数!!!!!!!!!!!!!!!!!
	~Person() {
		cout << "Person的析构函数调用" << endl;
	}
	int age;
};
 //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!调用!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
void show() {
	//1.括号法
	Person p1;
	Person p2(10);
	Person p3(p2);
	//2.显示法
	Person p4 = Person(20);
	//Person(20)是匿名对象,没有名字,当前行执行结束后,系统会立即收到匿名对象
	Person p5 = Person(p4);
}

int main() {
	show();
}
/*Person的无参构造函数
Person的有参构造函数
Person的拷贝构造函数
Person的有参构造函数
Person的拷贝构造函数
Person的析构函数调用
Person的析构函数调用
Person的析构函数调用
Person的析构函数调用
Person的析构函数调用
*/
  • 3)拷贝构造函数的使用

    • 使用一个已经创建完毕的对象来初始化一个对象
    • 值传递的方式给函数参数传值
    • 值方式返回局部对象(存疑,不懂
    //拷贝构造函数的使用
    #include <iostream>
    using namespace std;
    class Person {
    public:
    	Person() {
    		cout << "Person的无参构造函数" << endl;
    	}
    	Person(int a) {
    		age = a;
    		cout << "Person的有参构造函数" << endl;
    	}
    	//将传入的人身上的所有属性拷贝到我(新的人)身上
    	Person(const Person& a) {
    		age = a.age;
    		cout << "Person的拷贝构造函数" << endl;
    	}
    	~Person() {
    		cout << "Person的析构函数调用" << endl;
    	}
    	int age;
    };
    //使用一个已经创建完毕的对象来初始化一个对象
    void show1() {
    	Person p1(10);
    	Person p2(p1);
    	cout << p2.age << endl;
    }
    //值传递的方式给函数参数传值
    void work(Person p) {
    
    }
    void show2() {
    	Person p;
    	work(p);//实际参数传给形式参数,调用拷贝构造函数
    }
    int main() {
    	//show1();
    	show2();
    }
    /*show1():Person的有参构造函数
    Person的拷贝构造函数
    10
    Person的析构函数调用
    Person的析构函数调用
    show2():Person的无参构造函数
    Person的拷贝构造函数
    Person的析构函数调用
    Person的析构函数调用
    */
    

  • 4)构造函数的调用规则

    • 如果用户定义了有参构造。c++不再提供无参构造,但是还是会提供默认的拷贝构造
    • 如果用户定义了拷贝函数,c++不会再提供其他构造函数
  • 5)浅拷贝和深拷贝

    • 深拷贝:自己实现拷贝构造函数解决浅拷贝带来的问题(堆区的内存重复释放)

    在堆区重新申请空间,进行拷贝操作

    • 浅拷贝:利用编译器自己默认提供的拷贝构造函数
//深拷贝与浅拷贝
#include  <iostream>
using namespace std;
 
class Person {
public:
	Person() {
		cout << "Person的无参构造函数" << endl;
	}

	Person(int age,int height){
		p_age = age;
		p_height = new int(height);//堆区的数据,需要自己去释放
		cout << "Person的有参构造函数" << endl;
	}
	//自己实现拷贝构造函数,解决浅拷贝带来的问题
	Person(const Person& a) {
		p_age = a.p_age;
		//p_height=a.p_age;编译器默认实现的这行代码
		//深拷贝操作
		p_height = new int(*a.p_height);//重新在堆区域申请一块内存,让指针指向这个新的空间。
		cout << "Person的拷贝构造函数" << endl;
	}
	//将堆区开辟的数据做释放操作
	~Person() {
		cout << "Person的析构函数调用" << endl;
		if (p_height != NULL) {
			delete p_height;
			p_height = NULL;
		}
	}
	int p_age;
	int* p_height;//弄一个堆区的数据,使用完后让程序员自己释放该数据,利用到析构函数!!!
};

//使用一个已经创建完毕的对象来初始化一个对象
void showing() {
	Person p1(18,180);
	cout << p1.p_age << " " << endl;//身高怎么打印
	Person p2(p1);
	cout << p2.p_age<<" "<<endl;//
}

int main() {
	showing();
	system("pause");
	return 0;
}
/*
Person的有参构造函数
18
Person的拷贝构造函数
18
Person的析构函数调用
Person的析构函数调用
*/
  • 6)初始化列表
class Person {
public:
	/*Person(int a,int b) {
		p_a = a;
		p_b = b;
	}传统初始化,与下面的等效*/

	Person(int a, int b) :p_a(a), p_b(b) {
	}//初始化列表
	int p_a;
	int p_b;
};
  • 7)类对象作为另外一个类的成员

当类中成员是其他类的对象时,我们称该成员为 对象成员

构造顺序:先调用对象成员的构造,再调用本类构造

析构顺序与构造相反

3.对象模型和thisz指针

  • 1)静态成员

    • 静态成员变量
      • 类内声明,类外初始化
      • 所有对象共享同一份数据,所以可以在类外通过 类名::静态成员变量去访问
    • 静态成员函数
      • 所有对象共享同一个函数,所以可以在类外通过 类名::函数名去访问!!!!!
      • 静态成员函数只能访问静态成员变量
    //静态成员变量
    #include <iostream>
    using namespace std;
    //静态成员也有访问权限
    class Student {
    public:
    	static int s_a;
    private:
    	static int s_b;
    };
    int Student::s_a = 0;
    int Student::s_b = 0;
    
    //静态成员的访问方式
    //1.通过对象
    void text1() {
    	Student s1;
    	s1.s_a = 100;
    	cout << "s1.s_a= " << s1.s_a << endl;
    	Student s2;
    	s2.s_a = 50;
    	cout << "s1.s_a= " << s1.s_a << endl;//共享同一份数据,数据与s2的相同
    	cout << "s2.s_a= " << s2.s_a << endl;
    	//无法调用私有静态变量s_b
    
    //2.通过类名
    	 cout << "s_a= " << Student::s_a << endl;//数据也是与上面的相同
    	 //Student::s_b私有权限访问不到
    }
    
    int main() {
    	text1();
    }
    /*s1.s_a= 100
    s1.s_a= 50
    s2.s_a= 50
    s_a= 50
    */        
    
  • 2)成员变量和成员函数分开存储

    • 只有非静态变量才属于类的对象,静态成员函数、成员,非静态函数都不属于类的对象
    • 如果类为空,创建一个空对象,它也会占一个字节的空间
    • 如果类上面不为空,根据非静态成员变量的类型(int----4字节等)分配空间
  • 3)this指针

    this指针本质是指针常量,指针的指向不能修改

    • this指针指向被调用的成员函数--所属的对象
    • this指针是隐含在每一个非静态成员函数内的一种指针,不需要定义,直接使用
    • this指向的是调用对象本身,*this表示这个对象本体
    class Person{
        public:
        //!!!!!!!!!!!!当形参和成员变量同名时,可以用this指针区分
        Person(int age){
           this->age=age; 
        }
        Person& personAdd(Person &p){
        this->age+=p.age;
            return *this;
        }
        int age;
    };
    
    int main(){
        Person p1(10); 
        Person p2(10);
        p2.personAdd(p1).personAdd(p1);
        cout<<p2.age<<endl;
    }
    /*30*/
    
  • 3)空指针访问成员函数

    可以创建一个空的指针(Person * p=NULL)

    ,这个对象p不能通过“this->成员变量”的形式访问成员内部的数据,因为this为空。

  • 4)const修饰成员函数

    • 常函数:在成员函数后面加const,修饰的是this指针,本身它的指向不能修改,加了之后,它的值也不能修改
    • 常对象:在对象前面加const,也是不能修改属性的
    • 常对象只能调用常函数

如果一定要修改,成员属性声明时,加一个关键字mutable

4.运算符重载

  • 1)对已有运算符进行定义,赋予其另一种功能,以适应不同的数据类型

  • 2)加号运算符重载

    运算符加号重载

运算符重载也可以发生 函数重载

eg:上面想完成 Person p4=p1+20;

只需要将上面的函数的参数进行修改

Person operate+(Person &p1,int num ){

//p2也改为num......

}

注意:对于!内置!的数据类型表达式的运算符是不可能被改变的

3)赋值 运算符重载

5.继承

  • 1)继承的基本语法与概念

    • class 子类名: 继承方式(public、private、protect) 父类名{

    ​ };

    • 派生类的成员一类是从基类继承过来的(共性),一类是自己增加的成员(个性)

    继承的好处:减少重复的代码

  • 2)一些专有名词

    子类----->派生类

    父类----->基类

  • 3)继承方式

    • 公共继承(public)
    • 保护继承(protect)
    • 私有继承(private)

继承方式

  • 4)继承的对象模型

    • 父类中所有非静态成员属性是都会被子类继承下去的
    • 父类中的私有成员属性 是被编译器给隐藏了,因此是访问不到,但是它确实是被继承下去了
  • 5)继承中 构造和析构顺序

    继承中:先构造父类,再构造子类,析构顺序与构造相反

  • 6)继承同名非静态成员处理方式

    解决的问题:当子类与父类出现同名的非静态成员时,通过子类对象,访问子类或父类的同名数据

    • 访问子类同名对象(属性和函数)

      直接访问即可

    • 访问父类同名对象(属性和函数)

      加作用域::

class Father{
    Father(){
        age=100;
    }
    void func(){
        cout<<"Father_func()调用"<<endl;
    }
    void func(int a){
        cout<<"Father_func(int a)调用"<<endl;
    }
 int age;   
}

class Son:public Father{
    Son(){
        age=50;
    }
    void func(){
        cout<<"Son_func()调用"<<endl;
    }
   int age; 
}

void test1(){
    Son s1;
    cout<<"Son_age="<<s1.age<<endl; 
    cout<<"Father_age="<<s1.Father::age<<endl;
}

void test2(){
    Son s2
    s2.func();
    s2.Father::func();
    //如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏所有的同名成员函数,不管你参数是否不同
    s2.Father::func(100);
}

int main(){
    test1();
}

7)继承同名静态成员处理方式

  • 可以同上6)通过子类对象,访问同名的数据

也可以利用类名直接访问(Son::age)(Son::func())(Son::Father::age第一个::代表用类的方式访问,第二个::代表在Father类的作用域下)(Son::Father::func()同前

6.多态

  • 1)多态的概念

    多态的概念

  • 2)动态多态的满足条件及使用

    • 有继承关系
    • 子类要重写父类的虚函数(函数前面加一个关键字virtual)
    • 动态多态的使用:父类的指针或引用,指向子类对象
    • 重写:函数返回类型 函数名 参数列表完全相同
//静态多态与动态多态
#include <iostream>
using namespace std;

class Animal {
public:
    //virtual
	void yell() {
		cout << "动物在叫啊!" << endl;
	}
};

class Cat :public Animal {
public:
	void yell() {
		cout << "小猫在叫啊!" << endl;
	}
};

class Dog :public Animal {
public:
	void yell() {
		cout << "小狗在叫啊!" << endl;
	}
};
//父类函数,未加virtual关键字时:地址早绑定,在编辑阶段就确定了函数的地址
//如果要执行具体的动物:猫狗说话,地址就不能提前绑定,需要在运行阶段进行绑定----->地址晚绑定,加virtual关键字
void doYell(Animal &animal) {
	animal.yell();
}

void test01() {
	Cat cat;
	doYell(cat);//相当于:Animal &animal=cat;
    Dog dog;
    doYell(dog);//相当于:Animal &animal=dog;
    //父类的指针指向子类的对象
    Animal* animal1 = new Dog;
	animal1->yell();
}

int main() {
	test01();
}
/*父类函数未加virtual前:
动物在叫啊!
动物在叫啊!
动物在叫啊!
父类函数加了virtual后:
小猫在叫啊!
小狗在叫啊!
小狗在叫啊!
*/
  • 3)多态的原理

多态的原理

  • 解释:本身这个cat是子类的对象,当你调用animal.speak();时就是去找子类记录表中这个函数的地址,而原本的函数已经被重写,所以调用了重写后的函数

  • 4)纯虚函数和抽象类

    纯虚函数和抽象类

  • 5)虚析构和纯虚析构

posted @ 2020-12-09 20:26  维他命D片  阅读(52)  评论(0编辑  收藏  举报