C++类和对象成员函数,静态成员,构造函数和析构函数、初始化列表


C++面向对象的三大特性:封装、继承和多态。

数据成员的访问

class Point{
	double x,y;
	public:
		int a,b;
	protected:
		int c,d;
};

使用class关键字与struct不同,成员在默认情况下是私有的。

  • public:公有,可以由类的用户访问,
  • private:私有,除了对自身的成员函数外都不可见。
  • protected:保护,可以在它的类的子类中被访问。

定义成员函数

如果不想要用户直接访问数据成员,但又可以控制设置它的值,解决方案是使数据成员私有,并且依靠成员函数来提供间接访问。

class Point{
public:
	//内联定义函数
	void set_x(doublex newx){x = newx;}
	void set_y(double newy){y = newy;}
	double get_x(){return x;}
	double get_y(){return y;}
private:
	double x, y;
};
//在类声明之外定义
void Point::set_x(doublex newx){x = newx;}
void Point::set_y(double newy){y = newy;}
double Point::get_x(){return x;}
double Point::get_y(){return y;}

函数是公开的,可以被用户访问。数据成员是私有的,不能访问。
C++提供两种方式定义成员函数:

  • 内联,一个成员函数可以通过类本身中提供定义,被内联定义。函数原型紧跟要执行的语句的大括号{},与用inline关键字修饰的全局函数一样,内联函数的函数体被直接扩展到调用函数的主体中。(优化运行性能,但是占用更多内存,适用于简短函数)
  • 一个成员函数可以在类声明之外定义。在这种情况下,函数定义要求用作用域符(::)来说明函数的作用域。
    格式:
    返回类型 类名::函数名(参数){ 语句组 }
    在类声明之外定义一个成员函数能使你编写任意长的函数,而无需让类声明本身来负担。

调用成员函数

两种方式:

  • 对象.函数(参数)
  • 通过指向对象的指针:指针->函数(参数)

私有成员函数

私有成员函数和私有数据成员一样,对外隐藏,只能被类中其他函数调用。
函数成员还可以用protected被做成受保护的,此时类的子类可以参考甚至修改(重载)此函数。private和protected的区别只与涉及子类的地方有关

构造函数和析构函数

构造函数是一个初始化函数,当一个对象在内存中被分配后,它会自动被调用。它与类的名称相同,且无返回值。与其他函数一样,可以被内联,也可以在类外定义。
在其他成员函数中可以调用构造函数,但是只是产生一个匿名对象,对本对象并无影响
析构函数是清理函数。作用在对象销毁前系统自动调用,执行清理操作。
都只会被调用一次。

构造函数成员初始化列表

如果Classy是一个类,而mem1、mem2和mem3都是这个类的数据成员,则类构造函数可以使用如下的语法来初始化数据成员:

Classy::Classy(int n, int m) :mem1(n),mem2(0),mem3(n*m+2){
	...
}

上述代码将mem1初始化为n,mem2初始化为0,将mem3初始化为n*m+2,从概念上说,初始化工作是在对象创建时完成的,此时还未执行括号中的任何代码。注意事项:

  • 这种格式只能用于构造函数
  • 必须用这种格式初始化非静态const数据成员(至少在C++11之前)
  • 必须用这种格式初始化 引用& 数据成员。
  • 数据成员初始化的顺序与它们出现在类中的声明顺序相同,与排列顺序中的顺序无关。

必须用带有初始化列表的构造函数:
1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
2.const成员引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。

#include <iostream>
using namespace std;

//A 没有默认构造函数(无参构造函数)
class A
{
public:
	A(int k);
	static int e;
private:
	const int a=1;
	const int b;
	int & c;
	
};
int d = 5;
A::A(int k):b(2),c(d)
{
	//a = 1;
	cout << "a = "<<a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
	cout << "k = " << k << endl;
}

//静态成员变量的初始化:  类型+ 使用类名+作用域限定符
int A::e = 6;

class B
{
public:
	B();
	B(int k);
	

private:
	A a;
	int f;
};

//因为a无默认构造函数,所以必须在初始化列表中初始化,给a传入参数
B::B(int k):a(k)
{
}
B::B() : a(6)
{
}


int main(void) {

	A a(7);
	cout << "A::e = "<<A::e << endl;
	cout << "a.e = " << a.e << endl;

	cout << "---------------------" << endl;
	B b(9);
	return 0;
}
/*
 *结果:
a = 1
b = 2
c = 5
k = 7
A::e = 6
a.e = 6
---------------------
a = 1
b = 2
c = 5
k = 9
 */

在构造函数中使用new的注意事项

  • 如果构造函数中使用的new动态分配内存,则必须提供使用delete的析构函数。
  • new和delet必须相互兼容。new对应delete,new[ ] 对应于delete[]。
  • 如果有多个构造函数,则必须以相同的方式使用new和delete,因为只有一个析构函数,所有构造函数必须跟它兼容。

this指针

this指针指向被调用对象本身。每个非静态成员函数(包括构造函数和析构函数)都有一个this指针。this是调用对象的地址。*this才是被调用对象(解引用)。所以可以使用 this->a,或者(*this).a来调用被调用对象的数据和函数。

const成员函数

保证函数不会修改调用对象。

cosnt Stock land = Stock("Test");
land.show();

对于当前的编译器来说,会对第二行报错,因为show的代码无法保证调用对象不被修改。我们以前通过将函数参数声明为const引用,或者指向const的指针来解决这种问题。但是这里show()方法没有任何参数,它所使用的对象由方法调用隐式地提供。C++的解决方法是将const关键字放在函数括号的后面:

//函数声明
void show() cosnt;
//函数定义
void Stock::show() const

以这种方法声明和定义的函数被称为const成员函数,只要类方法不修改调用对象,就应该将其声明为const。
注意:

  • 常成员函数不能调用其他非常成员函数(静态函数除外)
  • 常成员函数可以修改静态成员变量
  • const对象只能调用常成员函数
  • 两个成员函数的名字和参数表相同,但一个是 const 的,一个不是,则它们算重载
#include <iostream>
#include <vector>
using namespace std;

class A
{
public:
	A(int aa = 0):a(aa) {
		
	}
	static int b;
	void show()const {
		cout << ++b << endl;
		setA1();
		cout << "show const" << endl;
		//setA();错误
	}
	void show() {
		cout << ++b << endl;
		setA1();
		cout << "show" << endl;

		//setA();错误
	}
	void setA() {
		cout << ++a << endl;
	}
	static void setA1() {
		cout << ++b << endl;
	}
private:
	int a;
};

int A::b = 1;
int main(void) {

	A a;
	const A ca;
	ca.show();
	a.show();
	cout << a.b << endl;
	cout << ca.b << endl;

	return 0;
}
/*
2
3
show const
4
5
show
5
5
*/

运算符重载

友元函数

通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限
创建友元函数:
第一步是将其原型放在类声明中,并在类声明前加上关键字 friend:

friend Time test(double n,Time &t);
  • 虽然该函数是在类中声明的,但它不是成员函数,因此不能用成员运算符调用。
  • 虽然友元函数不是成员函数,但是它与成员函数的访问权限相同。
    第二步是在类外编写函数的定义,不需要在定义中使用关键字friend,也不需要使用::限定符。

拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  1. 用同一类的一个已知的对象去新建并初始化该类的另一个对象
  2. 复制对象把它作为参数传递给函数,值传递—参数为对象。
  3. 复制对象,并从函数返回这个对象—返回值为对象。
  4. 编译器生成临时对象

如果在类中没有定义拷贝构造函数,编译器会自行定义一个(浅拷贝)。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数(深拷贝)。拷贝构造函数的最常见形式如下:

//const关键字是为了防止被拷贝的对象被修改
//使用引用的方式传递参数
classname (const classname &obj) {
   // 构造函数的主体
}
#include <iostream>

using namespace std;

class Line
{
public:
	int getLength(void);
	Line();
	Line(int len);             // 简单的构造函数
	Line(const Line &obj);      // 拷贝构造函数
	~Line();                     // 析构函数

private:
	int *ptr;
};

// 成员函数定义,包括构造函数
Line::Line() {
	cout << "调用无参构造函数" << endl;
	// 为指针分配内存
	ptr = new int;
	*ptr = 0;
}
Line::Line(int len)
{
	cout << "调用构造函数" << endl;
	// 为指针分配内存
	ptr = new int;
	*ptr = len;
}

Line::Line(const Line &obj)
{
	cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
	ptr = new int;
	*ptr = *obj.ptr; // 拷贝值
}

Line::~Line(void)
{
	cout << "释放内存" << endl;
	delete ptr;
}
int Line::getLength(void)
{
	return *ptr;
}

void display(Line obj)
{
	cout << "line 大小 : " << obj.getLength() << endl;
}
Line g() {
	Line line3(18);
	return line3;
}
// 程序的主函数
int main()
{
	Line line1(10);//调用普通构造函数
	Line line2 = line1;//情况1,同Line line2 = Line(line1);用类的一个已知的对象去初始化该类的另一个对象时,需要调用拷贝构造函数
	display(line1);//情况2,对象作为参数传递给函数,需要调用拷贝构造函数
	display(line2);//情况2
	Line line3 = g();//情况3,从函数返回对象
	display(line3);//情况2
	
	return 0;
}
/*
结果:

调用构造函数			//Line line1(10);
调用拷贝构造函数并为指针 ptr 分配内存			//Line line2 = line1;/
调用拷贝构造函数并为指针 ptr 分配内存			//display(line1);
line 大小 : 10									//display(line1);
释放内存										//display(line1);
调用拷贝构造函数并为指针 ptr 分配内存			//display(line2);
line 大小 : 10									//display(line2);
释放内存										//display(line2);
调用构造函数									//Line line3 = g();
调用拷贝构造函数并为指针 ptr 分配内存			//Line line3 = g();
释放内存										//Line line3 = g();
调用拷贝构造函数并为指针 ptr 分配内存			//display(line3);
line 大小 : 18									display(line3);
释放内存										display(line3);
释放内存			//line3
释放内存			//line2				
释放内存			//line1
*/

在这里插入图片描述
不要用拷贝构造函数初始化匿名对象

	Line line1(10);
	//错误,“Line line1”: 重定义
	//编译器会认为 Line (line1) == Line line1
	Line(line1);

隐式转换法

	Line line1 = 10;//等于Line line1 = Line(10);

默认情况下,C++编译器会给一个类添加三个函数:

  • 默认构造
  • 默认拷贝构造
  • 默认析构
    如果我们写了有参构造函数,编译器就不再提供默认构造;但会提供默认拷贝函数。
    如果我们写了拷贝构造函数,编译器就不再提供其他构造函数;

    拷贝构造 > 有参构造 > 默认构造

深拷贝与浅拷贝

默认的拷贝构造函数—浅拷贝。复制的是成员的值。按字节拷贝,当对象的数据资源是由指针指向的堆时,默认的拷贝构造函数只是将指针复制。 ,多个对象共用同一块资源,同一块资源释放多次,崩溃或者内存泄漏
深拷贝:深拷贝会重新开辟内存空间。,每个对象拥有自己的资源,必须显式提供拷贝构造函数和赋值运算符。

静态成员变量

#include <iostream>
using std::cout;
using std::cin;
using std::endl;
class Point
{
public:
	static int npoints;
	//特殊情况,用const和static一起修饰,只可以在类中声明一次
	const static int a = 0;
};
//static变量必须在类外定义一次,并只定义一次
int Point::npoints = 0;
int main(void) {
	Point p1, p2, p3;
	Point::npoints++;
	p1.npoints++;
	p2.npoints += 5;
	cout << p3.npoints << endl;//p3.npoints == 7
	return 0;
}

注意:不能在类声明中初始化静态成员变量,这是因为声明只描述了如何分配内存,但并不分配内存。例外情况:静态数据成员为const整数类型或枚举型。
数据通常是由单个对象所拥有的,而不是类拥有。然而它可以声明由同一个类的所有对象共享的一个或多个数据成员,无论创建了多少对象,程序都只创建一个静态类变量副本,这样的数据成员称为静态的。
成员函数也可以是静态的,静态成员函数的定义和声明和其他函数一样,但是用途受限,不能引用this指针,不能引用非静态成员函数。
静态成员可以是公有私有和受保护的。

class Student{
public:
    Student(char *name, int age, float score);
    void show();
public:
    static int m_total;  //静态成员变量
private:
    char *m_name;
    int m_age;
    float m_score;
};

static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为 m_total 分配一份内存,所有对象使用的都是这份内存中的数据。当某个对象修改了 m_total,也会影响到其他对象。

static 成员变量必须在类声明的外部初始化,具体形式为:

type class::name = value;

type 是变量的类型,class 是类名,name 是变量名,value 是初始值。将上面的 m_total 初始化:

int Student::m_total = 0;

静态成员变量在初始化时不能再加 static,但必须要有数据类型。被 private、protected、public 修饰的静态成员变量都可以用这种方式初始化。
注意:static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。
static 成员变量既可以通过对象来访问,也可以通过类来访问。
static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问

总结:

  1. 一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量,都可以引用它。

  2. static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。

  3. 静态成员变量必须初始化,而且只能在类体外进行。例如:
    int Student::m_total = 10;
    初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值。

  4. 静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。当通过对象名访问时,对于不同的对象,访问的是同一份内存。

静态成员函数

静态成员函数(static)不能通过对象来调用,也不能使用this指针。函数声明必须包含关键字static,但如果函数定义是独立的,则其中不能包含关键字static。如果静态成员函数是在公有部分声明的,则可以使用类名和作用域解析运算符::来调用它。

//在Test类中声明和定义
static int howmany(){ return num_string;}
//调用:
int count = Test::howmany;

由于静态成员函数不与特定的对象相关联。因此只能使用静态数据成员

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SOC罗三炮

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

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

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

打赏作者

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

抵扣说明:

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

余额充值