越秃越强的C++

第一章 导论

  • 汇编语言:将机器指令映射为一些助记符。如ADD,SUB等。抽象层次低,需要考虑机器细节。
  • 面向过程语言:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一个个实现,使用的时候依次调用就行。
  • C++的源程序是以【.cpp】为后缀的。即:C Plus Plus
  • 面向对象语言:是把构成问题的事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个问题的步骤中的行为。
  • 类:抽象出同一类对象的共同属性和行为,形成类。
  • 封装:隐蔽对象的内部细节,对外形成一个边界,只保留有限的对外接口,使用方便,安全性好。
  • 继承:意义在于代码复用,改造,扩展已有的类。
  • 多态:同样的消息作用在不同对象上引起不同的行为。
  • 源程序:用源语言(高级语言)写的,有待翻译。
  • 目标程序:完全的二进制机器语言。
  • 可执行程序:连接目标程序以及库中的某些文件生成一个可执行文件。例如:.exe文件。
  • 三种类型的翻译程序:
    (1)汇编程序:将汇编语言源程序翻译成目标程序。
    (2)编译程序:将高级语言源程序翻译成目标程序。一次翻译。
    (3)解释程序:将高级语言源程序翻译成机器指令,它是边翻译边执行。
  • 信息的存储单位
    (1)位(bit,b):数据的最小单位,表示一位二进制信息。
    (2)字节(byte,B):八位二进制数字组成。1byte = 8 bit。
    (2)转换关系:1 KB = 1024 B。1 MB = 1024 K。
  • 软件 = 程序+文档
  • “<<”:流插入运算符,将内容插入到输出流中去。
  • “>>”:提取运算符,作用在流对象cin上。
  • using namespace std;命名空间,可以避免命名冲突。所有标准库里面的名字有一个命名空间,这个空间叫std。如果不用命名空间,每次使用标准库中的东西,都必须要std::
  • 字符串:C语言使用字符数组来存放字符串,C++推荐使用String类存放字符串。
  • 全局变量会自动初始化,局部变量不会自动初始化。
  • const定义符号常量:
    1. 语法:const 类型名 常量名 = 常量值;,必须初始化。例如:const double PI = 3.1415926;
    2. C语言中有#define定义的常量。语法:#define PI 3.1415。没有分号。const有分号。
  • '\n':表示换行,注意是单引号。
  • 调试:这是断点,执行逐过程(F10)。

第二章 Hello World

  • a + =3;等价于a = a+3;x * = y+7;等价于x = x*(y+7);
  • 逗号运算符:连接两个表达式。先计算逗号左边的表达式,再计算右边,最终结果为表达式二的值。?
  • 运算符优先级:“<,>,>=,<= ”的优先级高于“==,!=”。“!”高于“&&”高于“||”。
  • 条件表达式:x = a>b?a:b;
  • sizeof运算:是求一个变量或一种数据类型所占的字节数。语法:int len = sizeof(int);
  • 位运算:
    (1)按位与(&):将两个数字化成二进制,每一位对齐,进行与运算。用途:将某一位置0,其他位不变。例如:将char类型变量a的最低位置0:a = a & 0xfe;。0xfe:11111110。
    (2)移位(<<,>>):左移(<<):移位后,低位补0,高位舍弃。相当于乘以2。右移(>>):低位舍弃,高位如果是无符号就补0,有符号就补符号位。相当于除以2.
  • 隐式类型转换:低精度转换为高精度。
  • 显式类型转换:语法:类型转换操作符<类型说明符>(表达式);。类型转换操作符:static_cast。例:static_cast<int> (z);
  • 调试,设置断点,监视变量。
  • C++没有输入输出语句,我们使用I/O流来输入输出。cin>>a>>b;cout<<a<<b<<endl;
  • 常用的I/O流类库操纵符:#include<iomanip>,就像C中的格式化一样。
操作符 含义
dec 数值数据采用十进制表示
hex 数值数据采用十六进制表示
oct 数值数据采用八进制表示
ws 提取空白符
endl 插入换行符,并刷新流
ends 插入空字符
setprecision(int) 设置浮点数的小数位数
setw(int) 设置域宽
  • 例如:cout << setw(10) << setprecision(8) << 3.1415;cout <<oct <<10 << endl;
  • if语句:实现选择结构。
  • switch语句:如果case语句后面没有break,就会依次执行。语句最后是default语句,当所有case语句都不瞒足时,执行default语句。【注意】:如果某个case:后面不写语句,它就和下面的一个case共用一组语句。
  • while语句:实现循环语句。避免死循环。
  • do-while语句:至少执行一次。while和do-while因题选择。
  • for语句:处理循环次数已知的循环。
  • break语句和continue语句:break跳出最内存的循环。continue结束当前循环。
  • const关键字:
    (1)用来定义常量。语法:const int MAX = 23;
    (2)用来定义常量指针:不可通过常量指针修改其指向的内容。语法:const int* p = &n;。之后不能进行*p = 5;//编译出错的操作,但是p可以再指向别的变量。p = &m;是可以的。【注】不能把常量指针赋值给非常量指针(如果非要这么做,可以进行强制类型转换),反过来可以。
    (3)定义常量指针的作用:用作函数参数,可以避免函数内部不小心修改参数指针所指的地方。
void fun(const char* p1)
{
	strcpy(p1, "hoooo");
	cout << p1 << endl;
}
  • 自定义类型:为已有类型起别名。枚举类型就是自定义类型。
    (1)typedef语句。语法:typedef 已有类型名 新类型名;。例如:typedef double B;
    (2)using语句。语法:using 新类型名 = 已有类型名;。例如:using A = int;。C++中的。
  • 枚举类型:不能对枚举类型进行赋值(除非是在大括号内进行初始化),它是一个常量。整数值不能直接赋值给枚举变量,需要进行强制类型转换。枚举常量可以给整数值赋值。枚举集是整数的一个子集。语法:enum num {a,b,c,d};//默认a=0,b=1······,枚举类型是整数的子集。
#include<iostream>
using namespace std;
enum num{a=3,b,c,d};//默认a=0,后面累加1
int main()
{
	num t;//定义枚举类型的变量
	for (int i = a; i<=d;i++)
	{
		if (i == 0)
			cout << "这是0" << endl;
		else if (i == 1)
			cout << "这是1" << endl;
		else if (i == 2)
			cout << "This is 2" << endl;
		else
			cout << "This is 3" << endl;
	}
	return 0;
}
  • auto类型:编译器通过初始值自动判断变量的类型。例:auto a = val1 + val2;,a的数据类型取决于val1+val2的类型。
  • decltype类型:定义一个变量与某一表达式的类型相同,但并不使用该表达式初始化变量。decltype(i) j = 2;,表示j以2作为初始值,类型与i一样。

第三章 函数

a r c t a n ( x ) = x x 3 3 + x 5 5 x 7 7 + arctan( x) = x-\frac{x^3}{3}+\frac{x^5}{5}-\frac{x^7}{7}+···
π = 16 a r c t a n 1 5 4 a r c t a n 1 239 π = 16arctan\frac{1}{5}-4arctan\frac{1}{239}
s i n ( x ) = x 1 ! x 3 3 ! + x 5 5 ! = n = 1 ( 1 ) n 1 x 2 n 1 ( 2 n 1 ) ! sin (x)=\frac{x}{1!}-\frac{x^3}{3!}+\frac{x^5}{5!}···=\sum_{n=1}^{\infty}(-1)^{n-1}\frac{x^{2n-1}}{{(2n-1)}!}
1 0 15 10^{-15} :表示为1e-15
整数相除结果为整数。如果想得到小数,可以这样写:double A = 1/ 2.0;

  • void srand(unsigned int seed);,seed是产生随机数的种子。功能:为使rand()产生一系列伪随机数而设置一个起点。
  • 随机数:int a = rand();。如果不设置种子(没有用srand()函数),它生成的就是伪随机数(每次都一样)。设置种子之后,产生由种子生成的随机数。

int a = rand()%11;,可以产生[0,10]之间的随机数。

  • 函数的递归调用:函数自己调用自己。注意,递归出口很重要!!!递推–回溯
  • 函数模板:
    1. 是指建立一个通用函数,其函数类型和形参类型都不具体指定,用一个虚拟的类型来代替。
    2. 凡是函数体相同的函数都可以用这个模板来代替不必定义多个函数,只在模板处定义一次就可以。
    3. 语法:template<typename T>//可以用class替换typename
#include<iostream>
using namespace std;
template<typename T>//可以用class替换typename
T max(T a, T b)
{
	if (a > b)
		return a;
	else
		return b;
}
int main()
{
	int x, y,max_i;
	double c, d,max_d;
	cin >> x >> y;
	cin >> c >> d;
	max_i = max(x, y);
	max_d = max(c, d);
	cout << "int型:" << max_i << endl;
	cout << "double型:" << max_d << endl;
	return 0;
}
  • 函数的参数传递:单向传递、双向传递
    (1)在函数被调用时才分配形参的存储单元。
    (2)单向传递:值传递是传递参数值。值或对象。不能改变实参。
    (3)双向传递:引用传递可以实现双向传递。【作用】:当需要返回多个值时使用。
  • 引用类型:【作用】:引用可以作为函数的参数,实现双向传递。【注意】:和C的指针类似,但有区别。指针是指向地址,引用是使用别名。
    (1)引用(&)是标识符的别名。相当于一个变量的别名。
    (2)定义一个引用时,必须同时对它进行初始化,使它指向一个已存在的对象或变量。
    (3)一个引用一旦被初始化,就不能改为指向其他对象。
    (4)引用可以作为函数的返回值。
    (5)作为常引用:int n; const int & r = n;,r的类型就是const int &。不能通过常引用去修改其引用的内容。
    (6)引用的使用范围:
    1. 不能定义void类型的引用:如:void &a = 9;
    2. 不能建立引用的数组。如:char c[6]="hello";char &rc = c;
    3. 不能定义指向引用类型的指针变量。如:int& *p = &a;
    4. 可以定义指针变量的引用。如:int i = 5; int *p = &i; int* &pt = p;
    5. 可以使用const对引用进行限定,但不允许改变该引用的值。int i = 5; const int &a = i; a = 10;//错误,但是可以改变i的值:如:i=10;
    6. 可以用常量或表达式对引用进行初始化。如:int a = 10; const int& b = a;//用a去初始化b。
int n=0;
int & Set()
{
	return n;
}
int main()
{
	Set()=40;
	cout<<n;
	return 0;
}//输出40
int a=4,b=5;
int &r=a;//r1是a的引用
r1=b;//是把b的值赋值给r1,也就是赋值给a。
#include<iostream>
using namespace std;
void swap1(int a, int b)//
{
	int t;
	t = a;
	a = b;
	b = t;
}
void swap2(int& a, int& b)//表示a和b是两个引用
{						//表示ab准备好了要给调用它的实参作别名。
	int t;
	t = a;
	a = b;
	b = t;
}
int main()
{
	//int i, j;
	//int &ri = i;//定义int引用ri,并初始化为变量i的引用。ri和i指向同一个东西
	//j = 10;
	//ri = j;//相当于i=j

	int a = 5, b = 10;
	cout << "值传递:" << endl;
	cout << "a=" << a <<"  "<< "b=" << b << endl;
	swap1(a, b);
	cout << "a=" << a <<"  "<< "b=" << b << endl;
	cout << "y引用传递:" << endl;
	cout << "a=" << a << "  " << "b=" << b << endl;
	swap2(a, b);//调用时,和值传递调用一样
	cout << "a=" << a << "  " << "b=" << b << endl;
	return 0;
}
  • 作用域运算符:“::”,当全局变量和局部变量重名是,可以使用“::”l来访问全局变量。
  • 含有可变参数的函数:C++有两种方法
    (1)如果所有的的实参类型相同,可以传递一个名为initializer_list的标准库类型。
    (3)initializer_list:是一个类模板,用于表示某种特定类型的值的数组。头文件:#include<initializer_list>
    (4)使用initializer_list时,要在后面加一对尖括号,写出类型参数。例:initializer_list<String> ls;,表示initializer_list的元素类型都是String类型的,就是说,它包括了一组String构成的参数表。
    (5)initializer_list的对象中的元素永远是常量值。
    (6)含有initializer_list类型的形参的函数也可以同时拥有其他类型的形参。
    (7)initializer_list使用列举:可以使用initializer_list编写一个错误信息的输出函数(异常处理)。
    (2)如果实参的类型不同,我们可以编写一个可变参数的模板。
  • 内联函数:为了减少函数调用的开销。【作用】:在调用简单函数时提高效率。
    (1)定义:在函数定义前面直接加【inline】关键字。
    (2)执行过程:不产生调用语句,直接把内联函数的代码插入到调用的地方。
    (3)注意:内联函数体内不能有循环语句和switch语句。内联函数的定义必须出现在内联函数第一次被调用之前(使编译器在调用之前就见过它)。对内联函数不能进行异常接口声明。
inline int fun(int a,int b)
{
	if (a > b)return a;
	else return b;
}
  • CONSTEXPR函数:用来初始化常量的,给的参数是常量,它的返回值一定是常量。
  • 函数的缺省参数:定义函数的时候可以让最右边的连续若干个参数有缺省值,在调用函数时,若相应位置不写参数,那参数就是缺省值。
    (1)目的:提高程序的可扩充性。即:如果写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,就可以使用缺省参数来避免对原来那些函数调用语句的修改。
    (2)如果函数声明在调用之前,可以在声明处给出函数的默认参数,下面定义函数时,就不能再给默认值了,即使一样也不行。如果函数定义在调用之前,可以直接给默认参数,如下。
void func(int a,int b = 1,int c = 3)
{
	```
}
//调用
func(10);//相当于func(10,1,3);
func(10,8);//相当于func(10,8,3);
func(10,,9);//出错,只能最右边的连续缺省。
  • 函数重载:多个函数名字相同,函数的参数个数或参数类型不同。不能以函数返回值来区别函数。即:参数列表不同。静态的多态性机制,在编译是就实现。

  • 代码调试时,使用“逐语句(F11)功能”,进入调用函数的内部。

  • 比较两个浮点数是否相等时用abs()函数。看两个数的差的绝对值是否小于一个很小的数。

  • C语言中的动态内存分配是通过malloc和free关键字实现的。

  • C++的动态内存分配:用new和delete代替malloc和free。
    (1)用new运算法实现动态内存分配

    1. 第一种用法,分配一个变量。例:p = new T;。T是任意类型名,P是类型为T*的指针。动态的分配出一片大小为Sizeof(T)字节的内存空间,并把该内存空间的起始地址赋值给P。例:int *a = new int;
    2. 第二种用法:分配一个数组。例:p = new T[];。T为任意类型名,P是类型为T*的指针,N是要分配的数组元素的个数。动态的分配出一块大小为NxSizeof(T)子节的内存空间,并把该内存空间的起始地址赋值给P。如:int *p = new int[10];

    (2)使用delete运算符释放动态分配的内存。

    1. 用“new”动态分配的内存空间,一定要用“delete”运算符来释放。释放操作只能进行一次。
    2. 语法:delete 指针;//该指针必须是被new出来的空间
    3. 释放动态分配的数组。语法:delete [] 指针;

第四章 类和对象

1 结构体
2 联合体
3 枚举类

扫描二维码关注公众号,回复: 9949877 查看本文章
  • 面向对象=类+类+类+……+类。
  • 定义对象时要用【构造函数】对对象进行初始化。
  • 删除对象时要用【析构函数】释放资源。

面向对象的特点:

  1. 抽象:对同一类对象的共同属性和行为进行概括,形成类。
  2. 封装:将抽象出来的数据,代码封装在一起,形成类。通过类声明中的大括号{}实现封装。
  3. 多态:同一个名称,不同的功能实现方式。【目的】:达到行为标识统一,减少程序中标识符的个数。
  4. 继承:在已有类的基础之上进行扩展,形成新的类。

4.1 类和对象的定义

  • 对象是类的实例化。定义类的对象,才可以通过对象使用类中定义的功能。
  • 类和结构体变量一样,对象所占用的内存空间的大小等于所有成员变量的大小之和。
  • 使用类的成员变量和成员函数:
    (1)用法一:对象名.成员名
    (2)用法二:指针->成员名。(就像C语言中指针一样,定义一个该类型的指针指向该变量)
    (3)用法三:引用名.成员民。
  • 类成员的可访问范围:
修饰符 含义
public 公有成员,可以在任何地方访问
private 私有成员,只能在类内访问
protected 保护成员,<待更新>
  • 定义类的语法:使用calss关键字。
class 类名称
{
	public:
		公有成员(外部接口)
	private:
		私有成员
		//int a = 1;//类内的初始值
	protected:
		保护型成员

};
  • 如果没有写修饰符,默认是private。
  • 设置私有成员的机制,叫做隐藏。隐藏的目的是强制对成员变量的范围一定要通过成员函数进行。
  • 成员函数可以重载也可以有缺省函数值。
  • 可设置类内的初始值,如果构造函数没有对该属性进行初始化,就使用类内的初始值,如果构造函数对其初始化,就使用构造函数的初始值。
  • private修饰的成员,只允许本类中的函数使用。类外部任何函数都不能访问(除非手动授权)。
  • 定义对象的语法:类名 对象名;,例:Clock myClock;
  • 类中的成员之间使用成员名来访问,类外访问成员要使用“对象名.成员名”的方式来访问public成员。
  • 类的成员函数:
    (1)在类中声明函数原型,在类外给出函数体实现,并在函数名前使用类名加以限定。语法如下:void man::fun(int x, int y, int z)
    (2)也可以直接在类体中给出函数体,形成内联成员函数。【注意】:对于简单的函数可以声明为内联函数形式,内联函数体中不要有复杂的结构(循环和switch语句)。【方式】:1. 将函数体放在类中。2. 使用inline关键字,在类体中声明函数原型,在类外实现函数,用inline。
    (3)允许声明重载函数和带默认参数的函数。
#include<iostream>
#include<cmath>
using namespace std;
class man
{
	public:
		int a,b,c;
		void fun(int a=1,int b=2,int c=3);//声明函数原型,带默认参数
		void dis();

};
void man::fun(int x, int y, int z)//函数的实现,
{								//如果在下面调用函数时不给参数,就使用默认参数
	a = x;
	b = y;
	c = z;

}
void man::dis()
{
	cout << a << " " << b << " " << c << endl;;
}
int main()
{
	man a;
	a.fun();
	a.dis();
	return 0;
}

4.2 构造函数

  • 对对象进行初始化,用到构造函数。
  • 如果我们没有定义构造函数,编译器会自动加一个默认的无参的构造函数。
  • 构造函数:
    (1)类体中特殊函数,用来描述对象的初始化算法。
    (2)规则:函数名与类名一样,不能定义返回值类型,不能有return语句,可以有形参,也可以没有,可以是内联函数,可以重载,可以带默认参数。
    (3)构造函数在对象创建时被自动调用。例:Clock myClock(1,2,3);
    (4)不带参数的构造函数和带默认参数的构造函数不能同时出现。
    (5)= default;:如果程序已经定义了构造函数,但是我们希望编译器隐含默认构造函数,就可以使用= default;。例:Clock()=default;
  • 构造函数在对象数组中的使用:
    (1)例:A arr[2];A arr_2[3] = { 2,5 };
    1. 第一个数组arr调用无参的默认构造函数去初始化两个对象。
    2. 第二个数组arr_2是调用一个参数的构造函数。用2去初始化arr[0],用5去初始化arr[1],用0去初始化arr[2]。
    3. 指针数组里面的元素是指针。没有初始化,就不会生成。
  • 构造函数也可以看做一个类型转换构造函数:
    (1)例:
class Demo
{
	public:
		int a;
		Demo(int i)
		{
			a=i;
		}

};
Demo d;
d = 4;

(2)会将4自动转换为一个临时对象赋值给d。这个临时对象在执行完这句后就会自动调用析构函数。
(3){}是一个变量的生存期的范围界限。{}内的对象,在出这个{}时,就自动调用析构函数。
(4)静态局部变量(对象)在函数调用结束时仍然保持其结果。直到整个程序结束。【static】就是静态的意思。
(5)先初始的,后析构。

#include<iostream>
#include<cmath>
using namespace std;
class man
{
	public:
		int a,b,c;
		man(int x, int y, int c);//声明构造函数,不能有返回值类型,直接写类名就可
		man();//默认构造函数
		void dis();//输出函数

};
//构造函数的实现,注意格式
man::man(int x, int y, int z):a(x), b(y), c(z) {}
//默认构造函数实现
man::man() : a(0), b(1), c(0) {}
void man::dis()
{
	cout << a << " " << b << " " << c << endl;;
}
int main()
{
	man A(12,13,14);//实例化对象,自动调用有参构造函数
	A.dis();//输出a,b,c。
	man B;//调用无参的默认构造函数
	B.dis();
	return 0;
}

【注意】:构造函数有两种类型:

  1. 一种是有冒号的,在冒号后面进行初始化。
  2. 另外一种是没有冒号的,在函数体中进行初始化。

两种的区别:

  1. 冒号初始化是给数据成员分配内存空间时就进行初始化。就是说,在进入下面的函数体之前,这个数据成员就已经被初始化。用于常量和引用的初始化。
  2. 在函数体中初始化,是在所有的数据成员分配完内存空间之后才进行初始化。这个不能用于常量和引用的初始。因为常量和引用在定义时就必须被初始化。

如果不是常量和引用的数据类进行初始化,这两种没有区别。注意语法。
如果是对数组进行初始化,则应当在函数体内进行。

  • 委托构造函数:使用类的其它构造函数执行初始化过程。【优点】:使代码容易修改。

  • 复制构造函数:【作用】:用一个已存在的对象去初始化同类型的另外一个对象。
    (1)用已存在的对象的引用做另外一个的参数。
    (2)复制构造函数是一种特殊的构造函数,其形参为本类对象的引用(别名,用&)
    (3)复制函数被调用的三种情况:

    1. 定义一个对象时,即:以本类的另外一个对象作为初始值,发生复制构造
    2. 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象(就是普通调用函数时,将实参赋值给形参),发生复制构造
    3. 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数,发生复制构造。

    (4)如果没有写复制构造函数,编译器会自动生成一个复制构造函数。【功能】:用初始值对象的每个数据成员,去初始化将要建立对象的对应的数据成员。
    (5)如果不希望对象被复制,使用“=delete;”指示编译器不生成默认构造函数。man(const man &p)=delete;
    (6)对象之间的赋值不会调用复制构造函数。(赋值和初始化是不一样的)
    (7)对象作函数参数,会调用复制构造函数,这样会产生较大的开销。所以,就可以使用引用类型作为为参数。如果想要确保实参值不会在被调函数体内被修改,可以使用常引用。const &。

class 类名
{
	public:
	类名(形参);//构造函数
	类名(const 类名 &对象名);//复制构造函数,使用const可以避免改变原有对象,常引用。
}
类名::类名(const 类名 &对象名)
{函数体}
//复制构造函数的使用
#include<iostream>
#include<cmath>
using namespace std;
class Man
{
	public:
		int a,b,c;
		Man(int x, int y, int c);//声明构造函数,不能有返回值类型,直接写类名就可
		Man(const Man &m);//如果不写,编译器会自动生成
		void dis();//输出函数

};
//构造函数的实现,注意格式
Man::Man(int x, int y, int z):a(x), b(y), c(z) {}
void Man::dis()
{
	cout << a << " " << b << " " << c << endl;;
}
//复制构造函数实现,可以不写,编译器会自动生成。
Man::Man(const Man &m)
{
	a = m.a + 1;//我们不直接复制,做一些改变
	b = m.b + 1;
	c = m.c + 1;

}
void fun1(Man A)//形参为对象,将实参复制给形参发生复制构造函数
{
	cout << "A.a=" << A.a << endl;
}
Man fun2()//返回值为对象
{
	Man m(1,2,3);
	return m;
}
int main()
{
	Man A(12,13,14);//实例化对象,自动调用有参构造函数
	cout << "对象A:";
	A.dis();//输出a,b,c。
	Man B(A);//用A来初始化B。第一次调用复制构造函数
	cout << "用对象A初始化之后的对象B:";
	B.dis();//输出的值比A的对应多1
	cout << "对象B的a值:";
	fun1(B);//对象B作函数参数,第二次调用复制构造函数

	Man C(0, 0, 0);
	cout << "对象C的a值:";
	C = fun2();//函数返回值是对象,第三次调用复制构造函数
	cout << "C.c = " << C.c << endl;
	return 0;
}

4.3 析构函数

  • 一个对象在实例化时,它可能会占用很多系统资源,所以,在用完之后,可以使用析构函数进行资源释放。
  • 如果程序中未声明析构函数,编译器会自动生成一个默认的析构函数,它的函数体为空。
  • 析构函数:
    (1)语法:~类名();
    (2)析构函数没有参数,没有返回类型。
~Man();//析构函数的声明

//析构函数的实现
Man::~Man()
{

}

这里是引用

  • new出来的对象,不使用delete就不会自动析构。所以,这里是p的两个对象和a被析构。p2不会自动调用析构函数。

4.4 类的组合

  • 一个类的对象可以作为另外一个类的成员。(相当于一个大的零件中加一个小零件)
  • 组合类的构造函数:不仅要对本类中的基本类型成员数据初始化,也要对对象成员初始化。
  • 有成员对象的类叫做封闭类。
    (1)任何生成封闭类对象的语句,都要让编译器明白,对象中的成员对象是如何初始化的。
    (2)具体做法就是:通过封闭类的构造函数的初始化列表。
  • 语法:
类名::类名(对象成员所需形参,本类对象形参):对象1(参数),对象2(参数)……
{
	//函数其他语句
}

【注意】:在定义类时,尽量注意要多写一个默认无参构造函数,使得类可以被广泛使用(作为另外一个类的成员时,如果组合类不写构造函数,那这个成员对象,就只能调用自己的默认无参构造函数,这时,如果我们给这个类没有定义无参默认构造函数,即:都是有参数的构造函数,编译器就会报错)。

  • 构造组合类对象时的初始化次序:
    (1)首先对构造函数初始化列表中列出的成员(基本类型成员和对象成员)进行初始化,成员在类体中定义的次序:

    1. 先执行所有对象成员的构造函数,然后执行封闭类的构造函数。
    2. 成员对象构造函数的调用次序:先声明者先构造
    3. 初始化列表中未列出的成员对象,调用默认无参构造函数进行初始化。

    (2)出来完初始化列表之后,在执行构造函数的函数体。
    (3)在封闭类的对象消亡时,先执行封闭类的析构函数,然后再执行对象成员的析构函数。

#include<iostream>
using namespace std;
class Node
{
public:
	int x, y;//点的xy坐标
	Node();//无参构造函数
	Node(int a, int b);//有参构造函数
	Node(const Node& node);//Node的拷贝构造函数
};
//Node无参构造函数实现
Node::Node() : x(0), y(0)
{

}
//Node有参构造函数
Node::Node(int a, int b) : x(a), y(b) 
{
	cout << "Node的有参构造函数" << endl;
}
//Node的拷贝构造函数的实现
Node::Node(const Node& node) : x(node.x), y(node.y) 
{

	cout << "Node的拷贝构造函数" << endl;
}
class Line
{
public:
	Node st, end;
	Line(Node p1, Node p2);//线段的有参构造函数
	Line(const Line& L);//Line的拷贝构造函数
	void showLine();//划线函数

};
//线段的有参构造函数
Line::Line(Node p1, Node p2) :st(p1), end(p2) {}
Line::Line(const Line& L) : st(L.st), end(L.end) 
{
	cout << "Line的拷贝构造函数" << endl;
}
void Line::showLine()
{
	cout << "起点:" << "x=" << st.x << ',' << "y=" << st.y << endl;
	cout << "终点:" << "x=" << end.x << ',' << "y=" << end.y << endl;
}
int main()
{
	Node p1(1,1), p2(9,9);
	Line L(p1, p2);//从后往前
	L.showLine();
	Line L2(L);//用L去初始化L2
	L2.showLine();
	return 0;
}

输出:
Node的有参构造函数
Node的有参构造函数
Node的拷贝构造函数
Node的拷贝构造函数
Node的拷贝构造函数
Node的拷贝构造函数
起点:x=1,y=1
终点:x=9,y=9
Node的拷贝构造函数
Node的拷贝构造函数
Line的拷贝构造函数
起点:x=1,y=1
终点:x=9,y=9

【代码说明】:

在执行Line L(p1, p2);这一句时,是先进行形实结合(把主函数的p1,p2与Line::Line(Node p1, Node p2) 的p1,p2形实结合,先p2再p1),调用两次Node拷贝构造函数,再进行st(p1), end(p2)的初始化,这的初始化又是用对象去初始化,所以,又调用两次的Node拷贝函数。

  • 【两个类相互引用】:用到前向引用声明
  • 类必须先声明后使用,如果需要在某个类的声明之前就引用该类,则应进行前向引用声明。前向引用声明只为程序引入了一个标识符,但具体声明在其他地方。
    (1)注意事项:
    1. 在提供一个完整的类声明之前,不能声明该类的的对象,也不能在内联函数中使用该类的对象。
    2. 当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的细节。
class B;//前向引用声明
class A
{
public :
	void fun(B b);//参数为B类型
};
class B
{
public :
	int a;
	void fun(A a);
};
  • 【UML】:
  • UNL有三个基本的部分:
    (1)事物(Things)
    (2)关系(Relationship)
    (3)图(Diagrams)

4.5 静态成员

什么是静态成员?

  • 静态成员:在说明前面加了static关键字修饰的成员。例:static int a;。有静态成员变量和静态成员函数。
    (1)静态成员变量:为每个对象共享。sizeof不会计算静态成员变量的大小。
    (2)静态成员函数:普通函数必须具体作用于某个对象,即:通过对象来调用函数。静态成员函数并不具体作用于某个对象。
    (3)静态成员不需要对象就可以访问。普通的访问方法也是可以。【类名::成员名】
    (4)静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。
    (5)静态成员函数本质上是全局函数。
    (6)设置静态成员的目的:

    1. 将和某些类紧密相关的全局变量和函数写到类中,看起来像一个整体,易于维护和理解。

    (7)所有的静态成员变量必须拿到所有的函数外面进行声明或初始化。
    (8)在静态成员函数中,不能访问非静态的成员变量,也不能调用非静态的成员函数。

  • 常量对象:使用const关键字定义的对象。例:const Demo obj;
    (1)常量对象上面不能使用非常量的成员函数。

  • 常量成员函数:在类的成员函数说明后面可以加上const关键字,则该成员函数成员常量成员函数。例:void Demo::fun1() const {……}
    (1)特点:常量成员函数在执行期间不应修改其所作用对象。因此,在常量函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类非常量的成员函数(静态函数除外)。
    (2)两个同名的函数。一个函数是常量函数,另外一个不是,这也算是重载。

4.5 结构体

  • C++的结构体是一种特殊的类
    (1)类的默认访问权限是private。
    (2)C++结构体的默认访问权限是public。
  • C++结构体的使用:
    (1)定义主要用来保存数据,而没有太多的函数(可以有函数)。
    (2)将一些数据集成在一起,可以在外部很方便的访问。
  • 结构体的定义:
struct 结构体名 {
	公有成员//默认
protected:
	保护型成员
private:
	私有成员
};
  • 结构体的初始化:
    (1)如果:

    1. 一个结构体的全部数据成员都是公有的。
    2. 没有用户定义的构造函数。
    3. 没有基类和虚函数。

    (2)初始化:类型名 变量名 = {成员数据1的初始值,成员数据2的初始值,……};

4.6 联合体

  • 联合体的目的是存储空间的共用
  • 联合体的定义:
union 联合体名称 {
	公有成员
protected:
	保护型成员
private:
	私有成员
};
  • 联合体的特点:
    (1)成员公用同一组内存单元。
    (2)如何两个成员不会同时有效。(比如定义性别时)
  • 无名联合体:只是说明联合体内的成员公用一个存储单元,同一时间只能有一个有效
union  {
	int i;
	int j;
};
//在程序中直接像普通的变量使用。如下:
i = 10;
j = 2.2;

4.7 枚举类

  • 枚举类也称为强类型的枚举
  • 语法:enum class 枚举类型名 : 底层类型 {};。这里的底层类型默认是int型。
  • 枚举类的优势:
    (1)强作用域:使作用域限制在枚举类中。使用时:枚举类型名::变量。
    (2)转换限制:枚举类对象不可以与整型隐式的相互转换。
    (3)可以指定底层类型。

#include<iostream>
#include<string>
using namespace std;
enum CPU_Rank{p1=1,p2,p3,p4,p5,p6,p7};
enum RAM_Type { DDR2=2, DDR3, DDR4 };//RAM类型
class CPU
{
private:
	CPU_Rank rank;
	string name;//名字
public:
	CPU(CPU_Rank r, string name_1);//CPU构造函数
	~CPU();//析构函数
	void run() { cout << "CPU开始运行" << endl; };
	void stop() { cout << "CPU停止运行" << endl; };
};
CPU::CPU(CPU_Rank r1, string name_1) :rank(r1), name(name_1)
{
	cout << "构造CPU" << endl;
};
CPU::~CPU()
{
	cout << "析构CPU" << endl;
}
class RAM
{//容量,类型,主频
private:
	int capa;//容量
	RAM_Type ty;//类型
	int a;//主频
public:
	RAM(int cpap_1,RAM_Type ty_1);//构造函数
	~RAM() { cout << "析构了一个RAM" << endl; };//析构函数
	void run() { cout << "RAM开始运行" << endl; };
	void stop() { cout << "RAM停止运行" << endl; };

};
RAM::RAM(int cpap_1, RAM_Type ty_1) :capa(cpap_1), ty(ty_1) 
{
	cout << "构造了一个RAM" << endl;
}
class COMPUTER
{
private:
	CPU my_cpu;
	RAM my_ram;
	int size;//机械硬盘,GB
public:
	COMPUTER(CPU c, RAM r, int s);
	void Run();
	void Stop();
};
void COMPUTER::Run()
{
	my_cpu.run();
	my_ram.run();
	cout << "computer开始运行" << endl;
}
void COMPUTER::Stop()
{
	my_cpu.stop();
	my_ram.stop();
	cout << "computer停止工作" << endl;
}
COMPUTER::COMPUTER(CPU c, RAM r, int s) :my_cpu(c), my_ram(r), size(s)
{
	cout << "构造一个computer" << endl;
}
int main()
{
	CPU a(p1, "英特");
	a.run();
	a.stop();
	cout << "………………\n" << endl;

	RAM r(1024, DDR3);
	r.run();
	r.stop();
	cout << "………………\n" << endl;

	COMPUTER com(a, r, 2048);
	com.Run();
	com.Stop();
	return 0;
}
  • string 类:字符串
    (1)s == t:判断字符串s和t是否相等
    (2)s < t:判断字符串s是否小于t,按字典序判断
    (3)s + t:连接两个字符串
    (4)s[i]:访问串中下标为i的字符
  • 字符串数组:string name[10];。代表10个字符串元素
  • getline():可以出入整行字符串。(头文件:string)
    (1)getline(cin,str):从键盘输入一行字符串,包括空格。以换行作为结束符
    (2)getline(cin,str,’,’):以“,’”作为结束标记,遇到“,”时,表示输入结束。

静态成员变量和

  • 静态成员:使用static关键字定义的成员变量。

·················································每周更新···············································

发布了59 篇原创文章 · 获赞 47 · 访问量 5495

猜你喜欢

转载自blog.csdn.net/qq_44755403/article/details/104383192
C++