c++类&对象的基本使用,构造函数,拷贝构造函数,析构函数,位拷贝,浅拷贝。this指针、类的分离、const成员、组合与聚合。

目录

类的概念

类的构成与设计

类的基本使用

方法一 .的使用

方法二 使用指针

构造函数

构造函数的作用

构造函数的特点

 构造函数的种类

默认构造函数

合成的默认函数

自定义的默认构造函数

自定义的重载构造函数

拷贝构造函数

手动定义的拷贝构造函数

合成的拷贝构造函数

浅拷贝和深拷贝

什么时候调用拷贝构造函数

赋值构造函数

 析构函数

 this指针-用不迷失的真爱

使用引用

使用指针

类文件的分离

 静态数据成员

 静态成员函数

const数据成员

const成员函数

组合与聚合

组合

聚合

const-error

vector-error

const-erro2

static-error



类的概念

 类是一个抽象的概念

类是看不见,摸不着的,是一个纯粹的概念

类是一种特殊的数据类型,不是一个具体的数据

Attention:类,和基本数据不同(char/int/short/long/long long/float/double)

类的构成与设计

eg:我们定义一个人类

#include <iostream>
#include <Windows.h>
#include <string.h>
using namespace std;

class Human{
public://这是公用的 对外的
	void eat(){}//方法 ,成员函数
    void sleep(){}
    void paly(){}
    void work(){}
    string getName();
    int getAge();
    int getSalary();
    
private://私有的  数据 不能直接从外部访问
    string name;
    int age = 18;//类内初始值
    int salary;
};

 再随便给出这些方法的实现


int Human::getAge(){
    
	 return age;
}

int Human::getSalary(){
	return salary;	
}

string Human::getName(){
	return name;
}


 

类的基本使用

什么是对象?

对象,是一个特定的”类“的具体实例

对象和普通变量有什么区别?

一般地,一个对象,就是一个特别的变量,但是拥有丰富的功能用法

方法一 .的使用

通过对象,能调用这个对象的public方法;且多个对象都有自己的数据,彼此无关;

方法二 使用指针

 -> 的使用(类似C语言的结构体用法)p需要初始化到h1才能使用。

构造函数

构造函数的作用

  •  在创建一个新的对象时,自动调用的函数,用来进行初始化的工作。
  • 对这个对象内部的成员进行初始化

构造函数的特点

1)自动调用(在创建新对象时,自动调用)

2)构造函数的函数名,和类名相同

3)构造函数没有返回类型

4)可以有多个构造函数(即函数重载形式)

 构造函数的种类

  • 默认构造函数

  • 自定义的构造函数

  • 拷贝构造函数

  • 赋值构造函数

默认构造函数

     没有参数的构造函数,称为默认构造函数

合成的默认函数

没有手动定义默认构造函数时,编译器自动为这个类定义一个构造函数。

1)如果数据成员使用了“类内初始值”,就使用这个值来初始化数据成员。【C++11】

2)否则,就使用默认初始化(实际上,不做任何初始化)

实例

int main(void) {
	Human  h1;  // 使用合成的默认初始化构造函数
	cout << "年龄: " << h1.getAge() << endl;     //使用了类内初始值
	cout << "薪资:" << h1.getSalary() << endl;  //没有类内初始值

	system("pause");
	return 0;
}

`

 发现问题:薪资salary是一个很大的负值。解决方法:使用手动定义的构造函数进行初始化

  • 只要手动定义了任何一个构造函数,编译器就不会生成“合成的默认构造函数”

  • 一般情况下,都应该定义自己的构造函数,不要使用“合成的默认构造函数”

自定义的默认构造函数

 常称为默认构造函数

在Human中定义构造函数 实现构造函数;


class Human {
public://这是公用的 对外的


    Human();

…………
};
Human::Human() {
    cout << "调用默认的构造函数" << endl;
    name = "无名氏";
    age = 25;
    salary = 30000;
 
}

 

说明:如果某数据成员使用类内初始值

同时又在构造函数中进行了初始化 那么以构造函数中的初始化为准。

相当于构造函数中的初始化,会覆盖对应的类内初始值(类内初始值age = 18 输出age = 25)

自定义的重载构造函数

使用this指针访问类Human 下private下的数据

在class下定义Human的重载函数

class Human {
public://这是公用的 对外的


    Human(int age,int salary);
    …………

 
};

Human重载的实现

Human::Human(int age, int salary) {
    cout << "调用自定义的构造函数" << endl;
    this->age = age;
    this->salary = salary;
    name = "Luciferau";
}

拷贝构造函数

手动定义的拷贝构造函数

    Human(const Human& man);//自定义的拷贝构造函数

 

Human::Human(const Human& man) {
    age = man.age;
    salary = man.salary;
    //使用深拷贝
    addr = new char[ADDR_LEN];
    strcpy_s(addr, ADDR_LEN, man.addr);
}

class Human{
void description();
…………
}


void Human::description(){
   
	cout << "name" << name << endl;
    cout << "age" << age<<endl;
	cout << "salary" << salary << endl;
}

 

 都给addr分配内存 进行深拷贝

Human::Human(int age, int salary) {
    cout << "调用自定义的构造函数" << endl;
    this->age = age;
    this->salary = salary;
    name = "Luciferau";
    addr = new char[ADDR_LEN];
    strcpy_s(addr, ADDR_LEN, "China");
}


Human::Human() {
    cout << "调用默认的构造函数" << endl;
    name = "Luciferau";
    age = 25;
    salary = 30000;
    addr = new char[ADDR_LEN];
    strcpy_s(addr, ADDR_LEN, "China");

}

合成的拷贝构造函数

先定义一个更改地址的函数: setaddr();

void Human::setaddr(const char* newAddr) {
    if (newAddr) {
        return;
     }
    strcpy_s(addr, ADDR_LEN, newAddr);
}

注释我们写的拷贝构造函数,让程序自动一个默认的拷贝构造函数

编写一个获取地址的函数

const char* Human::getAddr() {
    return addr;
}

 编写main函数

 我们可以看到h2的addr也变成了长沙。因为合成的拷贝构造函数使用的是浅拷贝,我们需要自定义拷贝构造函数进行深拷贝。

合成的拷贝构造函数的缺点: 使用“浅拷贝”

浅拷贝和深拷贝

详见我的这篇文章

什么时候调用拷贝构造函数

  1. 调用函数时,实参是对象,形参不是引用类型,如果函数的形参是引用类型,就不会调用拷贝构造函数

  2. 函数的返回类型是类,而且不是引用类型

  3. 对象数组的初始化列表中,使用对象。

调用形参是对象就会调用一次拷贝构造函数,使用指针和引用就可以避免此问题。形参是函数定义中的参数

形参指的是函数定义中的参数即为括号中的,实参指的是使用函数时调用的参数即为实参。

1.当实参是对象,形参不是引用类型

	Human h1(25, 35000);//使用自定义的默认构造函数
	Human h2(28, 40000);
    getBetterman(h1,h2);

  Human  getBetterman(const Human man1,const Human man2) {

	if (man1.getSalary() > man2.getSalary()) {
		return man1;
	}
	else {
		return man2;
	}

}
//调用二次拷贝构造函数Human h1,Human h2,这里是实参为对象而形参不是引用
//在调用一次拷贝构造函数 return man1 or man2 创建一个临时对象

代码优化


const Human& getBetterman(const Human &man1,const Human &man2) {

	if (man1.getSalary() > man2.getSalary()) {
		return man1;
	}
	else {
		return man2;
	}

}
//这样一次都不会调用拷贝构造函数

初始化列表(初始化列表中使用对象)

Human f1, f2, f3, f4;
Human F4[4] = { f1, f2, f3, f4};
//调用拷贝构造函数

赋值构造函数

什么时候会调用赋值构造函数呢 ??

	Human f1, f2;
	f1 = f2;//自动调用赋值构造函数!! 如果不定义赋值构造函数,编译器会生成一个合成的构造函数
 

当已经定义,定义后赋值给f1的时候会调用复制构造函数

一下情况则不会调用拷贝构造函数

Human f1;
Human f2 = f1; //这样f1 f2,调用的是拷贝构造函数 定义时候直接进行初始化

Attention: 合成的拷贝构造函数 都是浅拷贝(位拷贝)。

需要进行深拷贝


int main(void) {
	Human f1, f2;
	f2 = f1;
	f1.description();

	f2.description();

	cout << "-----------------" << endl;

	f1.setAddr("新加坡");
	f1.description();

	f2.description();
 

	system("pause");
	return 0;
}

Human& Human::operator=(const Human& man) {
	cout << "调用" << __FUNCTION__ << endl;
	if (this == &man) {
		return *this; //检测是不是对自己赋值:比如 h1 = h1;
	}


	age = man.age;
	salary = man.salary;
	/*addr = new char[ADDR_LEN];
 		这是进行深拷贝 分配内存的代码
	strcpy_s(addr, ADDR_LEN, man.addr);*/
	addr = man.addr;//浅拷贝

	 //反对对象的本身引用 以便为了做链式处理 f1 = f2 = f3
	return *this; //加*为对象本身 不加*是一个指针
}


//拷贝前先初始化addr 不然会将一个无效参数传递给一个无效参数 
Human::Human() {
	name = "无名氏";
	age = 18;
	salary = 30000;
	addr = new char[ADDR_LEN];
	strcpy_s(addr, ADDR_LEN, "China");
}

如果有必要请是释放自己资源

delete addr;
addr = new char[ADDR_LEN];//重新申请一块内存

 析构函数

作用:对象销毁前,坐清理工作(释放内存等),比如:如果在构造函数中,使用new分配了内存,就需在析构函数中用delete释放。

函数名:~类型

没有返回值,没有参数,最多只能有一个析构函数

使用方法:

  • 不能主动调用。

  • 对象销毁时,自动调用。

  • 如果不定义,编译器会自动生成一个析构函数(什么也不做)



Human::~Human() {
	delete addr;
}

 this指针-用不迷失的真爱

   this永远指向对象本身,谁调用指向谁.

Human::Human(int age, int salary) {
	cout << "调用自定义的构造函数" << endl;
	this->age = age;      //this是一个特殊的指针,指向这个对象本身
	this->salary = salary;
	name = "无名";

	addr = new char[64];
	strcpy_s(addr, 64, "China");
}

说明:在类的静态成员函数,不能使用this指针!

使用引用

int main(void) {
	Human  h1(25, 20000);  // 使用自定义的默认构造函数
	Human  h2(30, 40000);
	h1.compare1(h2).description();
	cout << "h1" << &h1 << endl;
	cout << "h1" << &h2 << endl;
	cout << "winner" << &(h2.compare1(h1) )<< endl;


 
	system("pause");
	return 0;
}


Human& Human::compare1(Human& man)
{
	if (this->salary > man.salary) {
		return *this;
	}
	else {
		return man;
	}

}

使用指针

Human* Human::compare2(Human* man)
{
	if (salary > man->salary){
		return this;
	}
	else {
		return man;
	}

}


int main(void) {
	Human  h1(25, 20000);  // 使用自定义的默认构造函数
	Human  h2(30, 40000);

	Human* p = &h1;
 
 
	p->compare2(&h2)->description();

 
	system("pause");
	return 0;
}

类文件的分离

实际开发中,类的定义保存在头文件中,比如Human.h【类的声明文件】(C++PrimerPlus)

类的成员函数的具体实现,保存在.cpp文件中,比如Human.cpp【类的方法文件】(C++PrimerPlus)

 静态数据成员

需求分析:需要获取总的人数,如何实现?

只能使用一个全局变量,然后在构造函数中对这个全局变量进行修改(加1)

缺点:使用全局变量不方便,破坏程序的封装性


private:
	string name = "Unknown";
	int age = 28;
	int salary;
	char* addr;
	static int HumanCount;//不在类里面的内存 属于类 不属于类的成员 只有一个实体 每个成岩都可以进行访问
#include "Human.h"

//对类的静态数据成员初始化 不能类内初始
  int Human::HumanCount = 0;
 

// 类的普通成员函数,可以直接访问静态成员(可读可写)
int Human::getCount() {
    
	return HumanCount;
}

对于非const的类静态成员,只能在类的实现文件中初始化。

const类静态成员,可以在类内设置初始值,也可以在类的实现文件中设置初始值。(但是不要同时在这两个地方初始化,只能初始化1次)

 静态成员函数

当需要获取总的人数时,还必须通过一个对象来访问,比如h1.getCount().

如果当前没有可用的对象时,就非常尴尬,不能访问getCount()!

void test() { cout << "总人数: "; // ??? 没有可用的对象来访问getCount()}

如果为了访问总的人数,而特意去创建一个对象,就很不方便,

而且得到的总人数还不真实(包含了一个没有实际用处的人)

 

解决方案:

把getCount()方法定义为类的静态方法!

类的静态方法:

  1. 可以直接通过类来访问【更常用】,也可以通过对象(实例)来访问。

  2. 在类的静态方法中,不能访问普通数据成员和普通成员函数(对象的数据成员和成员函数)

Human.h

#pragma once
......
class Human {
public:
    ......
	static int getCount();
    ......
};

Human.cpp

......

//静态方法的实现,不能加static
int Human::getCount() { 
	//  静态方法中,不能访问实例成员(普通的数据成员)
	// cout << age;

	// 静态方法中,不能访问this指针
	// 因为this指针是属于实例对象的
	// cout << this;

	//静态方法中,只能访问静态数据成员
	return count;
}
......

main.cpp

void test() {
	cout << "总人数: ";
	// ??? 没有可用的对象来访问getCount()

	// 直接通过类名来访问静态方法!
	// 用法:类名::静态方法
	cout << Human::getCount(); 
}

int main(void) {
	Human h1, h2;

	test();

	system("pause");
	return 0;
}

1)静态数据成员

对象的成员函数(没有static的成员函数)内部,可以直接访问“静态数据成员”

类的静态成员函数(有static的成员函数)内部,可以直接访问“静态数据成员”

即:所有的成员函数,都可以访问静态数据成员。

类不能直接访问普通的静态数据成员(Human::humanCount 非法)

2)静态成员函数

对象可以直接访问静态成员函数

类可以直接访问静态成员函数(Human::getHumanCount())

在类的静态成员函数(类的静态方法)内部,不能直接访问this指针和对象的数据成员!

在类的静态成员函数(类的静态方法)内部,只能访问类的数据成员

const数据成员

怎样表示人类的血型 怎样表示人的“血型”?

血型可以修改吗?

解决方案:

把血型定义为const数据类型(常量数据成员)

const数据成员的初始化方式:

  1. 使用类内值(C++11支持)

  2. 使用构造函数的初始化列表

(如果同时使用这两种方式,以初始化列表中的值为最终初始化结果)

注意: 不能在构造函数或其他成员函数内,对const成员赋值!

//初始化列表
Human::Human():bloodType("未知") {
	name = "无名氏";
	age = 18;
	salary = 30000;
	HumanCount++;
	
}
#pragma once
......
class Human {
public:
    ......
private:
    ......
	const string bloodType;
};
// 使用初始化列表,对const数据成员初始化
Human::Human():bloodType("未知") {
     ......

	//在成员函数内,不能对const数据成员赋值
	//bloodType = "未知血型";
	count++;
}

void Human::description() const {
	cout << "age:" << age
		<< " name:" << name
		<< " salary:" << salary
		<< " addr:" << addr 
		<< " bloodType:" << bloodType << endl; //其他成员函数可以“读”const变量
}

const成员函数

需求分析:

const的Human对象,不能调用普通的成员函数。

分析:

C++认为,const(常量)对象,如果允许去调用普通的成员函数,而这个成员函数内部可能会修改这个对象的数据成员!而这讲导致const对象不再是const对象!

解决方案:

如果一个成员函数内部,不会修改任何数据成员,就把它定义为const成员函数。

c++的成员函数设计建议:如果一个成员函数不修改任何数据成员,那么强烈建议把成员函数定义为const成员函数。

int main(void) {

	const Human rock ;
	rock.description();

	return 0;
}


void Human::description()const {

	cout << "name" << name << endl;
	cout << "age" << age << endl;
	cout << "salary" << salary << endl;
	cout << "bloodtype" << bloodType << endl;
}

组合与聚合

说明:组合和聚合,不是C++的语法要求,是应用中的常用手段

组合

        需求:

构建一个计算机类,一台计算机,由CPU芯片,硬盘,内存等组成。

CPU芯片也使用类来表示。

 CPU.H

#pragma once
#include <string>
using namespace std;
class CPU
{
public	:
	//CPU();
	CPU(const char* brand = "inter", const char* version = "i5");
	~CPU();
private:
	string brand;
	string version;
};

CPU.CPP

#include "CPU.h"
#include <iostream>
using namespace std;
CPU::CPU(const char* brand  , const char* version  )
{
	this->brand = brand;
	this->version = version;
	cout << __FUNCTION__ << endl;
}

CPU::~CPU()
{
	cout << __FUNCTION__ << endl;
}

computer.h

#pragma once
#include "CPU.h"

class computer
{
public:
	//computer();
	computer(const char*cpuBrand,const char*cpuVersion,int hardDisk,int memory);
	~computer();

private:
	//CPU cpu;//computer和CPU就是组合
	CPU* cpu;
	int hardDisk;
	int memory;

};

computer.cpp

#include "computer.h"
#include "CPU.h"
#include <iostream>
using namespace std;

computer::computer(const char* cpuBrand,
	const char* cpuVersion, int hardDisk, int memory)//:cpu(cpuBrand, cpuVersion)
{
	//this->cpu = CPU(cpuBrand, cpuVersion);
	this->cpu = new CPU(cpuBrand, cpuVersion);
	this->hardDisk = hardDisk;
	this->memory = memory;
	cout << __FUNCTION__ << endl;
}

computer::~computer()
{
	delete cpu;
	cout << __FUNCTION__ << endl;
}

main.cpp

#include "computer.h"
#include <iostream>
using namespace std;

void test() {
	computer PC("intel", "i9", 1000, 8);
}

int main(void)
{
	test();
}


小结:

被拥有的对象(芯片)的生命周期与其拥有者(计算机)的生命周期是一致的。

计算机被创建时,芯片也随之创建。

计算机被销毁时,芯片也随之销毁。

拥有者需要对被拥有者负责,是一种比较强的关系,是整体与部分的关系。

小结:

被拥有的对象(芯片)的生命周期与其拥有者(计算机)的生命周期是一致的。

计算机被创建时,芯片也随之创建。

计算机被销毁时,芯片也随之销毁。

拥有者需要对被拥有者负责,是一种比较强的关系,是整体与部分的关系。




聚合

需求:

给计算机配一台音响。

聚合不是组成关系,被包含的对象,也可能被其他对象包含。

拥有者,不需要对被拥有的对象的生命周期负责。

UML中的组合表示:

#include "computer.h"
#include <iostream>
#include <Windows.h>
#include "VioceBox.h"
using namespace std;

void test(VoiceBox* box) {
	computer PC("intel", "i9", 1000, 8);
	PC.addVoiceBox(box);

}

int main(void)
{
	VoiceBox box;
	test(&box);
	 
}
#include "VioceBox.h"
#include <iostream>
using namespace std;

VoiceBox::VoiceBox()
{
	cout << __FUNCTION__ << endl;
}

VoiceBox::~VoiceBox()
{
	cout << __FUNCTION__ << endl;
}
#pragma once
using namespace std;
class VoiceBox
{
public:
	VoiceBox();
	~VoiceBox();

};

#include "computer.h"
#include "CPU.h"
#include <iostream>
using namespace std;

computer::computer(const char* cpuBrand,
	const char* cpuVersion, int hardDisk, int memory)//:cpu(cpuBrand, cpuVersion)
{
	this->cpu = CPU(cpuBrand, cpuVersion);
	//this->cpu = new CPU(cpuBrand, cpuVersion);
	this->hardDisk = hardDisk;
	this->memory = memory;
	cout << __FUNCTION__ << endl;
}

computer::~computer()
{
	//delete cpu;
	cout <<"调用" << __FUNCTION__ << endl;
	//delete box
}

void computer::addVoiceBox(VoiceBox* box)
{
	this->box = box;
	
}

const-error

#include <iostream>
#include <windows.h>

using namespace std;

class Man{
public:
	Man(){}
	void play() {
		cout << "I am playing ...." << std::endl;
	}
};

int main(void) {
	const Man man;
	man.play();
}

报错:

error C2662: “void Man::play(void)”: 不能将“this”指针从“const Man”转换为“Man &”

原因:

man是const对象, 但是却调用了非const方法.

解决方案: 方案1:

把 const Man man; 修改为: Man man;

方 案2:

把play方法, 修改为 const方法.

vector-error

vector加入的成员是拷贝新成员

#include <iostream>
#include <windows.h>
#include <vector>
using namespace std;

class Man {
public:
	Man() {}
	void play() {
		count += 10;
		cout << "I am playing ...." << std::endl;
	}
	int getDrinkCount() const {
		return count;
	}
private:
	int count = 0; //一共喝了多少杯酒
};

int main(void) {
	Man zhangFei, guanYu, liuBei;
	vector<Man> men;

	// push_back是把参数的值,拷贝给vector
	// men[0]的值和liubBei是相同的,但是,是两个不同的对象
	men.push_back(liuBei);


	men.push_back(guanYu);
	men.push_back(zhangFei);

	men[0].play();
	cout << men[0].getDrinkCount() << endl; //10
	cout << liuBei.getDrinkCount() << endl;  //0

	system("pause");
	return 0;
}

const-erro2

#include <iostream>
#include <windows.h>

using namespace std;

class Man{
public:
	Man(){}
	void play() const {
		cout << "I am playing ...." << std::endl;
	}
};

void play(Man &man) {
	man.play();
}

int main(void) {
	const Man man;
	play(man);
}

原因: 非const引用, 不能对const变量进行引用 注意: const引用, 可以对非const变量进行引用

解决方案: 修改引用变量, 或者被引用的变量

static-error

#include <iostream>
#include <windows.h>

using namespace std;

class Man{
public:
	Man() { count++; }
	void play() const {
		cout << "I am playing ...." << std::endl;
	}

	int getAge() {
		return age;
	}

	static int getCount() {
		getAge();   //error!
		return count;
	}
private:
	static int count;
	int age;
};

int Man::count = 0;

int main(void) {
	Man man1;
	Man man2;
	cout << Man::getCount() << endl;
	
	system("pause");
	return 0;
}

原因:

类的静态方法(static方法) 内, 不能访问实例方法(非static方法)和实例数据成员.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luciferau

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值