C++编程
统一声明:
博客转载 声 明 : 本博客部分内容来源于网络、书籍、及各类手册。
内容宗旨为方便查询、总结备份、开源分享。
部分转载内容均有注明出处,如有侵权请联系博客告知并删除,谢谢!
百度云盘提取码:统一提取码:ziyu
个人网站入口:http://www.baiziqing.cn/
一、C++ 概念
编译工具 g++ 源文件扩展名cpp
//编译c++程序与C语言
#include <iostream>
using namespace std;
#include <stdio.h>
int main(int argc, char *argv[])
{
cout << "hello world\n";
printf("open\n");
}
二、C / C++的差异性
C语言是面向过 程编程,C++是面向对象编程。
2.1、相对于C/ C++ 有更为严格的类型检查
2.2、常变量 const只读变量
C语言中 ,const修饰的变量的数值、不可以该变量改变、但是可以通过非 const指针修改。
此处:&a 的数据类型为 const int *
c++中,const 修饰一个变量,表示该变量永远只读,只能使用,不能修改const 修饰的变量为只读变量
错误:c++ 有更为严格的类型检查,不能将 const int * 转换成 int *
//第一种差异性,常变量
#include <stdio.h>
int main()
{
/* C语言中,const修饰的变量的数值、不可以该变量改变、但是可以通过非 const指针修改。 */
const int a = 13;
//a += 12;
int *p = (int *)&a; //此处:&a 的数据类型为 const int *
*p += 3;
printf("a: %d\n", a);
}
//第一种差异性,常变量
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
/* c++中,const 修饰一个变量,表示该变量永远只读,只能使用,不能修改 const 修饰的变量为只读变量 */
const int a = 13;
//a++;
//错误:c++ 有更为严格的类型检查,不能将 const int * 转换成 int *
//int *p = &a; //error
int *p = (int *)&a;
*p += 3;
cout << "a: " << a << endl; //:13
}
2.3、强制类型转换
int b = int(a); //c++中的类型强制转换
int c = int(a + b); //c++中的类型强制转换
//第二种差异性,强制内型转
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
double a = 13.1111;
int b = int(a); //c++中的类型强制转换
int c = int(a + b); //c++中的类型强制转换
}
2.4、变量的引用
2.4.1、引用:就是为已存在的变量,取别名,是一种新的数据类型。
注意:
1、取别名时,必须赋值,但不能为常量取别名。
2、数据类型 & 变量名 = 变量;
3、函数参数传参也可以使用 引用
4、在函数被调用时,才会启用形参取别名
2.4.2、补充:
引用必须初始化
引用在初始化后,不可以改变
不能返回局部变量的引用
如果函数做左值,那么必须返回引用
引用的本质在c++内部实现是一个指针常量
//第三种差异性,变量的引用
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
/*引用:就是为已存在的变量,取别名,是一种新的数据类型。 注意: 1、取别名时,必须赋值。 2、数据类型 & 变量名 = 变量; 3、函数参数传参也可以使用 引用 */
int a = 13;
int &b = a; //&b:引用;为a变量取别名
//int &c = 123; //error 不能为常量取别名
cout << "&b: " << &b << endl; //:&b: 0xbf8f6348
cout << "&a: " << &a << endl; //:&b: 0xbf8f6348
b += 2;
cout << "&a: " << a << endl; //:&a: 15
int c = 11;
swap(a, c); //:a c 同在一个存储空间
cout << "a: " << a << "c: " << c << endl; //:a: 11c: 15
}
void swap(int &x, int &y) //在函数被调用时,才会启用形参取别名
{
int tmp = x;
x = y;
y = tmp;
}
2.5、函数重载
2.5.1、对一个函数名重新赋予它的新含义,使一个函数名可以多用。
c++中的函数重载,函数名相同,但参数列表必须不同
(1)、参数个数不同
(2)、参数个数相同、类型不同
使用的时候看实参来调用,函数的返回值不可以作为函数重载的条件
//函数重载和调用实列
/* c++中的函数重载,函数名相同,但参数列表必须不同 1、参数个数不同 2、参数个数相同、类型不同 使用的时候看实参来调用 */
#include <iostream>
using namespace std;
void sum()
{
cout << "18\n";
}
void sum(int a)
{
cout << "23\n";
}
void sum(int a, int b)
{
cout << "28\n";
}
void sum(int a, int b, int c)
{
cout << "33\n";
}
void sum(float a, float b)
{
cout << "38\n";
}
int main(int argc, char *argv[])
{
int a = 13;
sum(); //:18
sum(12); //:23
sum(1, 3); //:28
sum(1, 3, 5); //:33
sum(float(1.1), float(1.2)); //:38 c++ 小数点默认double类型
}
2.6、默认参数
2.6.1、c++的函数可以带默认参数:
默认参数设置规则:
1、只能在函数声明时,设置默认参数,定义时不能写默认值
2、默认参数只能从参数列表的最右开始,依次往左赋值,否则error 中间不能跳跃。
2.6.2、注意:
1、默认参数结合函数重载,在调用时,容易参数歧义(二义性)。
//函数默认参数
#include <iostream>
using namespace std;
int a;
int add() //H 16
{
cout << __LINE__ << endl; //H 18
}
int add(int x = 5) //H 21
{
cout << __LINE__ << endl; //H 23
}
int add(int x, int y, int z = 100)
{
cout << __LINE__ << endl; //H 28
}
int add(int x, int y, int &c = a) // H 33
{
cout << __LINE__ << endl; //H 35
}
/* c++的函数可以带默认参数: 默认参数设置规则: 1、只能在函数声明时,设置默认参数,定义时不能写默认值 2、默认参数只能从参数列表的最右开始,依次往左赋值,否则error 中间不能跳跃。 注意: 1、默认参数结合函数重载,在调用时,容易参数歧义(二义性)。 2、如果要对引用类型的形参 赋值,那么必须使用以存在的 全局变量;对应33 H */
int main(int argc, char *argv[])
{
add(1, 2, 3); //:28
add(1, 2); //:28
add(6); //:23
//add(); //: error //有歧义的,16行和20行,编译器不知道该调用那行
}
2.7、内联函数
2.7.1、内联函数,只在声明时使用 inline 说明。
(1)注意:
1、频繁使用的函数
2、函数代码语句很少(<= 5局)
3、函数代码语句简单,没有控制语句(循环语句,switch)
(2)目的:
1、优点:提升代码运行效率;缺点:目标程序体积较大
//内联函数
#include <iostream>
using namespace std;
inline int add(int a, int b); //声明一个内联函数
/* 内联函数,只在声明时使用 inline 说明。 注意: 1、频繁使用的函数 2、函数代码语句很少(<= 5局) 3、函数代码语句简单,没有控制语句(循环语句,switch) 目的: 提升程序运行时的效率,缺点就是 目标程序体积变大 */
int main(int argc, char *argv[])
{
cout << add(1, 2) << endl; //:3
//cout << add(1 + 2) << endl; //:error
}
int add(int x, int y) //内联函数的定义
{
return x+y;
}
2.8、结构体
2.8.1、c++ 中为了保护数据的安全性,引入了访问权限限定符。
2.8.2、结构体变量初始化、引入了构造函数
2.8.3、释放结构体变量占用资源、引入了构造函数
//成员函数的访问使用 1
#include <iostream>
using namespace std;
/* c++中,为了保护数据的安全性。 1、引入 访问权限限定符:public \ protected \private 2、结构体 默认为 public 访问权限 3、为了访问数据成员,引入成员函数 4、由于成员函数也在 Demo 作用域中,所以在函数体内部可以直接使用 成员变量 */
struct Demo{
public: //公有的才有被访问的权限,才能够被外部结构体.号进行访问。
void setValue(int a, int b); //成员函数的声明
int getX();
int getY()
{
return y;
}
private: //访问权限限定符,私有的
int x, y; //x和y为私有成员变量(也被称为数据成员)
};
void Demo::setValue(int a, int b) //成员函数的定义
{
x = a;
y = b;
}
int Demo::getX()
{
return x;
}
int main(int argc, char *argv[])
{
struct Demo obj;
obj.setValue(1, 3);
cout << obj.getX() << endl; //:1
cout << obj.getY() << endl; //:3
}
//成员函数的访问使用 2
#include <iostream>
using namespace std;
/* c++中,为了保护数据的安全性。 1、引入 访问权限限定符:public \ protected \ private 2、结构体 默认为 public 访问权限 3、为了访问数据成员,引入成员函数 4、由于成员函数也在 Demo 作用域中,所以在函数体内部可以直接使用 成员变量 5、当成员函数局部变量名与 成员变量名一致,引入特殊的 this 指针指向成员变量,区分局部变量和成员变量 6、结构体的每一个成员函数都默认拥有 this 指针,该指针指向当前调用函数的结构体变量 7、结构体变量的定义初始化,引入 构造函数: 1、没有数据类型,也就是该函数没有返回值 2、函数名与结构体名一致 3、该函数不能被 结构体变量调用,只能在定义结构体变量时,被系统调用 8、释放结构体变量占用的资源,引入 析构函数: 1、没有数据类型,也就是该函数没有返回值 2、函数名 由 ~结构体名 构成 3、该函数不能有参数 4、该函数不能被 用户调用,只能在结构体变量 生命周期 结束时被 系统调用,或者遇见 delete 时,系统调用 */
#define pri() cout<<__LINE__<<"func:"<<__func__<<endl; //构造函数重载
struct Demo{
public: //公有的才有被访问的权限,才能够被外部结构体.号进行访问。
void setValue(int a, int b); //成员函数的声明
int getX();
int getY()
{
return y;
}
/*构造函数*/
Demo() //构造函数
{
pri(); //:45func:Demo H45
}
Demo(int x) //带参数构造函数
{
pri();
}
Demo(int x, int y) //带参数构造函数
{
this-> x = x;
this-> y = y;
pri(); //:55func:Demo H55
}
/*构造函数*/
~Demo() //析构函数
{
pri(); //:69func:~Demo 调用了两次 因为有两个结构体变量,调用两次
// 69func:~Demo
}
private: //访问权限限定符,私有的
int x, y; //x和y为私有成员变量(也被称为数据成员)
};
#if 0
void Demo::setValue(int a, int b) //成员函数的定义
{
x = a;
y = b;
}
#endif
void Demo::setValue(int x, int y) //成员函数的定义
{
cout << this << endl; //0xbfd205b8 //参考第6点
this-> x = x; //参考第5点
this-> y = y;
}
int Demo::getX()
{
return x;
}
int main(int argc, char *argv[])
{
struct Demo obj;
cout << &obj << endl; //0xbfd205b8 //参考第6点
obj.setValue(1, 3);
cout << obj.getX() << endl; //:1
cout << obj.getY() << endl; //:3
/*结构体定义初始化,需要构造函数构造*/ //参考第7点
struct Demo obj1(2, 5); //结构体定义初始化
cout << obj1.getX() << endl; //:2
cout << obj1.getY() << endl; //:5
}
三、内存模型及名字空间
3.1、作用域
3.1.1、局部域
3.1.2、名字空间域
3.1.3、类域
3.1.4、变量的作用域:
局部变量
全局变量
3.2、链接性
3.2.1、链接性:描述名称如何在各个单元中共享。
外部链接:名称可以在文件间共享
内部链接:名称仅仅能在一个文件中的函数共享。
//链接性
#include <iostream>
using namespace std;
int a = 100;
int add() //内联函数的定义
{
return a;
}
#include <iostream>
using namespace std;
int a = 100;
int main(int argc, char *argv[])
{
cout << a << endl; //:100
}
//外部链接
#include <iostream>
using namespace std;
int a = 100;
int add() //内联函数的定义
{
return a;
}
//使用外部链接、链接其他文件定义初始化好的全局变量
#include <iostream>
using namespace std;
extern int a;
extern int add();
int b = 13;
int c = 10;
int main(int argc, char *argv[])
{
cout << a << endl; //:100
cout << add() << endl; //:100
int b = 11;
cout << b << endl; //:11 //打印局部
cout << ::a << endl; //:13 //::作用域访问符
extern int c; //引用全局变量 c
cout << c << endl; //访问全局变量 c //:10
c+=1; //使用全局变量 c
cout << c << endl; //:11
cout << ::c << endl; //:11
//int c = 14; //error 因为c变量被连接到了该函数,就不能在定义c
}
//c文件
#include <stdio.h>
int add(int x, int y)
{
return x+y;
}
//编译:gcc -c add.c
//编译得到一个add.o文件
//用链接c函数
#include <iostream>
using namespace std;
extern "C" int add(int, int);
int main(int argc, char *argv[])
{
cout << add(1, 3) << endl; //:4
}
//编译:g++ extern.cpp add.o
3.3、动态内存
3.3.1、new
在堆区开辟一个空间
3.3.2、delete
释放开辟的堆空间
//在堆区开辟一个空间 运算符:new delete
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int *p;
p = new int; //在堆区开辟一个int类型的空间,将1空间首地址给p指针
*p = 13;
cout << *p << endl; //:13
int *q = new int(20); //在堆区开辟一个int类型空间,初值为20
cout << *q << endl; //:20
delete p; //释放空间
/*开辟一个数组空间*/
int *buf = new int[5]; //在堆区开辟 数组空间,最多存5个int型元素
buf[0] = 1;
buf[1] = 2;
cout << *(buf) << *(buf + 1) << endl; //:1 2
delete []buf; //释放数组空间
}
3.4、声明与作用域
3.4.1、声明区,可以进行声明的区域
namespace MySpace{ //给这片声明区取名字 MySpace
}
3.4.2、名字空间
::
3.4.3、潜在的作用域
从声明点、到声明区结尾
//声明使用空间
#include <iostream>
using namespace std;
int a = 10;
int b = 20;
void demo()
{
cout << __LINE__ << endl; //:21 //H 29
}
namespace MySpace{
//给这片声明区取名字 MySpace
int a = 13;
void demo()
{
cout << __LINE__ << endl; //:29 //H 29
}
}
namespace B{
//给这片声明区取名字 MySpace
int b = 22;
void demo()
{
cout << __LINE__ << endl; //:38 //H 38
}
}
int main(int argc, char *argv[])
{
cout << a << endl; //:10
//方式一 :声明使用名字空间中的变量
using MySpace::a;
cout << a << endl; //:13
cout << a << endl; //:13 //声明这个空间中的都会调用名空间里面变量
cout << ::a << endl; //:10
//方式二:直接使用名字空间中的变量或函数
using MySpace::demo; //声明此行及以后都是用 MySpace 中的 demo 函数
demo(); //:29
::demo(); //:21 //调用全局不知名的 demo 函数
B::demo(); //:38 //使用 名字空间 B 中的函数 demo
}
四、输入输出流
4.1、输出流
//输出流
#include <iostream>
//using namespace std;
int main(int argc, char *argv[])
{
//cout << "hello Word" << endl; //:error
std::cout << "hello Word" << std::endl; //:hello Word
}
4.1.1、引用头文件:
#include
using namespace std;
4.1.2、基本形式如下:
cout << 表达式1 << 表达式2 << …;
4.1.3、换行:
控制符:endl
换行符:\n
4.1.4、一个cout语句可以分写若干行
#include <iostream>
using namespace std;
int main (int argc, char *agv[])
{
cout << "hello world" << endl;
cout << "hello"
"world"
<< endl;
return 0;
}
4.1.5、cout输出时,系统会自动判别输出数据的类型
#include <iostream>
using namespace std;
int main (int argc, char *agv[])
{
int a 123:
double b 456.789;
char c= 'a';
cout << a << b << c << endl;
return 0;
}
4.2、输入流
4.2.1、引用头文件:
#include
using namespace std;
4.2.2、基本形式如下:
cin >> 变量1 >> 变量2 >> …;
#include <iostream>
using namespace std;
int main (int argc, char *agv[])
{
int ival;
char ch:
cin >> ival >> ch
cout << ival << ch << endl;
return 0
}
4.2.3、换行:
控制符:endl
换行符:\n
4.2.4、一个cin语句可以分写若干行
4.2.5、cout输入时,系统会自动判别输人数据的类型
4.3、格式化控制符
//格式化控制符 1
#include <iostream>
#include <iomanip>
using namespace std;
int main(int argc, char *argv[])
{
int a = 50;
float b = 50.01;
char c = 50;
cout << "a = " << a << "\n"; //:50
cout << "b = " << b << endl; //:50.01
cout << "c = " << c << endl; //:2
cout << showpos << a << endl; //:+50 //正整数前显示”+”符号
cout << right << setw(8) << a << endl; //: +50 //输出数据在本域宽范围内右对齐
cout << left << setw(8) << a << endl; //:+50 //输出数据在本域宽范围内左对齐
float d = 50.00;
cout << showpoint << d << endl; //:50.00 //浮点数输出带小数点
cout << "hello"
"world"
<<endl; //:helloworld //换行打印
}
//格式化控制符 2
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int i = 0;
float f = 0;
char c = 0;
cout << "please input i:" << endl;
cin >> i; //:455
cout << "i = " << i << endl; //:i = 455
cout << "please input f and c:" << endl;
cin >> f >> c; //:564 42
cout << "f = " << f << endl; //:564
cout << "c = " << c << endl; //:42
cout << "f = " << f << "c = " << c << endl; //:f = 56c = 4
}
五、对象和类
5.1、OPP思想
5.1.1、抽象:就是声明定义一个类,抽象概括一类对象的公共性质。
数据抽象:设置成员变量
代码抽象:设置成员函数
5.1.2、封装:将数据成员和成员函数结合在一起,形成一个整体,就是类体部分。
5.2、类的声明定义
5.2.1、类体中默认权限为:private 私有的
5.2.2、成员函数在类体之外的定义语法:
数据类型 类名::函数名(<参数列表>){函数体}
5.2.3、成员变量的作用域:
整个类的作用域(类域):类的所有成员函数的函数体区域
5.2.3、类的所有成员函数都有一个特殊的指针 this,指向 访问当前成员函数的对象
5.2.4、类:
代码角度:用户自定义的数据类型,由class 进行说明
抽象角度:对象的类型,是一批对象的共享和特征,是对象的抽象概括
5.2.5、对象:
代码角度:就是一个变量
抽象角度:具备行为和属性的事物,一切皆为对象,也就是类的具体实列
//类的使用
#include <iostream>
using namespace std;
/* * 1、类体中默认权限为: private 私有的 * 2、成员函数在类体之外的定义语法: * 数据类型 类名::函数名(<参数列表>){ 函数体 } * * 3、成员变量的作用域: * 整个类的作用域(类域):类的所有成员函数的函数体区域 * * 4、类的所有成员函数都有一个特殊的指针 this,指向 访问当前成员函数的对象 * * 5、类: * 代码角度:用户自定义的数据类型,由 class 进行说明 * 抽象角度:对象的类型,是一批对象的共性和特征,是对象的抽象概括 * * 6、对象: * 代码角度:就是一个变量 * 抽象角度:具备行为和属性的事物,一切皆为对象,也是类的具体实例 * * 7、oop思想:面向对象编程思想。 * 1、抽象: 就是声明定义一个类,抽象概括出一类对象的公共性质 * 数据抽象: 设置成员变量 * 代码抽象: 设置成员函数 * * 2、封装:将数据成员和成员函数结合在一起,形成一个整体,就是类体部分 */
class Student; //声明一个不完整的 Student 类
class Student{
//定义 Student 类
public:
void setValue(char , int ); //在类体声明成员函数
private:
char sex;
int age;
};
void Student::setValue(char sex, int age) //成员函数的定义 //对应第2点
{
/*类域的范围*/ //对应第3点
this->sex = sex; //对应第4点
this->age = age;
}
int main(int argc, char *argv[])
{
class Student stu; //定义一个对象,实质是定义一个变量
stu.setValue(1, 2);
class Student *stu1; //定义一个对象指针,实质是定义一个指针
Student stu2;
stu2 = stu;
Student *stu3;
stu1 = &stu;
stu3 = new Student; //new 开辟空间,也叫 new 一个新的对象
stu1->setValue(2, 3);
}
5.2.6、如果设计一个类时,没有显示声明会自动生成:
- 构造函数
- 析构函数
- 复制构造函数
- 赋值运算符
- 地址运算符
//用类描述一个学生的信息
#include <iostream>
using namespace std;
#define pri() cout<<"line: "<<__LINE__<<endl;
class Student{
public:
Student(){
pri(); } //默认构造函数的显式说明
void setValue(int age, float weight){
this->age = age;
this->weight = weight;
}
int getAge(){
return age;
}
float getWeight(){
return weight;
}
private:
int age;
float weight;
};
int main(int argc, char *argv[])
{
Student stu; //定义一个对象,采用默认构造,没有显式说明时,编译器自动生成
#if 1
stu.setValue(18, 120);
Student stu1;
stu1 = stu; //默认赋值运算
cout << stu1.getAge() << endl; //:18
Student *p;
p = &stu; //默认地址运算
cout << p->getWeight() << endl; //:120
Student stu2 = stu1; //默认的拷贝构造
cout << stu2.getAge() << endl; //:18
Student stu3(stu1); //默认的拷贝构造
cout << stu3.getAge() << endl; //:18
Student *q = new Student;
#endif
}
5.3、构造和析构
5.3.1、构造
构造函数的目的: 用来初始化对象
特点:
1、默认构造函数没有显式说明时,会自动生成
2、构造函数没有数据类型,也就是没有返回值
3、构造函数函数函数名 与 类名一致
4、构造函数只能在定义对象时,被系统自动调用
5、构造函数可以重载
6、构造函数可以有默认参数
7、构造函数是类的 特殊成员函数
8、构造函数一般声明在 public 区域,也可以声明在 protected和private 区域形成限制构造
//构造函数
#include <iostream>
using namespace std;
#define pri() cout<<"line: "<<__LINE__<<endl;
/* * 构造函数的目的: 用来初始化对象 * 特点: * 1、默认构造函数没有显式说明时,会自动生成 * 2、构造函数没有数据类型,也就是没有返回值 * 3、构造函数函数函数名 与 类名一致 * 4、构造函数只能在定义对象时,被系统自动调用 * 5、构造函数可以重载 * 6、构造函数可以有默认参数 * 7、构造函数是类的 特殊成员函数 * 8、构造函数一般声明在 public 区域,也可以声明在 protected和private 区域形成限制构造 * * */
class Student{
public:
Student(){
pri(); } //构造函数的显式说明
Student(int a){
pri(); } //带参数的构造函数显式说明,此时无参数的构造函数将不会自动生成
Student(Student &obj){
pri(); } //拷贝构造函数的显式说明
private:
int age;
float weight;
};
int main(int argc, char *argv[])
{
Student stu; //定义一个对象,采用默认构造,没有显式说明时,编译器自动生成
Student stu1(1);
Student stu3(stu1); //默认的拷贝构造
Student stu2 = stu1; //默认的拷贝构造
#if 1
Student *q = new Student; //new 对象时,一定会构造函数
q = new Student(2);
q = new Student(stu);
#endif
}
5.3.2、拷贝构造
(1)、浅拷贝:同类型的两个对象的成员指针,指向同一个堆区空间,两成员指针相互干扰
(2)、深拷贝:同类型的两个对象的成员指针,指向两个不同的堆区,互不干扰
//浅拷贝和深拷贝
#include <iostream>
#include <string.h>
using namespace std;
class Student{
public:
Student(const char *name, int age);
Student(Student &obj);
char *getName();
void setName(const char *name);
private:
char *name;
int age;
};
Student::Student(const char *name, int age)
{
this->name = new char[ strlen(name) + 12 ];
strcpy(this->name, name);
this->age = age;
}
Student::Student(Student &obj)
{
#if 0
this->name = obj.name; //浅拷贝:同类型的两个对象的成员指针,指向同一个堆区空间,两成员指针相互干扰
#else
this->name = new char[strlen(obj.name) + 1]; //深拷贝:同类型的两个对象的成员指针,指向两个不同的堆区,互不干扰
strcpy(this->name, obj.name);
#endif
this->age = obj.age;
}
char * Student::getName()
{
return name;
}
void Student::setName(const char *name)
{
strcpy(this->name, name);
}
int main(int argc, char *argv[])
{
Student stu("张三", 18);
Student stu1(stu);
cout << "stu1: " << stu1.getName() << endl;
stu1.setName("张三疯");
cout << "stu1: " << stu1.getName() << endl;
cout << "stu: " << stu.getName() << endl;
}
5.3.3、补充-考题
//补充
#include <iostream>
using namespace std;
#define pri() cout<<"line: "<<__func__<<endl;
class A{
public:
A(){
pri(); }
A(A &obj){
}
};
class B{
public:
B() {
pri(); }
};
class Demo{
public:
B c;
Demo(){
pri(); }
private:
A a;
A b;
};
int main(int argc, char *argv[])
{
Demo obj;
}
5.3.4、析构函数
(1)、析构函数:作用用来回收对象占用的资源
特点:
1、没有数据类型,也就是没有返回值
2、函数名由 ~类名 构成
3、析构没有参数和返回值,因此不能被重载。
4、对象生命周期结束时,系统自动调用
5、对象指针遇见 delete 时,系统自动调用
(2)、包含对象成员(另一个类的对象最为改类的成员变量)的类对象的构造顺序;
构造顺序:先对象成员构造,再对象构造
析构顺序:先对象析构,再对象成员析构
//析构函数
#include <iostream>
#include <string>
using namespace std;
/* 析构函数:作用用来回收对象占用的资源 特点: 1、没有数据类型,也就是没有返回值 2、函数名由 ~类名 构成 3、析构没有参数和返回值,因此不能被重载。 4、对象生命周期结束时,系统自动调用 5、对象指针遇见 delete 时,系统自动调用 */
/* 包含对象成员(另一个类的对象最为改类的成员变量)的类对象的构造顺序; 构造顺序: 先对象成员构造,再对象构造 析构顺序: 先对象析构,再对象成员析构 */
/* 1、设计一个 Point 类描述坐标点,定义 p1点(0,4),p2点(3,0), 计算两点之间直线距离 cout << p1.dis(p2) << endl; ==> 5 */
#define pri() cout << "line" << __LINE__ << endl;
class Base{
public:
Base() //不带参数的构造函数
{
p = new char[12];
pri();
}
~Base() //析构函数
{
delete []p; //释放堆区空间
pri();
}
private:
char *p;
};
int main(int argc, char *argv[])
{
Base obj;
//while(1); //死循环析构函数不会调用
Base *p = new Base; //堆区空间不会随程序结束而结束,析构打印一次/构造打印两次
delete p; //析构/构造打印两次
}
5.4、this指针
this指针是一个特殊指针,指向类对象自身的首地址。
练习: 设计一个 Point 类描述坐标点,定义 p1点(0,4),p2点(3,0),计算两点之间直线距离
cout << p1.dis(p2) << endl; ==> 5
//计算两点之间直线距离
#include <iostream>
#include <cmath>
using namespace std;
class Point{
public:
Point(){
}
Point(int a, int b)
{
x = a;
y = b;
}
double Distance(Point a,Point b);
private:
int x, y;
};
double Point::Distance(Point a,Point b)
{
return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2));
}
int main()
{
Point point, p1(0,4), p2(3,0);
cout << point.Distance(p1, p2);
}
#include <iostream>
using namespace std;
#include <math.h>
class Point{
public:
Point(int x, int y)
{
this->x = x;
this->y = y;
}
float dis(Point &obj)
{
return sqrt( (x-obj.x)*(x-obj.x) + (y-obj.y)*(y-obj.y) );
}
private:
int x, y;
};
int main(int argc, char *argv[])
{
Point p1(3, 0), p2(0, 4);
cout << p1.dis(p2) << endl;
}
5.5、static 修饰成员
5.5.1、静态成员变量
(1)、在整个类域都可见,与具体的对象无关,
static 改变了生命周期,可以作用类型的不同对象之间的数据传递。
(2)、必须在类体之外定义初始化。
5.5.2、静态成员函数:
1、声明时必须有 staic 关键字,定义时不能有 static。
2、函数体中不能访问类的非静态成员。因为这里面没有this指针,也是语法规定的。
5.5.3、静态修饰对象:就是修饰一个变量,改变生命周期。
//static 修饰成员
#include <iostream>
using namespace std;
class Base{
public:
static int z;
Base(){
}
Base(int x, int y, int z)
{
this->x = x;
this->y = y;
this->z = z;
}
int getX()
{
return x;
}
static void setValue(int , int , int); //静态成员函数的声明
protected:
int y;
private:
static int x; //声明一个静态成员变量
};
int Base:: x = 13; //静态成员变量必须在类体之外 定义初始化:
//语法:数据类型 类名::变量 = 初值
int Base:: z = 0;
void Base:: setValue(int x, int n, int z) //静态成员函数的定义,不能有static
{
//this->x = x; //错误 静态成员函数与具体的对象无关,没有this指针
Base:: x = x;
y = n;
//Base:: y = n; //错误 静态成员函数,不能访问类的非静态成员
Base:: z = z;
}
/* 静态成员变量 1、在整个类域都可见,与具体的对象无关, static 改变了生命周期,可以作用类型的不同对象之间的数据传递。 2、必须在类体之外定义初始化。 静态成员函数: 1、声明时必须有 staic 关键字,定义时不能有 static。 2、函数体中不能访问类的非静态成员。 */
int main(int argc, char *argv[])
{
Base obj(1, 2, 3);
Base obj1;
cout << obj1.getX() << endl;//:1
//cout << Base::x << endl; //:error, x 私有的
cout << Base::z << endl; //:3 z 为公有的,静态全局变量
cout << obj1.z << endl; //:3 z 为公有的,说明静态成员变量与具体的对象无关
Base::setValue(1, 2, 3);
obj.setValue(2, 3, 4);
}
5.6、const 修饰成员
5.6.1、const 修饰成员变量,表示只读,必须在构造函数 参数初始化列表中赋初值
(1)、参数初始化列表语法: //只能在定义函数时写,声明时不能写
类名(<形参列表>):成员变量(形参1),成员变量(形参2)…
{}
说明:程序在执行时,调用构造函数先执行参数初始化列表,然后才执行 构造函数体
(2)、包含const 成员变量的类的所有构造函数都必须写出参数初始化列表,为只读变量赋初值。
5.6.2、const 修饰成员函数
(1)、声明定义都必须使用 const 关键字
(2)、表示不能使用该函数修改任何的成员变量,但是可以访问成员变量
5.6.3、const 修饰成员对象:
(1)、表示该对象定义初始化之后,任何属性都不能更改
(2)、const 修饰的对象不能访问类的非const 成员函数
//const 修饰成员
#include <iostream>
using namespace std;
class Base{
public:
Base();
Base(int x, int y) : x(x), y(y)
{
//this->x = x; //error ,const 为修饰只读变量,只能用 : x(x), y(y)
//this->y = y;
}
int getX()
{
//x++; //error ,x 为const 修饰只读变量
return x;
}
int setValue(int x, int y) const; //const 修饰成员函数
int z;
private:
const int x;
int y;
};
Base::Base():x(0) //对应第2点
{
}
int Base::setValue(int x, int y) const //const 修饰成员函数,在内外定义时必须加 const
{
//this->x = x; //error ,x 修饰只读变量,只能用 : x(x), y(y)
//this->y = y; //error ,该函数为 const 修饰的函数,表示不能通过该函数修改任何成员数据
int a = this->y; //const 只能访问,不能修改
}
/* const 修饰成员变量,表示只读,必须在构造函数 参数初始化列表中赋初值 1、参数初始化列表语法: 类名(<形参列表>):成员变量(形参1),成员变量(形参2)... {} 说明: 1、程序在执行时,调用构造函数先执行参数初始化列表,然后才执行 构造函数体 2、包含const 成员变量的类的所有构造函数都必须写出参数初始化列表,为只读变量赋初值。 const 修饰成员函数 1、声明定义都必须使用 const 关键字 2、表示不能使用该函数修改任何的成员变量,但是可以访问成员变量 const 修饰成员对象: 1、表示该对象定义初始化之后,任何属性都不能更改 2、const 修饰的对象不能访问类的非const 成员函数 */
int main(int argc, char *argv[])
{
#if 0
Base obj(1, 3);
cout << obj.getX() << endl; //:1
#endif
/* 定义const修饰的对象 */
const Base obj;
Base const obj1(1, 3);
obj.setValue(0, 3);
//obj.getX(); //error:const 对象只能访问类的 const 成员函数
int x = obj.z;
}
5.7、友元
5.7.1、友元函数
(1)、一个友元函数可以是多个类的有元函数,只需要在各个类中分别声明。
(2)、友元函数: 就是在成员函数声明之前使用 friend 关键字说明,此时该函数就打破类的封装:
1、友元函数不再是类的成员函数,不能通过对象 . 号访问,只能像普通函数调用一样使用
2、友元函数的函数体中,没有类体的访问权限限定,也就是可以直接通过 对象.号 访问非静态成员,可以通过 类名::静态成员
3、友元函数如果没有类类型作为形参,那么必须在类的外部进行定义,否则声明定义都可以在类体中
4、友元函数必须声明在类体中
//友元函数
#include <iostream>
using namespace std;
class Base{
public:
Base(int x):x(x){
}
friend int getValue(Base &obj)//声明定义一个友元函数,表示该函数打破类的封装,不再是该类的成员函数,也就是 没有 this 指针
{
return obj.x; //该友元函数打破了类的封装,也就是在该函数体中,没有权限限定
}
friend int getX();
private:
int x;
static int y;
};
int Base:: y = 13;
int getX() //友元函数在类外部定义,就像普通函数定义一样
{
Base obj(3);
obj.x = 5; //该友元函数打破类的封装,也就是没有 protected 和 private 的限制
return Base::y;
}
/* * 友元函数: 就是在成员函数声明之前使用 friend 关键字说明,此时该函数就打破类的封装: * 1、友元函数不再是类的成员函数,不能通过对象 . 号访问,只能像普通函数调用一样使用 * 2、友元函数的函数体中,没有类体的访问权限限定,也就是可以直接通过 对象.号 访问非静态成员,可以通过 类名::静态成员 * 3、友元函数如果没有类类型作为形参,那么必须在类的外部进行定义,否则声明定义都可以在类体中 * 4、友元函数必须声明在类体中 * */
int main(int argc, char *argv[])
{
Base obj(12);
cout << getValue(obj) << endl; //:12
cout << getX() << endl; //:13
}
5.7.2、友元类
(1)、友元关系不能被继承
(2)、友元关系是单向的,不具有交互性。
(3)、如果 B 是 A 的友元类,也就是 A 将 B 当成朋友:
那么在 B 类的所有成员函数中将 打破 A 类的权限。
//友元类
#include <iostream>
using namespace std;
//class B;
class A{
public:
A(int x, int y):x(x){
}
friend class B; //说明 B是 A 的友元类,也就是A类将 B 类当成朋友
private:
int x;
static int y;
};
int A::y = 13;
class B{
public:
int getY()
{
A obj(1, 2);
obj.x = 3;
return A::y;
}
int getX(A &obj)
{
return obj.x;
}
};
/* * 如果 B 是 A 的友元类,也就是 A将B 当成朋友: * 那么在B 类的所有成员函数中都将 打破A 类的权限封装 * */
int main(int argc, char *argv[])
{
A a(3, 4);
B b;
cout << b.getY() << endl;
cout << b.getX(a) << endl;
}
5.7.3、有元成员函数
(1)、B类的成元函数可以是类A的友元
//有元成员函数
#include <iostream>
using namespace std;
class A;
class B{
public:
int getX(A &obj);
int getY(A &obj);
};
class A{
public:
A(int x):x(x){
}
friend int B::getX(A &); //说明 B 类的getX成员函数是A类的友元,也就是getX函数打破A类的封装
private:
int x;
static int y;
};
int A::y = 15;
int B::getX(A &obj)
{
obj.y = 11;
return obj.x;
}
int B::getY(A &obj)
{
//obj.x = 15; //错误,因为该函数不是A类的友元,没有打破A类的封装。
//return obj.x;
}
int main(int argc, char *argv[])
{
}
练习:
(1)、友元类和友元成员函数中,构造、析构的执行顺序
(2)、设计一个Point类,定义 p1(3, 0), p2(0, 4),
cout << dis(p1, p2) << endl; 求两点之间的直线距离
(3)、分别定义一个类A和类B ,各有一个私有整数成员变量通过构造函数初始化;
类A有一个成员函数Show(B &b)用来打印A和B的私有成员变量,
请分别通过友元成员函数和友元类来实现此功能。
1、友元类和友元成员函数中,构造、析构的执行顺序先被调用的构造函数,
其对应(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。
/*2、设计一个Point类,定义 p1(3, 0), p2(0, 4), cout << dis(p1, p2) << endl; 求两点之间的直线距离*/
#include <iostream>
#include <cmath>
using namespace std;
class Point{
public:
Point(){
}
Point(int x, int y)
{
this->x = x;
this->y = y;
}
friend double dis(Point x, Point y);
private:
int x, y;
};
double dis(Point x, Point y)
{
return sqrt( pow(x.x-y.x, 2) + pow(x.y-y.y, 2) );
}
int main()
{
Point p1(3, 0), p2(0, 4);
cout << dis(p1, p2) << endl;
}
/*3.1、分别定义一个类A和类B ,各有一个私有整数成员变量通过构造函数初始化*/
#include <iostream>
using namespace std;
class B;
class A{
public:
A(int x):x(x){
}
void Show(B &);
private:
int x;
};
class B{
public:
B(int x):x(x){
}
private:
friend void A::Show(B &);
friend class A;
int x;
};
void A::Show(B &b)
{
cout << "A: " << x << endl;
cout << "B: " << b.x << endl;
}
int main(int argc, char *argv[])
{
A a(2);
B b(4);
a.Show(b);
}
六、运算符重载
运算符重载:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
不能被重载的运算符
逗号"." ?号表达式"?:" sizeof 作用域"::"
6.1、成员运算符重载函数
函数体中有this指针指向第一个操作数,重载函数的形参个数 = 操作数 - 1;除了b++或b–
//成员运算符重载函数
#include <iostream>
using namespace std;
class Base{
public:
Base(int x) : x(x){
}
int operator+(int a)
{
return x + a;
}
int operator-(int a);
int operator++(int )
{
return x++;
}
int operator++()
{
return ++x;
}
private:
int x;
};
int Base::operator-(int a)
{
return x - a;
}
/* 成员运算符重载函数,函数体中有this指针指向第一个操作数,重载函数的形参个数 = 操作数 - 1;除了b++或b-- */
int main(int argc, char *argv[])
{
Base b(3);
int a = b + 2; // <==> int a = b.operator+(2);
cout << a << endl; //:5
//a = b - 1; // <==> a = b.operator-(1);
a = b.operator-(2); // <==> 上一步
cout << a << endl; //:1
cout << b++ << endl; //:3
cout << ++b << endl; //:5
//cout << 2 + b << endl;
}
6.2、友元运算符重载函数
函数体中没有this指针指向第一个操作数,重载函数的形参个数 = 操作数;除了b++或b–
重载函数的第一个形参,表示第一个操作数
//友元运算符重载函数
#include <iostream>
using namespace std;
class Base{
public:
Base(int x) : x(x){
}
friend int operator+(int a, Base &b)
{
return a + b.x;
}
friend int operator++(Base &obj, int); // int 占位符,后++
friend int operator-(Base &b, int a)
{
return b.x - a;
}
friend ostream & operator<<(ostream &out, Base &obj)
{
out << obj.x;
return out;
}
friend istream & operator>>(istream &in, Base &obj)
{
in >> obj.x;
return in;
}
int operator()(); //成员
private:
int x;
};
int operator++(Base &obj, int)
{
return obj.x++;
}
int Base::operator()() //成员
{
return x;
}
/* 友元运算符重载函数,函数体中没有this指针指向第一个操作数,重载函数的形参个数 = 操作数;除了b++或b-- 重载函数的第一个形参,表示第一个操作数 */
int main(int argc, char *argv[])
{
Base b(3);
cout << 2 + b << endl; //:5
cout << b++ << endl; //:3
cout << b - 2 << endl; //:1
cout << b << endl; //:4 <==> operator << (cout, b) << endl;
Base a(1);
cin >> a >> b; //:2 4
cout << "a: " << a << "b: " << b << endl; //:a: 2 b: 4
cout << a() << endl; //:2 防函数:类对象模仿函数功能
//a(): 重载了()运算符,模拟函数功能,称为防函数
}
//防函数写法
#include <iostream>
using namespace std;
class Base{
public:
Base(int x) : x(x){
}
int operator()();
int operator()(int a); //防函数写法
private:
int x;
};
int Base::operator()() //成员
{
return x;
}
int Base::operator()(int a) //防函数写法
{
return x + a;
}
int main(int argc, char *argv[])
{
Base obj(10);
cout << obj() << endl; //:10 a(): 重载了()运算符,模拟函数功能,称为防函数
cout << obj(5) << endl; //:15
}
6.3、函数数据类型
6.3.1、一般情况下,由返回值决定。
6.3.2、返回类的成员变量,并且期望返回之后能改变成员变量的数值,则返回引用类型。
练习:
1、设计 Mytring 类,模仿string 类
/* std::operator+–字符串连接 std::operator!=–不等比较 std::operator==–相等比较 std::operator<–小于比较 std::operator<=–小于等于比较 std::operator>–大于比较 std::operator>=–大于等于比较 std::operator<<–字符串内容写到输出流中 std::operator>>–从输入流中读取一个字符串 operator[] 括号运算,取某个字符 operator() */
2、设计链表类, +号表示,链表尾部插入数据, -号表示尾部删除数据
3、重载 -> 、* 、new[] 三个运算符
七、模板
7.1、什么是模板
模板:支持参数多态化都工具,让函数或者;类具备通用性。
目的:让程序员编写与类型无关的代码,是泛型编程的基础
//什么是模板
#include <iostream>
using namespace std;
int add(int x, int y)
{
return x + y;
}
float add(float x, float y)
{
return x + y;
}
/* 模板:支持参数多态化都工具,让函数或者;类具备通用性。 目的:让程序员编写与类型无关的代码,是泛型编程的基础 分类: 1、函数模板 仅针对函数参数类型,返回值不同,功能相同的函数 2、类模板 类模板针对仅数据成员和成员函数类型不同的 */
int main(int argc, char *argv[])
{
int a, b;
cin >> a >> b;
cout << add(a, b) << endl;
cout << add((float)3.1, float(2.1)) << endl;
}
分类:
(1)、函数模板
仅针对函数参数类型,返回值不同,功能相同的函数
template < class形参名, class形参名> 返回类型 函数名(参数列表)
{
函数体
}
(2)、类模板
类模板针对仅数据成员和成员函数类型不同的
template < class形参名, class形参名,…> class 类名
{
…
}
template <class T, typename B …>
template: 关键字,说明是一个模板
class T : 定义类型模板形参,class 等价于 typename
T : 类型模板形参,用来替换数据类型
注意:
模板声明和全局只能在全局。
7.2、类型模板参数
//函数模板
#include <iostream>
using namespace std;
/* 模板:支持参数多态化都工具,让函数或者;类具备通用性。 目的:让程序员编写与类型无关的代码,是泛型编程的基础 分类: 1、函数模板 仅针对函数参数类型,返回值不同,功能相同的函数 2、类模板 类模板针对仅数据成员和成员函数类型不同的 template <class T, typename B ...> template: 关键字,说明是一个模板 class T : 定义类型模板形参,class 等价于 typename T : 类型模板形参,用来替换数据类型 注意: 模板声明和全局只能在全局。 */
# if 0
template <class T>
T add(T x, T y); //声明定义一个函数模板,T:类型模板形参,用来替换数据类型
//class 等价于 typename
{
return x + y;
}
#endif
#if 1
template <class T> T add(T x, T y); //声明一个函数模板
#else
template <class T = int> T add(T x, T y); //声明一个带默认参数函数模板
//32位编译时使用:g++ 2.cpp -std=c++0x
#endif
int main(int argc, char *argv[])
{
int a, b;
cin >> a >> b;
cout << add(a, b) << endl; //函数模板的隐式调用,自动匹配数据类型
cout << add(4.1, 1.1) << endl;
cout << add<>(10, 3) << endl; //函数模板的显示调用,自动匹配数据类型,也就是T代表了int类型
cout << add < float >(1.1, 0.3) << endl;//函数模板的显示调用,手动传递数据类型,将float传递赋值给了T
}
template <class B> B add(B x, B y) //定义一个函数模板
{
return x + y;
}
/* 输出: 5 2 7 5.2 13 1.4 */
//两个类型函数模板,可以有默认参数,但无意义
#include <iostream>
using namespace std;
template <class T, class B> B add(T x, B y)
{
//cout << "x: " << x << endl;
//cout << "y: " << x << endl;
return x + y;
}
int main(int argc, char *argv[])
{
cout << add(1, 2) << endl; //隐式调用, 自动匹配 T 代表 int, B 表示 int
cout << add(1, 1.2) << endl; //隐式调用, 自动匹配 T 代表 int, B 表示 double
cout << "********************" << endl;
cout << add< >(2, 3) << endl;
cout << add< >(2.1, 3) << endl;
cout << "********************" << endl;
cout << add< int, double >(2, 3.1) << endl;
cout << add< int >(2, 3.1) << endl; //实参 int 与 实参 2的数据类型匹配,则 int 传递给了T, B则自动匹配的
cout << add< double >(2, 3.1) << endl; //参数 double 与实参 2 类型匹配,将2转换成double,则double传递了T,B则自动匹配的
cout << add< double, int >(2, 3.1) << endl;
//cout << add< double, >(2, 3.1) << endl; //error
/*这个传参是从左往右传参,先T,再B*/
}
/* 输出: 3 2.2 ******************** 5 5 ******************** 5.1 5.1 5.1 5 */
成员函数在类模板 类体外定义的基本语法,必须单独使用 template 说明
注意: 作用域访问符前 必须是 类名<类型形参>::
//声明定义一个类模板
#include <iostream>
using namespace std;
template <class T = int> //声明定义一个类模板
class Demo{
public:
#if 0
Demo(T x):x(x){
}
void setValue(T x){
this->x = x;
}
T getValue(){
return this->x;
}
#else
Demo(T );
void setValue(T );
T getValue();
#endif
private:
T x;
};
#if 0
#else
//成员函数在类模板 类体外定义的基本语法,必须单独使用 template 说明
//注意: 作用域访问符前 必须是 类名<类型形参>::
template <class B>
Demo<B>::Demo(B x):x(x){
}
template <class T>
void Demo<T>::setValue(T x)
{
this->x = x;
}
template <class C>
C Demo<C>::getValue()
{
return x;
}
#endif
int main(int argc, char *argv[])
{
// Demo obj(12); //错误,类模板定义对象时不能隐式说明
// Demo < > obj(12); //错误,类模板定义对象时不能自动匹配类型
Demo< int > obj(12);
cout << obj.getValue() << endl;
obj.setValue(35);
cout << obj.getValue() << endl;
Demo< > obj1(3.14); //这种使用,要求模板形参必须有默认值
cout << obj1.getValue() << endl;
Demo<char> obj2('r');
cout << obj2.getValue() << endl;
}
//实列
#include <iostream>
using namespace std;
template <class T, int max>
class Demo{
public:
Demo(T);
private:
T buf[max];
};
template <class T, int max>
Demo<T, max>::Demo(T x)
{
for(int i = 0; i < max; i++)
buf[i] = x+i;
}
int main(int argc, char *argv[])
{
Demo<int, 6> obj(3);
}
7.3、非类型模板参数
使用非类型模板形参,目的:让模板中的常数具备灵活性。
template <class T, int var> class Demo
{
…
}
//非类型模板形参
#include <iostream>
using namespace std;
/* 使用非类型模板形参,目的:让模板中的常数具备灵活性。 */
#define MAX 12
template <class T, int max = MAX> bool getmem(T **p)
{
char buf[max] = {
0};
cout << sizeof(buf) << endl;
*p = new T[max];
if(p)
return true;
return false;
}
int main(int argc, char *argv[])
{
int *p = NULL;
if(getmem<int, 4>(&p))
{
for(int i = 0; i < MAX; i++)
p[i] = i;
for(int i = 0; i < MAX; i++)
cout << p[i] << " ";
cout << endl;
}
}
/* 输出: 4 0 1 2 3 4 5 6 7 8 9 10 11 */
//声明定义一个类模板
#include <iostream>
using namespace std;
template <class T> //声明定义一个类模板
class Demo{
public:
Demo(T x):x(x){
}
void setValue(T x){
this->x = x;
}
T getValue(T &x){
x = this->x;
return this->x;
}
private:
T x;
};
int main(int argc, char *argv[])
{
// Demo obj(12); //错误,类模板定义对象时不能隐式说明
// Demo < > obj(12); //错误,类模板定义对象时不能自动匹配类型
Demo< int > obj(12);
int a;
cout << obj.getValue(a) << endl;
cout << "a: " << a << endl;
obj.setValue(35);
cout << obj.getValue(a) << endl;
}
7.4、默认模板参数
template <class Tl, class t2=int> class Demo
{
…
}
//默认模板参数,多个类型函数模板,可以有默认参数,但无意义
#include <iostream>
using namespace std;
/* 模板:支持参数多态化都工具,让函数或者;类具备通用性。 目的:让程序员编写与类型无关的代码,是泛型编程的基础 分类: 1、函数模板 仅针对函数参数类型,返回值不同,功能相同的函数 2、类模板 类模板针对仅数据成员和成员函数类型不同的 template <class T, typename B ...> template: 关键字,说明是一个模板 class T : 定义类型模板形参,class 等价于 typename T : 类型模板形参,用来替换数据类型 注意: 模板声明和全局只能在全局。 */
# if 0
template <class T>
T add(T x, T y); //声明定义一个函数模板,T:类型模板形参,用来替换数据类型
//class 等价于 typename
{
return x + y;
}
#endif
#if 1
template <class T> T add(T x, T y); //声明一个函数模板
#else
template <class T = int> T add(T x, T y); //声明一个带默认参数函数模板
//32位编译时使用:g++ 2.cpp -std=c++0x
#endif
int main(int argc, char *argv[])
{
int a, b;
cin >> a >> b;
cout << add(a, b) << endl; //函数模板的隐式调用,自动匹配数据类型
cout << add(4.1, 1.1) << endl;
cout << add<>(10, 3) << endl; //函数模板的显示调用,自动匹配数据类型,也就是T代表了int类型
cout << add < float >(1.1, 0.3) << endl;//函数模板的显示调用,手动传递数据类型,将float传递赋值给了T
}
template <class B> B add(B x, B y) //定义一个函数模板
{
return x + y;
}
/* 输出: 5 2 7 5.2 13 1.4 */
7.5、友元函数模板
//成员函数和友元函数重载
#include <iostream>
using namespace std;
template <class T>
class Base{
public:
Base(T x):x(x){
}
#if 0 //成员函数重载
T operator+(Base<T> &obj){
//成员函数声明+定义
return x + obj.x;
}
#elif 0
T operator+(Base<T> &); //成员函数声明
#elif 0 //友元函数重载
friend T operator+(Base<T> &obj1, Base<T> &obj2){
//友元函数声明+定义
return obj1.x + obj2.x;
}
#else
template <class B>
friend B operator+(Base<B> &, Base<B> &); //友元函数声明
#endif
private:
T x;
};
/*******函数重载定义*******/
#if 0 //外部定义重载
#elif 0
template <class B> B Base<B>::operator+(Base<B> &obj) //成员函数定义
{
return x + obj.x;
}
#else
template <class T>
T operator+(Base<T> &obj1, Base<T> &obj2) //友元函数定义
{
return obj1.x + obj2.x;
}
#endif
int main(int argc, char *argv[])
{
Base<int> a(2), b(3);
cout << a + b << endl;
Base<double> c(2.1), d(3.1);
cout << c + d << endl;
}
八、类继承
8.1、概念
在进行C编程的时候,代码重用的目的就是提高开发效率、减少错误、让大规模代码开发的关注点转到软件结构上。
C++(OOP)的代码重用除了简单层次的提供类库,还提出了更高层次:
类继承( inheritance)、多态( Polymorphism)、泛型编程( Generic Programming),等等
-
基类:在面向对象设计中,被定义为包含所有实体共性的class类型,被称为“基类”。
-
继承:就是在一个已存在的类的基础上建立一个新的类。
-
派生:从已有的类产生一个字类
-
最远派生类:在派生体系中派生到最下层的类
-
派生类继承了基类的所有数据成员和成员函数 ***
-
公有继承:继承过来的成员的 权限不变
-
子类和基类有相同的成员,要访问基类 同名成员时,必须使用 基类名::成员名
#include <iostream>
#include <string.h>
using namespace std;
class farther{
public:
double money;
char name[128];
char *getFname()
{
return name;
}
protected:
void setMoney(int m){
}
private:
int eyes;
};
//派生一个子类,子类继承父类的所有数据成员和成员函数
/* 公有继承:继承过来的成员的 权限不变 */
class son : public farther
{
public:
char name[128]; //7.将会隐藏 基类同名的成员,也就是派生类中有自己的 name 成员,也有基类的 name 成员。
void getMoney()
{
money = 2000; //4.公有继承时,在派生类内部,可以直接访问 基类 公有成员
setMoney(1000); //5.公有继承时,在派生类内部,可以直接访问 基类 保护成员
//eyes = 4; //6.公有继承时,在派生类内部,不能直接访问 基类 私有成员
}
char *getMname()
{
return name;
}
protected:
private:
};
int main(int argc, char *argv[])
{
son obj;
obj.money = 10000; //1.公有继承时,在派生类外部,可以通过对象访问 公有成员
//obj.setMoney(2000); //2.公有继承时,在派生类外部,不能通过对象访问 保护成员
//obj.eyes = 10000; //3.公有继承时,在派生类外部,不能通过对象访问 私有成员
strcpy(obj.name, "张三");
/* 8.子类和基类有相同的成员,要访问基类 同名成员时,必须使用 基类名::成员名 */
strcpy(obj.farther::name, "张二");
cout << obj.getFname() << endl;
cout << obj.getMname() << endl;
}
8.2、派生一个类
-
默认向上隐式转换,也就是将派生类对象赋值给基类对象时,将派生类类型转换成基类类型,派生类将会丢弃自身的数据部分
-
也称为小范围赋值给大范
-
向下转换是不被允许的,不允许将大范围赋值给小范围
//继承方式
#include <iostream>
using namespace std;
#define pri() cout << "line: " << __LINE__ << "func: " << __func__ << endl;
class Base{
public:
Base(int x):x(x){
pri(); }
int getValue()
{
return x;
}
private:
int x;
};
class Subclass : public Base{
public:
Subclass(int x, int y) : x(x), Base(y)
{
}
int getX()
{
return x;
}
private:
int x;
};
int main(int argc, char *argv[])
{
Subclass obj(8, 5);
Base B(7);
cout << "x: " << obj.getValue() << endl;
cout << B.getValue() << endl;
#if 1
B = obj; //默认向上隐式转换,也就是将派生类对象赋值给基类对象时,将派生类类型转换成基类类型,派生类将会丢弃自身的数据部分
//也称为小范围赋值给大范围
cout << B.getValue() << endl;
#else
// obj = B; //向下转换是不被允许的,不允许将大范围赋值给小范围
#endif
#if 1 //同上
Base &p = obj;
cout << "p: " << p.getValue() << endl;
#else //同上
// Subclass &q = B;
#endif
}
8.3、派生类的构造和析构
派生类不能继承基类的构造、析构函数
派生类有自己的构造、析构函数
如果基类构造函数有参数,在从派生类的构造函数把参数传递给基类的构造函数
派生类名∷:构造函数名(参数列表):基类名(参数列表)
-
继承时,构造顺序:先基类构造,再派生构造
析构顺序:先派生类析构,再基类构造 -
包含对象成员的派生类和基类构造顺序:
先 基类构造,再对象成员构造,最后派生类构造 -
析构顺序与构造顺序相反
/*构造析构顺序*/
#include <iostream>
using namespace std;
#define pri() cout << "line: " << __LINE__ << "func: " << __func__ << endl;
class A{
public:
A() {
pri(); }
~A() {
pri(); }
};
class Base{
public:
Base() {
pri(); }
~Base() {
pri(); }
};
class Subclass : public Base{
public:
Subclass() {
pri(); }
~Subclass() {
pri(); }
A a;
};
/* 1、继承时,构造顺序:先基类构造,再派生构造 析构顺序:先派生类析构,再基类构造 2、包含对象成员的派生类和基类构造顺序: 先 基类构造,再对象成员构造,最后派生类构造 3、析构顺序与构造顺序相反 */
int main(int argc, char *argv[])
{
Subclass obj;
}
注意:类体中显示说明,带参数的构造函数,那么无参数的默认构造 不会自动生成
-
派生类构造函数参数初始化列表中,隐式调用基类 默认构造(无参数的构造函数)
-
如果基类出现带参数构造函数,必须在派生类的构造函数参数初始化列表中,显示调用 基类带参构造函数
-
如果派生对象成员的构造函数带参数,必须在派生类的构造函数参数初始化列表中,为对象成员这个变量赋初值。
#include <iostream>
using namespace std;
#define pri() cout << "line: " << __LINE__ << "func: " << __func__ << endl;
class A{
public:
A(int x){
}
};
class Base{
public:
Base(int x):x(x){
}
~Base() {
}
private:
int x;
};
class Subclass : public Base{
public:
Subclass(int x) : a(x), Base(x)
{
}
~Subclass(){
}
private:
A a; //定义一个对象成员
};
/* 注意:类体中显示说明,带参数的构造函数,那么无参数的默认构造 不会自动生成 1、派生类构造函数参数初始化列表中,隐式调用基类 默认构造(无参数的构造函数) 2、如果基类出现带参数构造函数,必须在派生类的构造函数参数初始化列表中,显示调用 基类带参构造函数 3、如果派生对象成员的构造函数带参数,必须在派生类的构造函数参数初始化列表中,为对象成员这个变量赋初值。 */
int main(int argc, char *argv[])
{
Subclass obj(80);
}
8.4、is-a / has-a关系
is-a关系
is-a 一般是继承关系,has-a一般是组合。
-
is-a 派生类对象也是一个基类对象,基类对象能够做的操作,派生类也可以
-
has-a 是一种聚合,表示一个类中有另一个类的对象。
8.5、多重继承
//多重继承:名二义性
#include <iostream>
using namespace std;
#define pri() cout << "line: " << __LINE__ << endl;
class A{
public:
void prnmsg()
{
pri();
}
private:
};
class B{
public:
void prnmsg()
{
pri();
}
private:
};
class C : public A, public B{
//多重继承
public:
private:
};
int main(int argc, char *argv[])
{
C obj;
//多重继承的名字二义性:解决方式,作用域访问符
//obj.prnmsg(); //错误,有歧义
obj.A::prnmsg();
obj.B::prnmsg();
}
多重继承的路径二义性:
解决方式,作用域访问符 和 设置虚基类(虚继承(继承时有 virtual)共同的基类叫虚基类)
//多重继承的路径二义性:解决方式,作用域访问符 和 设置虚基类
#include <iostream>
using namespace std;
#define pri() cout << "line: " << __LINE__ << endl;
class Bass{
public:
void prnmsg()
{
pri();
}
private:
};
class A : virtual public Bass{
public:
private:
};
class B : virtual public Bass{
public:
private:
};
class C : public A, public B{
public:
private:
};
int main(int argc, char *argv[])
{
C obj;
//多重继承的路径二义性:解决方式,作用域访问符 和 设置虚基类(虚继承(继承时有 virtual)共同的基类叫虚基类)
//obj.prnmsg(); //错误:有歧义
obj.A::prnmsg();
obj.B::prnmsg();
}
九、多态
9.1、什么是多态
9.1.1、什么是多态
简单概括:“一个接口,多种方法”;
也就是程序在运行时才决定调用的函数,是面向对象编程领域的核心概念。
9.1.2、什么是多态性:
多态性是将接口与实现进行分离;也就是实现以共同的方法,但因个体差异,而采用不同的策略。
9.1.3、面向对象编程(OPP)主要特征:
-
封装:实现实细节隐藏,使代码模块化。
-
继承:扩展已经存在的代码,目的是为了代码重用。
-
多态:目的是为了接口重用。
//什么是多态
#include <iostream>
using namespace std;
#define pri() cout << "line: " << __LINE__ << endl;
/* 1、什么是多态 简单概括:"一个接口,多种方法"; 也就是程序在运行时才决定调用的函数,是面向对象编程领域的核心概念。 2、什么是多态性: 多态性是将接口与实现进行分离;也就是实现以共同的方法,但因个体差异,而采用不同的策略。 3、面向对象编程(OPP)主要特征: 3.1、封装:实现实细节隐藏,使代码模块化。 3.2、继承:扩展已经存在的代码,目的是为了代码重用。 3.3、多态:目的是为了接口重用。 4、多态性的实现:虚函数 (virtual function) 简单地说,用 virtual修饰的成员函数,就是虚函数 4.1、必须是类的成员函数 4.2、类的静态成员函数不能成员虚函数 4.3、构造函数不能定义为虚函数,但是析构函数可以定义为虚函数 4.4、成员函数使用 virtual 关键字,定义时不需要 4.5、基类有虚函数,那么派生类中的同名函数(函数名相同,参数列表一致,返回值相同)自动成员析构函数 */
class Base{
public:
Base(int x):x(x){
}
virtual void Show()
{
cout << "Base::x: " << x << endl;
}
private:
int x;
};
class Subclass : public Base{
public:
Subclass(int x, int y):x(x), Base(y){
}
void Show()
{
pri();
cout << "Base::x: " << x << endl;
}
private:
int x;
};
void test(Base &obj)
{
obj.Show();
}
int main(int argc, char *argv[])
{
Subclass obj1(3, 8);
Base obj2(5);
test(obj2);
test(obj1);
}
9.2、虚函数
9.2.1、多态性的实现:虚函数 (virtual function)
简单地说,用 virtual修饰的成员函数,就是虚函数
A、必须是类的成员函数
B、类的静态成员函数不能成员虚函数
C、构造函数不能定义为虚函数,但是析构函数可以定义为虚函数
D、成员函数使用 virtual 关键字,定义时不需要
E、基类有虚函数,那么派生类中的同名函数(函数名相同,参数列表一致,返回值相同)自动成员析构函数
9.3、覆盖、重载及隐藏
9.3.1、成员函数覆盖( override,也称重写)
是指派生类重新定义基类的虚函数,特征如下:
A、不同的作用域(分别位于派生类与基类)
B、函数名字相同
C、参数相同
D、基类函数必须有 virtual关键字,不能有 static
E、返回值相同
F、重写函数的权限访问限定符可以不同
9.3.2、成员函数重载( overload)
是指函数名相同,参数不同(数量、类型、次序),特征如下:
A、相同的范围(在同一个作用域中)
B、函数名字相同
C、参数不同
D、 virtual关键字可有可无
E、返回值可以不同
9.3.3、成员函数隐藏(也称重定义)
A、不在同一个作用域(分别位于派生类与基类)
B、函数名字相同
C、返回值可以不同
D、参数不同。此时,不论有无 virtual关键字,基类的函数将被隐藏(注意与重载的区别)
E、参数相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意与覆盖的区别
//覆盖、重载及隐藏
#include <iostream>
using namespace std;
#define pri() cout << "line: " << __LINE__ << endl;
class Base{
public:
virtual void func(int a){
pri()} // 1
int func(void){
pri()} // 2
};
class Subclass : public Base{
public:
void func(int a){
pri()} // 3
int func(void){
pri()} // 4
};
/* 覆盖关系:1和3(派生类的虚函数覆盖基类的同名虚函数) 隐藏关系:1和4 || 2和3 || 2和4 (参数相同时,基类不能有 virtual关键字; 参数不同时,基类无论有无 virtual关键字) */
int main(int argc, char *argv[])
{
Subclass obj;
obj.func(1);
obj.func();
Base obj2;
obj2.func(2);
obj2.func();
obj2 = obj;
obj2.func(3);
obj2.func();
Base &obj3 = obj;
obj3.func(4);
obj3.func();
}
9.4、动态联编
9.4.1、联编(链接)
就是将模块或者函数起生成可执行代码的处理过程。按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编
9.4.2、静态联编(静态链接)
是指在編译阶段就将函数实现和函数调用关联起来,因此静态联編也叫早绑定,
9.4.3、动态联编(动态链接)
是指在程序执行的时候才将函数实现和函数调用关联,因此也叫运行时绑定或者晚绑定。C+中一般情况下联編也是静态联編,但是
及到多态和虛拟函数就必须要使用动态联編了
9.4.4、重载只是一种语言特性
编译器根据函数不同的参数表,把同分开来,属于静态联编,与多态无关。引用一句 Bruce Ecke的话要犯傻,如果它不是晚绑就不是多态。
//虚函数表
#include <iostream>
using namespace std;
#define pri() cout << "line: " << __LINE__ << endl;
class Base{
public:
Base(int x):x(x), y(3){
}
virtual void funca(){
pri(); } // 18H
virtual void funab(){
pri(); } // 19H
int getX(){
return x; }
private:
int x;
int y;
};
typedef void(*pfunc)(void);
int main(int argc, char *argv[])
{
Base obj(5);
cout << *((int *)&obj+1) << endl; //:5
cout << *((int *)&obj+2) << endl; //:3
//((pfunc)*((int *)&obj+3))();
pfunc p = (pfunc)*(int *)(*(int *)&obj);
p(); //:18H
int vtbl = *(int *)&obj;
p = (pfunc)*((int *)vtbl + 1);
p(); //:19H
char c = *(char *)&obj;
}
//类的字节对齐
#include <iostream>
using namespace std;
class A{
};
class B{
int x;
};
class C{
short x;
char buf[3];
};
class D{
short x;
char buf[3];
int y;
};
class E{
public:
int getValue(){
return x; }
static void setValue(){
}
static int y;
private:
int x;
};
class F{
public:
int getValue(){
return x; }
static void setValue(){
}
static int y;
private:
int x;
};
int F::y = 12;
class G{
public:
virtual int getValue(){
return x; }
virtual int getValue2(){
return x; }
private:
int x;
};
class H{
public:
virtual int getValue(){
}
virtual int getValue2(){
}
private:
};
int main(int argc, char *argv[])
{
cout << sizeof(A) << endl; //:1
cout << sizeof(B) << endl; //:4
cout << sizeof(C) << endl; //:6
cout << sizeof(D) << endl; //:12
cout << sizeof(E) << endl; //:4
cout << sizeof(F) << endl; //:4
cout << sizeof(G) << endl; //:8
cout << sizeof(H) << endl; //:4
}
9.5、抽象类
9.5.1、抽象基类:
含有纯虚函数的类就是抽象类。
抽象类没有完整的信息,只能是派生类的基类
抽象类不能有实例,不能有静态成员
派生类应该实现抽象类的所有方法
9.6、虚继承
9.6.1、虚析构函数:
C++使用虚拟继承( Virtual Inheritance),解决从不同途径继承来的同名的数据成员在内存中有不同的
拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。
9.7、虚析构函数
虚析构函数:解决资源回收不完整问题(基类指针指向堆区的派生类对象的首地址,释放基类指针)
如果基类并不需要回收清理什么,那么析构函数就可以是纯虚函数。
由于指针指向的对象是基类类型,所以 delete销毁对象的时候,并不会调用派生类的析构函数,这样就造
成员对象消耗不完整
如果基类并不需要回收清理什么,那么析构函数就可以是纯虚函数。
//虚析构
#include <iostream>
using namespace std;
#define pri() cout << "func: " << __func__ << "line: " << __LINE__ << endl;
class Base{
public:
Base(){
pri(); } //:19H
virtual ~Base(){
pri(); } //:20H //虚析构函数:解决资源回收不完整问题(基类指针指向堆区的派生类对象的首地址,释放基类指针)
};
class Subclass : public Base{
public:
Subclass(){
pri(); } //:26H
~Subclass(){
pri(); }//:27H
};
int main(int argc, char *argv[])
{
Base *p = new Base;
delete p;
/* 输出: func: Baseline: 19 func: ~Baseline: 20 */
cout << "-------------------" << endl;
Subclass *q = new Subclass;
delete q;
/* 输出: func: Baseline: 19 func: Subclassline: 26 func: ~Subclassline: 27 func: ~Baseline: 20 */
cout << "-------------------" << endl;
p = new Subclass;
delete p;
/* 输出: func: Baseline: 19 func: Subclassline: 26 func: ~Subclassline: 27 func: ~Baseline: 20 */
}
9.8、限制构造函数
一个类的构造函数访问权限不是 public,该类的构造函数就是限制构成函数
练习:
1、设计一个图形类,定义图形类的对象指针,该指针指向圆时,可以计算该圆的面积;指向三角形时,可以计算三角形面积;该指针指向矩形时,可以计算该矩形的面积…
面积计算都通过指针,访问 arer() 函数。
//设计一个图形类
#include <iostream>
using namespace std;
class Graph{
public:
Graph(){
}
virtual ~Graph(){
}
virtual float Area() = 0;
};
class Circle : public Graph{
public:
Circle(float r):r(r){
}
~Circle(){
}
float Area(){
return 3.14*r*r;
}
private:
float r;
};
class Triangle : public Graph{
public:
Triangle(float w, float h):w(w), h(h){
}
~Triangle(){
}
float Area(){
return w*h/2;
}
private:
float w, h;
};
int main(int argc, char *argv[])
{
Graph *p = new Triangle(4, 5);
cout << "Triangle area: " << p->Area() << endl;
delete p;
p = new Circle(4);
cout << "Circle area: " << p->Area() << endl;
delete p;
/* Triangle a(4, 6); p = &a; delete p; */
}
十、异常
10.1、什么是异常
- 什么是异常:异常在一种容错机制,是一种错误处理系统。
- 为什么要有异常:保证软件系统运行的稳定性与健状性
- C++的异常处理机制有3部分组成try(检查)→ throw(抛出)> catch(捕获)
try{
//检查语句
if(错误){
throw异常
}
}
catch(异常类型1){
进行异常处理的语句1
}
…
#include <iostream>
#include <stdexcept> //异常处理头文件
#include <string.h>
using namespace std;
// C++ 中默认每一个函数都会抛出异常
int func(int x, int y)
{
if(y) //检查错误
return x/y;
else
cout << "y == 0" << endl;
//抛出异常,携带错误信息,本质时抛出一个匿名对象,抛出之后该函数立即结束
//throw invalid_argument("error, y = 0");
invalid_argument tmp("error, y = 0");
throw tmp;
}
int main(int argc, char *argv[])
{
int a, b;
AA:
cin >> a >> b;
try{
//准备进行错误检测,抛出异常
cout << func(a, b) << endl;
}catch(const invalid_argument &err){
//捕获异常,捕获之后给了 err,则err 中就会保存异常的错误信息
//异常的处理,打印异常携带的错误信息
cout << "异常:" <<err.what() << endl;
if(strcmp(err.what(), "error, y = 0") == 0)
goto AA;
}
}
- C++ 中默认每一个函数都会抛出异常,但是不会指定抛出异常类型,
- 所以函数设计者应该在函数声明定义时需要显示说明抛出的异常类型。throw(…)
#include <iostream>
#include <stdexcept> //异常处理头文件
#include <string.h>
using namespace std;
// C++ 中默认每一个函数都会抛出异常,但是不会指定抛出异常类型,
//所以函数设计者应该在函数声明定义时需要显示说明抛出的异常类型。throw(...)
int func(int, int) throw(runtime_error); //函数声明
int func(int x, int y) throw(runtime_error) //显示说明该函数抛出什么类型的异常,方便函数使用者捕获该类型的异常
{
if(y) //检查错误
return x/y;
else
{
//抛出异常,携带错误信息,本质时抛出一个匿名对象,抛出之后该函数立即结束
invalid_argument tmp("error, y = 0");
throw tmp;
}
}
int main(int argc, char *argv[])
{
int a, b;
AA:
cin >> a >> b;
try{
//准备进行错误检测,抛出异常
cout << func(a, b) << endl;
}catch(const invalid_argument &err){
//捕获异常,捕获之后给了 err,则err 中就会保存异常的错误信息
//异常的处理,打印异常携带的错误信息
cout << "异常:" <<err.what() << endl;
if(strcmp(err.what(), "error, y = 0") == 0)
goto AA;
}
}
10.2、使用标准异常
- C++ 中默认每一个函数都会抛出异常,如果要说明该函数不会抛出异常,那么声明和定义时,都用 throw()
- 如果函数说明为 不会抛出异常,那么内部强行抛出异常,会出现终止程序运行
- throw() :是C++ 旧标准的
- noexcept:是C++ 11 标准中的运算符,32位机中编译时,必须使用 -std=c++0x
- int func(int, int)throw();
#include <iostream>
#include <stdexcept> //异常处理头文件
#include <string.h>
using namespace std;
// C++ 中默认每一个函数都会抛出异常,如果要说明该函数不会抛出异常,那么声明和定义时,都用 throw()
// 如果函数说明为 不会抛出异常,那么内部强行抛出异常,会出现终止程序运行
// throw() :是C++ 旧标准的
// noexcept:是C++ 11 标准中的运算符,32位机中编译时,必须使用 -std=c++0x
// int func(int, int)throw();
int func(int, int) noexcept;
int func(int x, int y) noexcept
{
if(y)
return x/y;
else
{
invalid_argument tmp("error, y = 0");
throw tmp;
}
}
int main(int argc, char *argv[])
{
int a, b;
AA:
cin >> a >> b;
try{
cout << func(a, b) << endl;
}catch(const invalid_argument &err){
cout << "异常:" <<err.what() << endl;
if(strcmp(err.what(), "error, y = 0") == 0)
goto AA;
}
}
10.3、自定义异常
- 在设计一个大系统的时候,往往设计者会自定义很多错误,这些错误在标准错 误里面是没有的,那么就需要我们来设计一些异常类
//自定义设计异常
#include <iostream>
#include <stdexcept> //异常处理头文件
#include <string.h>
using namespace std;
//用户自己设计一个异常类,继承与标准异常 exception
class Base : public exception{
public:
Base(const char *err) throw() :msg(err){
}
virtual ~Base() throw();
private:
const char *msg;
};
int func(int, int) throw(Base); //说明,改函数可以抛出一个 Base 字节设定的异常类型
int func(int x, int y) throw(Base)
{
if(y)
return x/y;
else
{
throw Base("error, y = 0");
}
}
int main(int argc, char *argv[])
{
int a, b;
AA:
cin >> a >> b;
try{
cout << func(a, b) << endl;
}catch(const invalid_argument &err){
cout << "异常:" <<err.what() << endl;
if(strcmp(err.what(), "error, y = 0") == 0)
goto AA;
}
}
//用户自己设计一个异常类,继承与 标准异常 exception
#include <iostream>
using namespace std;
#include <stdexcept>
//用户自己设计一个异常类,继承与 标准异常 exception
class Base{
public:
Base(const char *err) throw() : msg(err){
}
virtual ~Base() throw(){
}
const char *prnmsg() const{
return msg; }
private:
const char *msg;
};
int func(int, int) throw(Base); //说明 该函数可以抛出一个 Base 字节设定的异常类型
int func(int x, int y) throw(Base)
{
if (y)
return x/y;
else{
throw Base("error, y = 0");
}
}
#include <string.h>
int main(int argc, char *argv[])
{
int a, b;
AA:
cin >> a >> b;
try{
cout << func(a, b) << endl;
}catch (const Base &err){
cout << "异常: " << err.prnmsg() << endl;
if ( strcmp(err.prnmsg(), "error, y = 0") == 0)
goto AA;
}
}
10.4、异常规范
#include <iostream>
using namespace std;
class Base{
public:
Base(int x):x(x){
}
int getValue() {
return x; }
private:
int x;
};
int main(int argc, char *argv[])
{
Base obj(5);
int a = 13;
obj = a; //将 a 隐式转换成 Base 类型 ==> obj = Base(a);
cout << obj.getValue() << endl;
obj = 1; // ==> obj = Base(1);
// a = obj; //错误,不能将 Base 类型 隐式转换成 int 类型
cout << a << endl;
}
十一、转换函数
11.1、什么是转换函数
11.1.1、C++中,自定义的类型转换成 其他任何一种类型,都采用转换函数,转换函数应该声明在 要转换的类体中
转换函数的实质:
运算符重载,只是重载的不是内置运算符,而是类名或者基本数据类型
11.1.2、转换函数语法:
operator 类型名()
{
实现转换的语句
}
11.1.3、转换函数的特点:
(1)转换函数只能是成员函数,无返回值,空参数,有return返回值。
(2)不能转换成 void 数组 函数类型
(3)常定义为 const 形式
//什么是转换函数
#include <iostream>
#include <stdexcept>
#include <string.h>
using namespace std;
/* C++中,自定义的类型转换成 其他任何一种类型,都采用转换函数,转换函数应该声明在 要转换的类体中 转换函数的实质: 运算符重载,只是重载的不是内置运算符,而是类名或者基本数据类型 转换函数语法: operator 类型名() { 实现转换的语句 } 转换函数的特点: (1)转换函数只能是成员函数,无返回值,空参数,有return返回值。 (2)不能转换成 void 数组 函数类型 (3)常定义为 const 形式 */
class Base{
public:
Base(int x):x(x){
}
int getValue() {
return x; }
operator int()
{
return x;
}
private:
int x;
};
class Demo
{
public:
Demo(int x):x(x){
}
operator Base()
{
return x;
}
private:
int x;
};
int main(int argc, char *argv[])
{
Base obj(5);
int a = 13;
obj = a; //将a隐式转换成 Base 类型 -> obj = ase(a);
cout << obj.getValue() << endl; //:13
obj = 1; //-> obj = ase(1);
a = obj; //-> a = obj.operator int();
a = obj.operator int();
cout << a << endl; //:13
Demo obj1(5);
obj = obj1; //-> obj = Base(obj1.operator Base());
obj = Base(obj1.operator Base());
cout << obj.getValue() << endl; //:5
}
//explicit 关键字
#include <iostream>
using namespace std;
class Demo{
public:
Demo(int x):x(x){
}
int getValue() {
return x; }
private:
int x;
};
class Base{
public:
explicit Base(int x):x(x){
}
int getValue() {
return x; }
private:
int x;
};
int main(int argc, char *argv[])
{
//定义初始化对象,先将 int 类型隐式转成 Demo 类型,再初始化
Demo obj1 = 6; // ==> Demo obj1 = Demo(6);
cout << obj1.getValue() << endl;
//Base obj = 4; //错误,此处不能隐式转换,因为 构造函数被 explicit 修饰
Base obj(4);
// obj = 5; //错误,构造函数被 explicit 修饰,不允许隐式转换
obj = Base(5);
}
11.2、标准转换函数
11.3、自定义转换函数
//自定义转换函数
#include <iostream>
using namespace std;
class Base{
public:
Base(int x):x(x){
}
private:
int x;
};
class Subclass : public Base{
public:
Subclass(int x, int y): Base(y), x(x){
}
private:
int x;
};
int main(int argc, char *argv[])
{
char buf[12] = "aello world";
// reinterpret_cast<new type>(expression) // 将一个指针类型,转换成另一个指针类型
int *p = reinterpret_cast<int *>(buf);
cout << &buf << endl;
cout << p << endl;
cout << (char)*p << endl;
int q = reinterpret_cast<int>(buf);
cout << q << endl;
const char *str = "hello world";
// *str = 'a';
char *s = const_cast<char *>(str);
cout << s << endl;
Base obj1(2);
Subclass obj2(3, 8);
obj1 = obj2;
//obj2 = static_cast<Subclass>(obj1);
// 使用 static_cast 可以将具有继承关系的,基类指针转换成 派生类指针,实现 大范围赋值给小范围
Subclass *m = static_cast<Subclass *>(&obj1);
}
11.4、慎用转换函数
十二、智能指针
12.1、什么是智能指针
//什么是智能指针
#include <iostream>
using namespace std;
#define pri() cout<<"line: "<<__LINE__<<endl;
class Base{
public:
Base(){
pri(); }
~Base(){
pri(); }
};
void test()
{
Base *p = new Base;
// ....
//
}
int main(int argc, char *argv[])
{
test();
test();
test();
}
12.2、shared-ptr
//:shared_ptr
#include <iostream>
using namespace std;
#define pri() cout<<"line: "<<__LINE__<<endl;
#include <memory>
class Base{
public:
Base(){
pri(); }
~Base(){
pri(); }
void func(){
pri(); }
};
void test()
{
// Base *p = new Base;
// ....
// shared_ptr<Base> : 准备定义一个 管理 Base 类型数据的智能指针,此处重载 * 号运算符,因此 p 就是一个指针
// p(new Base) : 让指针 p 指向 Base 类型的堆区空间
// 当 p 指针的声明周期结束时,会自动回收堆区资源
#if 0
// shared_ptr<Base> m = new Base;
shared_ptr<Base> p(new Base);
// ....
// ....
#else
// make_shared 该函数模板是在堆区开辟一个资源共享的区域,执行完时会自动回收一次
shared_ptr<Base> p = make_shared<Base>( Base() );
cout << "-----------" << endl;
#endif
shared_ptr<Base> q = p; //在定义一个智能指针,指向 p 所指的空间
// ....
// 此处重载了 -> 运算符, 以 -> 访问成员
p->func();
q->func();
}
int main(int argc, char *argv[])
{
test();
}
12.3、unique ptr
12.4、weak-ptr
十三、STL
13.1、STL简介
STL的目的是标准化组件,这样就不用重新,可以使用现成。
-
容器( containers )
特殊的数据结构,实现了数组、链表、队列、等等,实质是模板类 -
迭代器( iterators )
一种复杂的指针,可以通过其读写容器中的对象,实质是运算符重载 -
算法( algorithms )
读写容器对象的逻辑算法:排序、遍历、查找、等等,实质是模板函数 -
空间配置器( allocator)
容器的空间配置管理的模板类 -
配接器( adapters )
用来修饰容器、仿函数、迭代器接口 -
仿函数( functors )
类似函数,通过重载()运算符来模拟函数行为的类
13.2、标准容器简介
STL标准模版库是一种泛型编程。
参考链接 ->
13.3、vector
//vector
#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char *grgv[])
{
#if 0
//定义一个整型数组
vector<int>a;
cout << sizeof(a) << endl; //:12字节
/*size(): 计算元素的个数*/
cout << a.size() << endl; //:0
/*push_back():在尾部插入元素*/
a.push_back(12);
cout << a.size() << endl; //:1
/*at():取某个下标的元素,等价于[]*/
cout << a[0] << " " << a.at(0) << endl; //:12 12
#else
int buf[5] = {
5, 2, 3, 4, 1};
/*定义初始化一个 vector 向量,必须使用一个数组的起始地址和结束地址为构造函数的实参。*/
vector<int> a(buf, buf + 5);
cout << a.size() << endl;
/*删除一个尾部元素*/
a.pop_back();
for(int i=0; i<a.size(); i++)
{
cout << a.at(i) << " "; //:5 2 3 4
}
cout << endl;
//定义一个 vector 的迭代器指针,重载了*运算符
vector<int>::iterator p;
/*.begin(); 开始,求数组的首地址*/
p = a.begin();
cout << *p << endl; //:5
for(int i=0; i<a.size(); i++)
{
*p += 1;
p++;
}
for(int i=0; i<a.size(); i++)
{
cout << a.at(i) << " "; //:6 3 4 5
}
cout << endl;
/*end():求数组最后一个元素的下一个地址*/
const vector<int>::iterator q = a.end();
cout << *q << endl; //:1
for(int i=a.size(); i>=0; i--)
{
*q -=1;
//q--; //:const 修饰的q,表示其指向不能改变
}
for(int i=0; i<a.size(); i++)
{
cout << a.at(i) << " "; //:6 3 4 5
}
cout << endl;
//将a.begin() 这个起始地址 a.end() 结束地址上所有数据进行擦除,其实质是将元素的索引进行改变
a.erase(a.begin(), a.end());
cout << "a.size(): " << a.size() << endl; //:a.size(): 0
for(int i=0; i<a.size(); i++)
{
cout << a.at(i) << " "; //:
}
cout << endl;
p = a.begin();
for(; p<q; )
{
cout << *p << " "; //:6 3 4 5
p++;
}
cout << endl;
#endif
}
13.4、list
13.5、deque
C++高级开发推荐资料手册!
C++高级开发推荐资料手册!
链接:https://pan.baidu.com/s/1-U_rettuHeSs1pUqaMbRwg
提取码:xxkt
文章评论