C++学习(6)对象和类

C++面向对象三大特性:封装、继承、多态
万事万物都能成为类(拥有属性、行为、访问权限)

类的注意事项

  • 类只是一个数据类型或者说是一种模板(Template),编译后不占用内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据。只有在创建对象以后才会给成员变量分配内存,这时候就可以进行赋值了。
  • 对象指针的注意点:
Student stu;
Student *pStu = &stu;

Student *pStu = new Student;

——> 在栈上创建出的对象都有一个名字,比如stu,使用指针指向它不是必须的。但是通过new 创建出来的对象就不一样了,它是在堆上分配内存的,没有名字,只能得到一个指向它的指针,所以必须使用一个指针变量接收,否则以后再也无法找到这个对象了,更无法使用它。也就是说, 使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它, 再借助指针来访问它的成员变量或成员函数

  • 栈内容是程序自动管理的,不能使用delete删除再栈上创建的对象
    堆内存由程序员管理, 对象使用完毕后可以通过 delete 删除。
    在实际开发中, new 和 delete 往往成对出现,以保证及时删除不再使用的对象, 防止无用内存堆积。

  • 类体中和类体外定义的成员函数的区别
    在类体中定义的成员函数会自动成为内联函数,在类体外定义不会
    在类体内部定义的函数默认是内联函数。

    • 这种默认的内联函数一般不是我们所期望的,它会将函数调用处用函数体替代,所以建议在类体内部对成员函数作声明,而类体外进行定义,这是一般良好的编程习惯,开发人员基本都这么做的
    • 当然吧那些函数体小、函数语句很少的放在类体中定义,让它自动成为内联函数即可。
    • 类的成员变量最好以m_开头,以看出这是个成员变量,预定俗成的东西
    • 类的成员函数中 给成员赋值的函数 以 set开头命名最好,获取成员变量的值的函数 以get开头命名最好

类class定义与使用

一般来说,类被定义在头文件中,再被cpp文件所调用,

  • 访问权限——
    public(公共权限):成员被访问, 类内可以访问,类外可以访问
    private(私有权限):成员被访问, 类内可以访问,类外不可以访问,继承中子类不能访问父类private成员
    protected(保护权限):成员被访问, 类内可以访问,类外不可以访问,继承中子类可以访问父类protected成员
  • 属性——变量——称为:成员属性or成员变量
  • 行为——函数——称为:成员函数or成员方法
  • 属性、行为:统一称为成员
  • struct和class的区别
    struct和class没有太大的区别,唯一的区别在于 默认的访问权限不同:
  • struct 默认访问权限是公共
  • class 默认访问权限是私有

test_6.h 头文件

//test_6.h
#ifndef TEST_6_H_
#define TEST_6_H

#include<string>

class MyClass //以MyClass作为类名称
{
public:
	void sell();
	void test();
	
private:
	std::string company;
	double buy;
	void set_() { buy =2*2; }
	void set_2();//定义成私有函数
//以上的,类的成员可以是数据也可以是函数
};

#endif // !TEST_6_H_

的关键词 class 指出这些代码定义了一个类设计

1、访问控制

即:上面代码上的 publicprivate 关键词,其是对类成员的访问控制

2、控制对成员的访问:共有还是私有

(1)无论类成员是数据成员还是成员函数,都可以在类的公有部分的或者私有部分中声明——即:类内可以任意访问

(2)通常,数据项通常放在私有部分,组成类接口的成员函数放在公有部分——定义优化

(3)在定义类时,关键词 private 是可以不写的,因为默认访问控制属于私有的,即

class MyClass
{//私有
	std::string company;
	double buy;
public://共有
	void sell();
	void test();

};

4)处于公有(public)下的函数是可以被其他文件调用的,但是私有(private)只能被当前文件调用。

3、实现对类成员函数

test_6.cpp 文件,对应上面的 test_6.h 的头文件。

(1)该 test_6.cpp 可以只用于存储 类成员函数的定义,其他文件调用使用的话,通过包含头文件即可。

(2)类的成员函数可以调用其类的私有函数

//test_6.cpp
#include <iostream>
#include "test_6.h"
using namespace std;//使用using编译指令使得所有名称都可用

void MyClass::sell()
{
	cout << "类函数sell():" << endl;
	set_();//类的成员函数可以调用私有函数
}

void MyClass::test()
{
	cout << "类函数test():" << endl;
}

inline void MyClass::set_2()//采用内联函数进行定义
{
	cout << "私有函数调用" << endl;
}

(3)位于类中的成员函数都将自动成为内联函数,为了当然也可以使用 inline 进行标注为内联函数

4、类的使用
最简答的方法是:
(1)声明类变量,句点符合调用函数:

MyClass kyline;//接上面.h文件定义好的 Myclass 类,定义变量
kyline.test();//采用句点调用类函数

注意这样声明属于默认情况下的。

(2)联合起来

test_6.h 声明类及成员
test_6.cpp 存储类成员函数的定义内容
main.cpp

//main.cpp
#include <iostream>
#include"test_6.h"

int main()
{
	//实例化类对象
	MyClass kyline1;//默认构造函数调用,此时不要加(),若加了()编译器会将其认为为函数声明
	kyline.sell();//这样就属于在访问类的函数,sell()函数拥有公共访问权限
	kyline.test();
	return 0;
}

联合运行后的输出结果:
类函数sell():
私有函数调用
类函数test():

:反正一个工程中只能有一个文件包含 main() 函数即可,运行从其开始。

5、成员函数
成员变量、成员函数 两者是分开存储的

class MyClass
{
}
MyClass my;

如上,创建一个空对像my,占用内存是 1个字节,
C++ 编译器会给每个空对象分配 1 个字节的空间,为了区分空对象占用内存的位置
每个空对象有一个独一无二的内存地址

当类中有内容时:

class MyClass
{
	int  mA; //非成员变量 属于类的对象上
	static int mB;//静态成员函数 不属于对象上,所以类的大小不加上mB
	void func(){;}//非静态成员函数,与成员变量分开存储,也不属于类的对象上,类的大小也不加上func
	static void func2(){}//静态成员函数,也不属于类的对象上,类的大小也不加上func2
}
MyClass my;

结论:只有非静态成员变量属于类对象上,其他类型数据都不属于类的对象上,所以类的大小只取决于非成员函数的大小。

构造函数

构造函数 应该可以说就是对 类的初始化,且特点如下:

  • 类什么名,构造函数就是什么名

  • 没有返回值,不能写void

  • 构造函数可以有参数,也可以发送重载(同时出现多个同名但是参数不同的构造。。。)

  • 创建的类时,构造函数自动调用,且只调用一次(且访问权限在public)

  • 构造函数的分类:
    参数: 有参数构造、无参数构造
    类型: 普通构造、拷贝构造(MyClass(const MyClass &a){})

  • 上创建对象时,实参位于对象名后面,例如Student stu("小明", 15, 92.5f)

  • 上创建对象时,实参位于类名后面,例如new Student("李华", 16, 96)

  • 构造函数必须是 public 属性的,否则创建对象时无法调用

  • 一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成

  • 调用方式:即:

  • 括号法:

MyClass kyline2(10);//有参数构造函数调用
MyClass Kyline3(Kyline2);//拷贝构造函数
  • 显示法:
MyClass kyline4 = MyClass(10);//调用有参数函数
MyClass kyline5 = MyClass(kyline4);
  • 隐式转换法:
//隐式转换法:
	MyClass p = 10; //相当于 MyClass p = MyClass(10);
  • 构造函数的调用规则
    1、创建一个类,C++会默认出现三个操作:
    (1)默认构造函数(空实现状态)
    (2)默认析构函数(空实现状态)
    (3)默认拷贝构造函数(对类进行只拷贝的操作,但是其他操作不做
    (4)重载赋值运算符(浅拷贝)
    2、如果我们写了有参构造或拷贝,编译器不在提供默认。

  • 浅拷贝
    浅拷贝就是对类的成员进行值赋值,所以当类A使用了指针和new关键字定义(int *he = new int();)了一个堆区时,浅拷贝进行会将类A的指针所指向的堆区地址复制到新的拷贝类B中,此时类A的指针保存的内容和类B的指针保存的内容都是一样的,都是一个地址(即同一个堆区),又要明白如果自定义了堆区,该堆区需要自己手动在析构函数中使用delete函数将其释放掉,此时问题就出现了:当我们释放掉类B时,类B经过拷贝构造初始化的指针所指向的堆区先被释放掉,而类A再释放时,类A的指针所指的内容还是同一个堆区,所以就出现了对一个堆区进行两次释放的问题,这是不允许的。
    该问题由深拷贝解决

#include <iostream>
using namespace std;

class person
{
public:
	person();//默认构造
	person(int a);
	~person();
	//person(const person &p);//不定义,类自动定义,拷贝构造

private:
	int age;
	int *higt = new int();
};

person::person()
{

}
person::person(int a)
{
	age = a;
}
//不定义拷贝函数,类会自主进行以下拷贝构造操作:
//person::person(const person &p)
//{
//	age = p.age;
//	higt = p.higt;
//}
person::~person()
{
	//手动释放掉堆区
	if (higt != NULL)
	{
		delete(higt);
		higt = NULL;
	}

}
void test()
{
	person p1 = person(10);
	person p2 = person(p1);//浅拷贝构造,析构释放会出错,high指针所指堆区重复释放

}
int main()
{
	test();
    std::cout << "Hello World!\n";
}
  • 深拷贝
    就是在对类A 指针所指地址进行拷贝时,不拷贝类A的地址给类B,而是重新创建一个新地址给类B的指针,这样就避免了类A指针所指地址被重复释放的问题了。
    例:
//自己实现一个深拷贝操作;
person::person(const person &p)
{
	age = p.age;
	higt = new int(*p.higt);//深拷贝,新堆区
}
  • 初始化列表
class person
{
public:
	person(int a0 ,int a, int b, int c,int *d);
	~person();

private:
	int age;
	int A;
	int B;
	int C;
	int *higt = new int();
};
//初始化列表
person::person(int a1,int a, int b, int c,int *d) :age(a0),A(a), B(b), C(c),higt(d)
{
}
void test()
{
	person p(1,2,3,4,5);
}

test_6.h

#ifndef TEST_6_H_
#define TEST_6_H

#include<iostream>

class MyClass
{
public:
	void sell();
	void test();
	/******普通构造*******/
	MyClass(const std::string &cp,double buy_);//这里就是构造函数定义处
	
	//里面的参数根据 private 中的需要初始化的参数来定
	MyClass(doube a)
	{
		buy = a;
	}
	/*********/
	
	/*******拷贝构造********/
	//拷贝构造写法,拷贝一份一样的类,在做进行新操作,但是不能对拷贝的进行修改,所以要用const
	MyClass(const MyClass &a)
	{
		buy = a.buy;
		company = a.company;
	}
	/**********************/
	
private:
	std::string company;
	double buy;
	void set_() { buy =2*2; }
	void set_2();//定义成私有函数

};

#endif // !TEST_6_H_
#pragma once

test_6.cpp 中增加的地方:


MyClass::MyClass(const string &cp, double buy_)
{
	company = cp;
	buy = buy_;
	cout <<"构造函数输出1:"<< buy << endl;
	set_();
	cout << "构造函数输出2: "<<buy << endl;
}

main.cpp 中修改的地方

#include <iostream>
#include"test_6.h"

int main()
{
	std::string str = "qkqk";
	MyClass kyline = MyClass(str,20);//因为采用了构造函数方式,就不能进行默认定义了
	//或者 :MyClass kyline(str, 20);
	
    //实例化类对象——括号法
	MyClass kyline1;//默认构造函数调用,此时不要加(),若加了()编译器会将其认为为函数声明
	MyClass kyline2(10);//有参数构造函数调用
	MyClass Kyline3(Kyline2);//拷贝构造函数
	
	//显示法:
	MyClass kyline4 = MyClass(10);//调用有参数函数
	MyClass kyline5 = MyClass(kyline4);
	
	MyClass(10);//创建匿名对象,特点:这行代码执行完后,该匿名对象会立马被系统回收点,且不能用拷贝构造初始化匿名函数,会报错

	//隐式转换法:
	MyClass p = 10; //相当于 MyClass p = MyClass(10);

	kyline.sell();
	kyline.test();
	return 0;
}

:因为采用了构造函数方式,MyClass 就不能进行默认定义了,需要自己给初始化

  • 不能用拷贝构造初始化匿名函数,会报错.

静态成员函数与静态成员

  • 对象的 this 指针对对象得静态成员变量与静态函数没有任何意义,不能在static 成员函数与变量上使用
  • 故我们可以使用静态成员变量来实现多个对象共享数据的目标。静态成员变量是一种特殊的成员变量,其被关键字 static 修饰。比如:有时候我们希望在多个对象之间共享数据,对象 a 改变了某份数据后对象 b 可以检测到。
    示例
    共享数据的典型使用场景是计数,以前面的 Student 类为例, 如果我们想知道班级中共有多少名学生,就可以设置一份共享的变量,每次创建对象时让该变量加1
  • static 成员变量必须在类声明的外部初始化,具体形式为:
type class::name = value;

静态成员变量在初始化时不能再加 static , 但必须要数据类型。被private、protected、 public 修饰的静态成员变量都可以用这种方式初始化。访问权限还是继续遵循这三个规则
如:

class Student{
public:
	static int m_total;
};
int Student::m_total = 0;

注意
1)static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的static 成员变量不能使用。
2)static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可访问。
3)普通成员函数可以访问所有成员(包括成员变量与函数),静态成员函数只能访问静态成员
4)静态成员函数与普通成员函数的根本区别在于:普通成员函数有this指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态变量与静态函数)

class Person
{
public:
	static void func()//静态成员函数
	{
		mA = 10;//静态成团函数可以访问静态成员变量
		//mB = 10;//错误,静态成员函数不可以访问非静态成员变量
	}
	
	int mB;
	static int mA;
}
void test()
{
//静态函数的访问方式
	//访问方式1:
	Person p;
	p.func();
	//访问方式2:
	Person::func();
}

析构函数

解释一下:
用构造函数创建对象后,程序负责跟踪该对象,知道其过期为止。
对象过期时,程序将自动调用一个特殊的成员函数,即——析构函数,其作用是 完成清除工作,析构函数不能有参数

  • 系统自动调用
  • 没有返回值,没有参数
  • 析构和构造是系统自动定义有的,只是我们没有定义时,析构与构造为空
  • 不可以重载
    形式:
~MyClass();

MyClass类型的析构函数就是在前面加一个“~”,需要知道的是析构函数没有参数,
MyClass的析构函数不承担任何重要的工作,所以可以将它编写为不执行任何操作的函数:

MyClass::~MyClass()
{
}

:什么时候调用析构函数,这由编译器来决定,通常不应在代码中显式地调用析构函数。
正如 Qt Creator 提供的原始编程模板上就是,会自动添加,

//.h
~Widget();
//.cpp
Widget::~Widget()
{
    delete ui;
}

this 指针

这个说白 this就是指向所在区域的类的指针
其本质是:this 指针是指针常量,即指针的指向是不可以修改的
即:

void MyClass::test()
{
	cout << "类函数test():" << endl;
	set_();
	this->sell();//这个类显然是指向的 MyClass 类 然后调用其中的函数
}

特别说明,在类的成员函数声明后面加上 const 关键字的含义:
即:成员函数隐含传入的this指针为const指针,即说明在该成员函数中,任意试图修改它的类的成员的操作都是不允许的。非成员函数的声明不能在其后加const ,编译不会通过的
如:

class MyClass
{
public:
	void sell();
	void test() const;
}
  • 使用场景:
    (1)解决名称冲突
class person
{
public:
int a;
};
person::person(int a)
{
	//a = a;//错误
	this->a = a;//正确
}

(2)返回对象本身用*this
//链式编程

class person
{
public:
perosn(int A):a(A)
{
}

person personADD1(const person &p)//叠加
{
	this->a += p.a;
	return *this;//this是指向类的指针,*this 就是类对象的本体
}
person& personADD2(const person &p)//使用引用的方式,不断创建新的类
{
	this->a += p.a;
	return *this;//this是指向类的指针,*this 就是类对象的本体
}

int a;
};
void test()
{

}
int main()
{
	person p(10);
	person p2;
	p2.personADD1(p).personADD1(p).personADD1(p);//叠加3次,10+10+10+10=40
	p2.personADD2(p).personADD2(p).personADD2(p);//重复创建3次新类,最终输出值看最后一次的操作,即10+10=20
	return 0;
}

const修饰成员函数与成员变量

const 修饰成员变量
(1)被const 修饰的成员变量的初始化的唯一方法就是使用初始化列表

const 成员函数特性
(1)const 成员函数可以使用类中的所有成员变量, 但是不能修改它们的值,这种措施主要还是为了保护数据而设置的。

(2)在成员函数的后面加上const ,就将该函数变成了一个常函数

char *getname() const;
int getage() const;

(3)区分一下const 的位置:

  • 函数开头的 const 用来修饰函数的返回值,也表示返回值是const 类型,不能被修改
  • 函数的结尾加上 const 表示常函数, 这种函数只能读取成员变量的值,不能修改成员变量的值

加了const后,就相当于在这个函数中,对MyClass * const this进行了新的操作:const MyClass * const this;前者限定了this这个指针,后者在前者基础上还限定了this指针所指向的值(对象本体)

class MyClass
{
public:
	void sell();
	//加了const后,就相当于在这个函数中,对MyClass * const this进行了新的操作:const MyClass * const this;
	//加了const 后,该函数中就不能在进行对类对象(this所指)的属性进行修改了
	void test() const 
	{
		age = 100;//错误
		this->age  = 100;//与上一句意思一样,还是错误
		ageB = 10; //正确,ageB加了mutable
	}
	void fun()
	{
	}
int age ;
mutable int ageB;  //特殊变量,即使在常函数中,也可以修改这个值
}
int main()
{
	const MyClass M;
	//M.age= 10;//错误,常对象的值不能修改
	M.ageB = 10;//正确,ageB前面加了mutable,所以修改
	M.test();//正确,test是常函数
	//M.fun();//错误,fun不是常函数

	return 0;
}
  • 在成员变量前加const,该变量就变成了常对象
  • 常对象只能调用常函数

对象数组

	MyClass zzz[3] ={
		MyClass("q",10),
		MyClass("x",20),
		MyClass("s",30)
	};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LionelMartin

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值