C++ 点滴积累(3)

本文介绍了C++编程中的关键概念,包括析构函数的调用时机,如在类内部定义的内部类及其特性,静态成员的声明、初始化与使用,以及友元函数和友元类的作用。此外,还详细阐述了常量引用、常对象、常成员函数以及预处理指令如#include、#define等的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.析构函数的调用时机:

1)} 或 文件尾

2)delete(有条件的)

3)catch()

当中途退出程序时(exit()、abort() )不调用析构函数


2.在函数内部定义的类叫内部类,内部类的成员函数只能是内联函数。


3.静态成员(类属性)

静态数据成员–用关键字static声明;

–该类的所有对象维护着同一份拷贝;

不能在构造函数中初始化,应在类外单独进行,必须在生成对象之前用(::)来指明所属的类。

静态成员函数

–类外代码可以使用类名和作用域操作符(::)来调用静态成员函数;

–静态成员函数只能引用属于该类的静态数据成员或静态成员函数。

//具有静态数据、函数成员的Point类
#include <iostream>
using namespace std;
class Point//Point类声明
{
public://外部接口
	Point(int xx=0, int yy=0) {X=xx;Y=yy;countP++;}
	Point(Point &p);//拷贝构造函数
	int GetX() {return X;}
	int GetY() {return Y;}
	static void GetC()
	{cout<<" Object id="<<countP<<endl;}
private://私有数据成员
	int X,Y;
	static int countP;
};

Point::Point(Point &p)
{
	X=p.X;
	Y=p.Y;
	countP++;
}
int Point::countP=0;
void main()//主函数实现
{
	Point A(4,5);  //声明对象A
	cout<<"Point A,"<<A.GetX()<<","<<A.GetY();
	A.GetC();     //输出对象号,对象名引用
	Point B(A);   //声明对象B
	cout<<"Point B,"<<B.GetX()<<","<<B.GetY();
	Point::GetC(); //输出对象号,类名引用
}
#include <iostream>
using namespace std;

class A
{
public:
	A(int xx):x(xx){}
	static void f(A a);
private:
	int x;
};

void A::f(A a)
{
	/* 
	静态成员函数对对象成员的访问是错误的,
	因为它是类属性,并不知晓对象的事。
	*/
	//cout<<x; //对x的引用是错误的
	cout<<a.x; //正确
}

void main()
{
	A obj(2);
	obj.f(obj);
}

静态成员的性质
静态数据成员
–对象的存储空间中不包含静态成员的位置;
–尽管可能是私有成员,也必须在类外定义和初始化;
–若是对象成员,则必然调用构造函数
静态成员函数
–静态成员函数不含有this 指针;
–静态数据成员或静态成员函数不继承;
静态成员函数不可以定义为虚函数;
静态成员函数不必重载也不必为常函数。

5.友元

– 友元是C++提供的一种破坏数据封装和数据隐藏的机制。
– 通过将一个模块声明为另一个模块的友元,一个模块能够使用到另一个模块中本是被隐藏的信息。
– 可以使用友元函数和友元类。
– 友元不是组合,定义和调用时不受类作用域限制。

== 友元函数

友元函数是在类声明中由关键字friend修饰的非成员函数,在它的函数体中能够通过对象名访问private 和protected成员;
友元函数可以是类外的普通函数,也可以是另一类的成员函数,
作用:增加灵活性,使程序员可以在封装和效率间合理选择。
若访问对象的成员,必须前缀对象名。这样可以访问类中的任何访问权限的成员。

//使用友元函数计算两点距离
#include <iostream>
#include <cmath>
using namespace std;
class Point//Point类声明
{ 
public://外部接口
	Point(int xx=0, int yy=0) {X=xx;Y=yy;}
	int GetX() {return X;}
	int GetY() {return Y;}
	friend double Distance(Point &a, Point &b);
private://私有数据成员
	int X,Y;
};

double Distance( Point& a, Point& b)
{
	double dx=a.X-b.X;
	double dy=a.Y-b.Y;
	return sqrt(dx*dx+dy*dy);
}
int main()
{
	Point p1(3.0, 5.0), p2(4.0, 6.0);
	double d = Distance(p1, p2);
	cout<<"The distance is "<<d<<endl;
	return 0;
}

== 友元类

若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员(全方位开放)。

声明语法:将友元类名在另一个类中使用friend修饰说明

友元关系是单向的如果B类是A类的友元,则B类的函数可以访问A类的私有和保护数据,但A类的成员函数却不能访问B类的私有、保护数据。(孙悟空钻进铁扇公主肚里。)

友元关系不能传递如果B类是A类的友元,C类是B类的友元,若没特别声明,则C类和A类无友元关系。(朋友的朋友不见得是朋友!)

友元关系不能继承如果B类是A类的友元,B类的子类不会自动成为A类的友元类。(借来的东西不是遗产。)

#include <iostream>
using namespace std;
class A
{ 
friend class B;
public:
	A(int xx = 1):x(xx){}
	void Display()
	{cout<<x<<endl;}
private:
	int x;
};
class B
{ 
public:
	void Set(int i);
	void Display();
private:
	A a;
};

void B::Set(int i)
{
	//访问组合对象的私有成员正是友元的“特长”。
	a.x=i;
}
void B::Display()
{
	a.Display();
}

int main()
{
	B obj;
	obj.Display();
	return 0;
}
6.“常”的种类

常引用:被引用的对象不能被更新。const 类型说明符&引用名

常对象:必须进行初始化,且不能被更新。类名const 对象名

常对象只能调用常成员函数;在常函数和非常函数并存时,非常对象只能调用非常函数,在只有常函数时,非常对象可以调用常函数

常成员(包括常数据成员和常函数成员)类型说明符 const  数据成员名 ,   返回类型    函数成员名()const

常成员函数
–使用const关键字说明的函数。
–常成员函数不更新对象的数据成员。
–常成员函数说明格式:
类型说明符函数名(参数表)const;
这里,const是函数类型的组成部分,因此在实现部分也要带const关键字。
const关键字可以被用于参与对重载函数的区分
派生时可以用const防止基类的函数被子类覆盖;
–const不能用于构造、析构(程序执行不警告)
常对象仅能调用它的常成员函数。

#include<iostream>
using namespace std;
class R
{ 
public:
	R(int r1, int r2){R1=r1;R2=r2;}
	void print();
	//const关键字可以被用于参与对重载函数的区分
	void print() const;
private:
	int R1,R2;
};

void R::print()
{
	cout<<R1<<":"<<R2<<endl;
}

//const是函数类型的组成部分,因此在实现部分也要带const关键字。
void R::print() const
{
	cout<<R1<<";"<<R2<<endl;
}
void main()
{
	R a(5,4);
	a.print(); //调用void print()
	const R b(20,52);
	//常对象仅能调用它的常成员函数。
	b.print(); //调用void print() const
}

常成员函数的实质:
在常函数:返回类型函数成员名()const 中
const 实质上是用于限定该函数所含的this指针的,即变成了“指向常对象的指针”。于是对象在调用常函数时,尽可放心。
这也回答了为什么只有非静态成员函数(因为静态成员函数不含有this 指针)才能是常函数。

常数据成员
–使用const说明的数据成员。常数据成员/常引用是通过用初始化列表完成赋值的。

#include<iostream>
using namespace std;
class A
{
public:
	A(int i);
	void print();
	const int& r;
private:
	const int a;
	static const int b; //静态常数据成员
};
const int A::b=10;
A::A(int i):a(i),r(a) {}
void A::print()
{ 
	cout<<a<<":"<<b<<":"<<r<<endl;  
}
void main()
{
	/*
	建立对象a和b,并以100和0作为初值,分别调用构造函数,
	通过构造函数的初始化列表给对象的常数据成员赋初值
	*/
	A a1(100),a2(0);
	a1.print();
	a2.print();
}
运行结果:

100:10:100
 0:10:0

常数组:数组元素不能被更新。类型说明符 const   数组名[大小]

指向常量的指针和常指针:

指向常量的指针const int a=99;const int* p = &a;亦可写为:int const * p = &a;

常指针(相当于引用)char * const p = “ABCDE”;

是指针类型的常量;
若声明指针为常量,则指针本身的值不能被改变,即该指针始终指向一个地方,不可改变。常用来作形参接受数组名实参,于是该指针便老老实实地指着该数组不变。这样指针便不可能丢失数组。

指向常量的常指针(很少用)const char *const p = "John";


7.必须使用初始化列表的场合:
(1)类成员是 引用
(2)类含有 常数据成员
(3) 被组合的类的构造函数带参,而由组合类创建对象时
(4)基类的 构造函数带参,子类创建对象时

8.“常”与函数的关系
常参数:函数的形参若是指针或引用,就会将实参完全暴露给子函数。若仅仅为了大量传送而非双向传递数据,可用const修饰引用和指针。

const 类型说明符&引用名

const 类型说明符*指针名

常函数:封锁了函数的“写”功能,不能更新数据成员。

常返回:函数返回的是个常量。通常这个常量是指针或引用。

9.改变常对象的数据
一个对象一旦定义为常对象就意味着其所的数据成员都不可更改了,可是在实际应用中会有这种情况:一个存单类,记录了户主、金额、期限等信息,当发生抵押时,户主要变化,其它项不动。
为了适应这种需求,C++提供mutable 关键字,用于修饰常对象中的可变数据成员,使得常函数可以修改它,非常函数也可以用。
mutable 关键字可用于非静态、非常、非引用的数据成员。

#include <iostream>
#include <string>
using namespace std;
class scrollbar
{
private:
	int size; //related to constness
	mutable string owner; //可变数据成员
public:
	scrollbar(int sz, string own) : size(sz), owner(own){ }
	void setSize(int sz) //changes size
	{ 
		size = sz; 
	}
	void setOwner(string own) const //是竟然可以修改的常函数
	{ 
		owner = own; 
	}
	int getSize() const //returns size
	{ 
		return size; 
	}
	string getOwner() const //returns owner
	{ 
		return owner; 
	} 
};

void main()
{
	const scrollbar sbar(60, "Window1"); //生成常对象
	cout<< sbar.getSize() << ", " << sbar.getOwner() << endl<< endl;
	// sbar.setSize(100); //can't do this to const obj
	// 常对象调用常函数,修改mutable数据成员
	sbar.setOwner("Window2");
	cout<< sbar.getSize() << ", " << sbar.getOwner() << endl<< endl;
}

10.编译预处理命令

1)#include 包含指令
–将一个源文件嵌入到当前源文件中该点处。
–#include<文件名>
按标准方式搜索,文件位于C++系统目录的include子目录下。
–#include"文件名"
首先在当前目录中搜索,若没有,再按标准方式搜索。
2)#define 宏定义指令
–定义符号常量,很多情况下已被const定义语句取代。
–定义带参数宏,已被内联函数取代。
#undef
–终止由#define定义的宏,使之不再起作用。

3)条件编译指令#if 和#endif

#if常量表达式
//当“常量表达式”非零时编译
程序正文
#endif

4)条件编译指令#else

#if 常量表达式
//当“常量表达式”非零时编译
程序正文1
#else
//当“常量表达式”为零时编译
程序正文2
#endif

5)条件编译指令#ifdef
#ifdef标识符
程序段1
#else
程序段2
#endif
如果“标识符”经#defined定义过,且未经# undef删除,则编译程序段1,否则编译程序段2。

6)条件编译指令#ifndef
#ifndef标识符
程序段1
#else
程序段2
#endif
如果“标识符”未被定义过,则编译程序段1,否则编译程序段2。

7)使用条件编译的头文件
//head.h
#ifndefHEAD_H //避免了重复编译
#define HEAD_H //此标识符被称为“哨兵”

class Point
{

}

#endif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值