c++之类和对象快速入门

c++ 类和对象

文章目录

  • c++ 类和对象
    • 访问修饰符
    • class和struct比较
    • 构造函数和析构函数
      • 构造函数
        • 语法
        • 分类
        • 初始化列表
        • 3种调用方法
      • 析构函数
    • 成员变量和成员函数
    • 静态成员变量
    • this指针
    • 空指针访问成员函数
    • const修饰成员函数(常函数)
    • const修饰对象(常对象)
    • 友元 friend
      • 全局函数做友元
      • 类做友元
      • 成员函数做友元
    • 运算符重载
      • `+`重载
      • `++`运算符
      • `=`赋值运算符重载
      • `==`&`!=`重载
      • `()`函数调用运算符重载(仿函数)
    • 继承
      • 继承的方式
      • 继承中的对象模型
      • 构造函数和析构函数的执行顺序
      • 同名成员函数的处理
    • 虚继承
      • 菱形继承
    • 多态
    • 纯虚函数和抽象类
    • 虚析构和纯虚析构

访问修饰符

访问修饰符 类内部 子类 普通外部类
public
protected ×
private × ×

class和struct比较

  • 默认访问权限

    • struct默认为public
  • class默认为private

构造函数和析构函数

构造函数

语法

类名(){}

  • 构造函数可以重载
  • 构造函数系统自动调用
class Person{
    //无参构造函数
   	Student();
    //有参构造函数
	Student(string name, int age);
	//有参构造函数
	Student(int age);
    //拷贝构造函数
	Student(const Student& stu);
    
    string toString();
	//析构函数
    ~Student();
}

分类

  • 有参数
  • 无参数
  • 拷贝构造函数
//拷贝构造函数举例

Person(const Person& p){
    this->age = stu.age;
	this->name = stu.name;
}

//拷贝构造函数调用情景

//1.使用已存在对象初始化新对象
Person p1("xiaohong",14);
Person p2(p1);


//2.函数值传递复制参数的时候会调用拷贝构造函数
void func(Person p){
    
}
int main(){
    Person p1("xiaohong",14);
    func(p1);
}

//3. 值方式返回局部对象
Person func(){
    Person p("Lisi",20);
    // 返回的Person对象是P的拷贝
    return p;
    
}
int main(){
    Person p1("xiaohong",14);
    Person p2=func();
}
  • 编译器默认有 无参数构造函数、析构函数和拷贝复制函数
  • 写了有参数构造函数之后,不再提供无参数构造函数,但是提供拷贝构造函数
  • 写了拷贝构造函数之后,不再提供其他构造函数(包括默认无参数构造函数)

初始化列表

<类名():参数1(初始值)>[,参数2(初始值),...]

代码示例

//
Person():age(12),name("Lisi"){
    
}

Person(){
    this->age = 12;
	this->name = "Lisi";
}


//

Person(string myName , int myAge):name(myName),age(myAge){
    
}

Person((string myName , int myAge){
    this->age = myAge;
	this->name = myName;
}

3种调用方法

  • 括号法
Person p1; //无参数构造函数,不要加括号
Person p1(); //编译器会认为这是一个函数p1()的声明
Person p2("xiaohng",10); //有参数构造函数
Person p3(p2); //拷贝构造函数
  • 显式法
Person p1;//无参数构造函数
Person p1 = Person("xiaohng",10);//有参数构造函数
Person p3 = Person(p2);//拷贝构造函数

//相关知识点  匿名对象
 Person("xiaohng",10); 

//不要使用拷贝函数 初始化匿名对象
//示例

Person(p3); //报错,Person p3重定义
//原因: Person(p3)语句 相当于 Person p3; 
  • 隐式法
Person p1; //无参数构造函数
Person p2 = 10; //有参数构造函数== Person p2(10)
Person p3 =p2; //拷贝构造函数==Person(p2)

析构函数

  • 语法 ~类名(){}
  • 析构函数没有参数
  • 对象释放时自动调用

成员变量和成员函数

  • 成员变量和成员函数分开存储
  • 只有非静态变量才属于对象

代码说明

//测试类1
class Person {

};

Person p;
// sizeof(p)=1;
//类内部为空的对象大小为1字节

 //测试类2   
class Person {
	int age;
};  
Person p;
//sizeof(p)=4;


//测试类3
class Person {
	int age;
    static int count;
}; 

Person p;
//sizeof(p)=4;


//测试类4
class Person {
	int age;
    static int count;
    void func();
};  
Person p;
//sizeof(p)=4;

//测试类5  
class Person {
	int age;
    static int count;
    void func();
    static void func2()
}; 
Person p;
// sizeof(p)=4;

静态成员变量

静态成员变量声明之后,需要在类的外部分配空间

代码示例

class Person{
public:
    static int count;
    
}

int Person::count=0;

this指针

  • this指向对象本身
  • *this解引用为当前对象
  • this本质,指针常量 Person * const this

使用this实现链式编程示例

class Timer

#include 
using namespace std;
class Timer
{
private:
	int sec;
	int min;
	int hour;
public:
	Timer():sec(0),min(0),hour(0) {
		
	}
    
	Timer(int sec, int min, int hour) {
		this->sec = sec;
		this->min = min;
		this->hour = hour;
	}
    
    
 	//下面三个函数返回当前对象的引用
    //一定要返回当前对象的引用,否则会返回当前对象的拷贝,无法对当前对象实现链式操作
	Timer& addSec(int sec=0) {
		this->sec += sec;
		this->min += this->sec / 60;
		this->sec = this->sec % 60;
		return *this;
	}

	Timer& addMin(int min=0) {
		this->min += min;
		this->hour += this->min / 60;
		this->min = this->min % 60;
		return *this;
	}

	Timer& addHour(int hour=0) {
		this->hour += hour;
		return *this;
	}

	string toString() {
		return "[" + to_string(hour) + ":" + to_string(min) + ":" + to_string(sec) + "]";
	}

};

main.cpp

int main() {

	Timer timer(0, 0, 0);
	timer.addSec(10).addMin(20).addHour(100);  //链式
	cout << timer.toString() << endl;
	timer.addSec(20).addMin(30).addHour(1);    //链式
	cout << timer.toString() << endl;
	timer.addSec(50).addMin(10).addHour(0);   //链式
	cout << timer.toString() << endl;
    
    return 0;
}

空指针访问成员函数

在c++中,空指针可以访问没有使用非静态成员变量的函数、

示例代码

class Person{
private:
    int age;
public:
    void sayHello(){
        cout<<"hello world!"<

为了程序的健壮性,可以将上面的Person类改为下面的代码

class Person{
private:
    int age;
public:
    void sayHello(){
        cout<<"hello world!"<

const修饰成员函数(常函数)

this的本质

指针常量 Person * const this,在使用this指针时,this指针的指向不可以改变,例如下面的代码是错误的

class Person{
public:
    void change(){
        this == NULL; //错误
    }
}

但是指针指向的值时可以改变的,例如

class Person{
    int age;
public:
    void change(){
        this->age = 10; //正确
    }
}

使用const修饰成员函数,相当于将this修改为const Person * const this,即this指针的指向和指向对象的值都不可以改变,因此在常函数体内部无法修改当前对象的属性。

但是使用mutable修饰的属性值在常函数内部可以改变

常函数声明格式

class Classname{
    //常函数声明
    func() const{
        
    }
}

示例代码

class Person{
private:
    int age;
    mutable string name;
public:
    void testChange() const{
        this->age = 10; //错误
        this->name = "root"; //正确
    }
}

const修饰对象(常对象)

  • const修饰对象后,该对象的属性值不可以修改(mytable修饰的除外)
  • 常对象只能调用常函数(非常函数可以修改属性值,常对象不可以修改属性值,为了避免冲突,常对象不能调用常函数)

代码示例

class Person{
public:
    int age;
    mutable string name;
public:
    
    //常函数声明
    void testChange() const{
        this->age = 10; //错误
        this->name = "root"; //正确
    }
    
    //普通成员函数
    void changeAge(){
        age=12;
    }
}


int main(){
    
    //常对象声明
    const Person p;
    p.age = 12; //错误
    p.name = "Tom"; //正确
    p.testChange();  //正确
    p.changeAge(); //错误
    
}

友元 friend

一般情况下,类中private属性在类的外部不能被访问,但是一个类的友元可以不受这个限制。

三种友元的方式

  1. 全局函数
  2. emp

全局函数做友元

使用全局函数作为友元时,只需要在类的声明中加入friend void func();即可将一个全局函数声明为一个类的友元

代码示例

class Person

#pragma once
#include
#include

using namespace std;
class Person
{
	//友元全局函数声明
	friend void askSecret(Person &p);
public:
	string name;
	int age;
private:
	string secret; //秘密,只能告诉好朋友,一般人不能访问

public:

	Person( string name,int age) {
		this->name = name;
		this->age = age;
	}

	void setSecret(string str) {
		this->secret = str;
	}
};

MainApp.cpp

#include"Person.h"


void askSecret(Person& p) {
    //类的外部访问私有属性 p.secret
	cout << p.name << "'s secret is " << p.secret << endl;  //tom's secret is I like Jess
}

int main() {
	Person p("tom", 12);
	p.setSecret("I like Jess");
	askSecret(p);
	system("pause");
	return 0;
}

类做友元

使用类作为友元时,只需要在类的声明中加入friend class ;即可将一个类声明为另一个类的友元

代码示例

class Person

#include
#include

using namespace std;
class Person
{
    //声明友元类
	friend class Doctor;
public:
	string name;
	int age;
private:
	string secret; //秘密,只能告诉好朋友,一般人不能访问

public:

	Person( string name,int age) {
		this->name = name;
		this->age = age;
	}

	void setSecret(string str) {
		this->secret = str;
	}
};

class Doctor

#include"Person.h"
class Doctor
{
	string name;

public:
	Doctor() {
		this->name = "doctor li";	
	}


	void askSecret(Person& patient) {
        //友元访问Person类的私有属性
		cout << patient.name << "'s secret is \"" << patient.secret << "\"" << endl;
	}
};

MainApp.cpp

#include"Person.h"
#include "Doctor.h"

int main() {
	Person p("tom", 12);
	p.setSecret("I like Jess");
	Doctor doctor;
	doctor.askSecret(p);   //tom's secret is "I like Jess"
	system("pause");
	return 0;
}

成员函数做友元

使用成员函数作为友元时,只需要在类的声明中加入friend void ::func();即可将一个成员函数声明为另一个类的友元

注意:尽量不要使用这种做法,会造成 A&B两个类互相包含的错误。例如,A.func()是B的友元,在声明时,需要在B中包含A,但是在A中访问B的时候,又必须包含B。

运算符重载

  • 重载有两种方式,使用成员函数实现和使用全局函数实现
  • 运算法重载函数也可以发生函数重载
  • 基本数据类型的运算符不可以重载

声明方式

//成员函数实现
Timer operator+(Timer t) {
    Timer tmp(t);
    tmp.addSec(this->sec).addMin(this->min).addHour(this->hour);
    return tmp;
}




//全局函数实现
Timer tmp(t1);
tmp.addSec(t2.getSec()).addMin(t2.getMin()).addHour(t2.getHour());
return tmp;


//调用方式
Timer t1(10,20,30);
Timer t2(20,40,50);

Timer t4 = t1 + t2;  // 本质为 Timer t3=t1.operator+(t2);

+重载

代码示例

class Timer

#include 
using namespace std;
class Timer
{
private:
	int sec;
	int min;
	int hour;
public:
	Timer():sec(0),min(0),hour(0) {
		
	}

    //运算符重载声明
	Timer operator+(Timer t) {
		Timer tmp(t);
		tmp.addSec(this->sec).addMin(this->min).addHour(this->hour);
		return tmp;
		
	}

	Timer(int sec, int min, int hour) {
		this->sec = sec;
		this->min = min;
		this->hour = hour;
	}

	Timer& addSec(int sec=0) {
		this->sec += sec;
		this->min += this->sec / 60;
		this->sec = this->sec % 60;
		return *this;
	}

	Timer& addMin(int min=0) {
		this->min += min;
		this->hour += this->min / 60;
		this->min = this->min % 60;
		return *this;
	}

	Timer& addHour(int hour=0) {
		this->hour += hour;
		return *this;
	}	
	string toString() {
		return "[" + to_string(hour) + ":" + to_string(min) + ":" + to_string(sec) + "]";
	}

};

MainApp.cpp

int main() {
	Timer timer(0, 0, 0);
	timer.addSec(10).addMin(20).addHour(100);
	Timer timer2(10, 50, 40);
	//Timer timer3 = timer.operator+(timer2);
	Timer timer3 = timer + timer2;

	cout << timer.toString() << endl;
	cout << timer2.toString() << endl;
	cout << timer3.toString() << endl;
	system("pause");
	return 0;
}

++运算符

区分++intint++的方法

//++i
operator++(){
    
}

// i++
operator++(int){
    
}

=赋值运算符重载

  • c++中,使用赋值给对象赋值时,会拷贝对象的属性值,这一点与java引用赋值不一样
  • 如果对象属性中,有在区的数据,即使用new关键字开辟的内存空间,使用赋值语句赋值时会产生浅拷贝的问题,这个时候,对=·运算符重载非常有必要

==&!=重载

()函数调用运算符重载(仿函数)

代码示例

class Printer

#include
#include
using namespace std;

class Printer
{
public:
    //这里对“()”进行重载
	void operator()(string text) {
		cout << text << endl;
	}
};

MainApp.cpp


int main() {
    
	Printer print;
    //使用`对象()`的方式可以调用仿函数
	print("Hello world!!");

    //匿名对象: 类名()
	Printer()("这里是匿名对象函数打印结果");
	return 0;
}

继承

继承的方式

  • 语法class 类名: <继承方式> <父类>

  • 继承方式

    • public
    • protected
    • private
父类属性权限 public protected private
public继承 public protected 不可访问
protected继承 protected protected 不可访问
private继承 private private 不可访问

继承中的对象模型

  • 子类的对象大小包含子类的属性和所有父类的非静态属性(包括私有属性)
  • 编译器将父类的私有属性隐藏,因此,子类对象访问不到

代码示例

class Base{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
}

class Son: public Base{
public:
    int m_D;
}

int main(){
    cout<

构造函数和析构函数的执行顺序

创建一个子类对象时:

  • 先执行父类构造函数
  • 再执行子类构造函数

释放子类对象时:

  • 先调用子类析构函数
  • 再调用父类析构函数

同名成员函数的处理

  • 当子类和父类有同名函数时,直接调用函数会调用子类函数
  • 如果需要调用父类同名函数,需要使用如下语法
class Animal(){
public:
    string getName(){
        
    }
}


class Dog:public Animal(){
public:
    string getName(){

    } 
}


int main(){
    Dog d;
    d.getName(); //调用子类函数
    d.Animal::getName(); //调用父类函数
}
  • 当子类和父类有同名函数时,编译器会隐藏掉父类中所有同名函数,因此,需要加作用域访问

虚继承

菱形继承

base
son1
son2
grandson
  • 发生菱形继承时,子类grandson会有两份base中的同名属性,互不干扰
  • 浪费内存
  • 解决方法,虚继承

虚继承语法

class name: virtual public base{}

代码示例

没有使用虚继承

class A {
public:int age;
};

class B :public A {

};

class C : public A {

};

class D :public B, public C {

};

int main() {

	D d;
	d.C::age = 18;
	d.B::age = 20;
    // d.age = 20; 编译错误
 
	cout << "d.C::age=" << d.C::age<class A {
public:int age;
};

//虚继承
class B :virtual public A {

};
//虚继承
class C : virtual public A {

};

class D :public B, public C {

};

int main() {

	D d;
	d.C::age = 18;
	d.B::age = 20;
    d.age = 30; //可以使用,没有编译错误
 
	cout << "d.C::age=" << d.C::age<class Animal
{
public:
    //虚函数
	virtual void speak() {
		cout << "animal is speaking..." << endl;
	}

};

class Dog

class Dog : public Animal
{
	void speak() {
		cout << "dog is speaking" << endl;
	}

	
};

MainApp.cpp

void test(Animal & a) {
	a.speak();
}

int main() {

	Dog d;
	test(d);  //dog is speaking
}

纯虚函数和抽象类

  • 父类中的虚函数实现一般没有意义,使用纯虚函数
  • virtual returnType func(param)=0
  • 当类中有纯虚函数时,该类变为抽象类
  • 抽象类无法实例化对象
  • 继承抽象类的子类如果没有重写父类纯虚函数,则这个子类也是抽象类

虚析构和纯虚析构

  • 使用多态时,利用父类类型接受子类对象

    Animal animal = new Dog;
    
  • 当释放这个对象时,会调用父类的析构函数,子类的析构函数不会被调用,导致子类中有释放堆区数据的代码不能被执行,内存释放不干净

  • 解决方法:将父类的析构函数写成虚析构函数

    virtual ~Animal(){   
    }
    
  • 这样在释放子类对象时,会先调用子类的析构函数,再调用父类的析构函数

  • 纯虚析构

    • 纯虚析构需要在类内部声明,在外部实现
    virtual ~Animal()=0;
    //类的外部
    Animal::~Animal(){
        
    }
    

你可能感兴趣的:(#,c++,后端)