C++语法与最佳实践完整指南(上)
一、C++介绍
C++的诞生背景
1979年4月,贝尔实验室的本贾尼速度为UNIX系统内核开发更高效的模块化工具时,提出了扩展的需求。同10月,C预处理器ningsCpre诞生,为C语言引入了类机制,标志着面向对象特性的雏。1983诞生了C++的首个版本,最初名为“C with Classes”。
C++与C::的关键差异
- 语法兼容性:C++保留了C的语法核心,确保平滑过渡。
- 面向对象编程:支持类、继承、多态等OOP特性。
- 运算符重载:允许自定义运算符行为编程行为。
- 泛型编程:通过模板实现实现类型无关的代码复用。a
- 异常机制:提供try-catch块结构化的错误处理。
- 类型系统:相较C'alias更严格的编译时类型检查。
二、第一个C++程序
1、文件扩展名
.cpp .cc .C .cxx
2、编译器
g++ 大多数系统需要额外安装,Ubuntu系统下的安装命令:
sudo apt-get update
sudo apt-get install g++
gcc也可以继续使用,但需要增加参数 -xC++ -lstdc++
补充说明:以下引自
https://stackoverflow.com/questions/172587/what-is-the-difference-between-g-and-gcc
gcc and g++ are compiler-drivers of the GNU Compiler Collection (which was once upon a time just
the GNU C Compiler).
Even though they automatically determine which backends (cc1 cc1plus ...) to call depending on the
file-type, unless overridden with -x language, they have some differences.
The probably most important difference in their defaults is which libraries they link against
automatically.
According to GCC's online documentation link options and how g++ is invoked, g++ is equivalent to
gcc -xc++ -lstdc++ -shared-libgcc (the 1st is a compiler option, the 2nd two are linker options).
This can be checked by running both with the -v option (it displays the backend toolchain commands
being run).
机翻为:gcc和g++是GNU编译器的编译器驱动程序。收藏(很久以前就是GNUC编译器).
即使它们自动确定哪个后端(cc1 cc1plus.)根据文件类型进行调用,除非-x language他们有一些不同之处。
它们的默认值中最重要的区别可能是它们自动链接到哪个库。
根据GCC的在线文件链接选项和如何调用g++, g++等于gcc -xc++ -lstdc++ -shared-libgcc(第一个是编译器选项,
第二个是链接器选项)。可以通过使用-v选项(它显示正在运行的后端工具链命令)。
第二条关于 g++和gcc的区别的回答如下:
GCC:GNU编译集
引用GNU编译器支持的所有不同语言。
GCC:GNU C编译器
G++:GNU C++编译器
主要区别是:
GCC将编译:*C/*cpp文件,分别作为C和C++。
G++将编译:*.c/*.cpp文件,但它们都将被视为C++文件。
另外,如果使用g++链接对象文件,它将自动链接到STD C++库中(GCC不会这样做)。
GCC编译C文件的预定义宏较少。
GCC编译*.cpp和g++编译*.c/*.cpp文件有一些额外的宏。
编译*.cpp文件时的额外宏:
#define __GXX_WEAK__ 1
#define __cplusplus 1
#define __DEPRECATED 1
#define __GNUG__ 4
#define __EXCEPTIONS 1
#define __private_extern__ extern
可以参考:https://www.zhihu.com/answer/536826078
3、头文件
#include <iostream>
#include <stdio.h> 可以继续使用,但C++建议使用 #include <cstdio>
4、输入/输出
cin >> 输入数据
cout << 输出数据
cin/cout会自动识别类型
scanf/printf可以继续使用
注意:cout和cin是类对象,而scanf/printf是标准库函数。
5、增加了namespace(名字空间/命名空间)
std::cout
using namespace std;
三、namespace(名字空间/命名空间)
1、什么是namespace(名字空间/命名空间)
在C++中经常使用多个独立开发的库来完成项目,由于库的作者或开发人员没见过面,因此命名冲突在所难免。
2、为什么需要namespace(名字空间/命名空间)
在项目中函数名、全局变量、结构、联合、枚举、类,非常有可能名字冲突,而名字空间就对这些命名进行
逻辑空间划分(不是物理单元划分),为了解决命名冲突,C++之父为防止命名冲突给C++设计一个名字空间的机制。
通过使用namespace XXX把库中的变量、函数、类型、结构等包含在名字空间中,形成自己的作用域,避免
名字冲突。
namespace xxx
{
}// 没有分号
注意:namespace(名字空间/命名空间)也是一种标识符,在同一作用域下不能重名。
3、同名的namespace(名字空间/命名空间)有自动合并(为了声明和定义可以分开写)
同名的namespace(名字空间/命名空间)中如果有重名的依然会命名冲突
4、namespace(名字空间/命名空间)的使用方法
::域限定符
空间名::标识符 // 使用麻烦,但是非常安全
using namespace 空间名; 把空间中定义的标识符导入到当前代码中
不建议这样使用,相当于把垃圾分类后,又导入同一个垃圾车,依然会冲突
例程:
#include <iostream>
namespace test
{
int cout = 100;
int cin = 99;
}
int main()
{
std::cout << test::cout << ' ' << test::cin << std::endl;
}
输出:
[Running] cd "f:\VS Code Demo\" && g++ 1.cpp -o 1 && "f:\VS Code Demo\"1
100 99
5、无名名字空间
不属于任何名字空间中的标识符,隶属于无名名字空间。
无名名字空间中的成员使用 ::标识符 进行访问。
如何访问被屏蔽的全局变量。
6、namespace(名字空间/命名空间)的嵌套
namespace(名字空间/命名空间)内部可以再定义名字空间,这种名字空间嵌套
内层的名字空间与外层的名字空间的成员,可以重名,内层会屏蔽外层的同名标识符。
多层的名字空间在使用时逐层分解。
例程:
#include <iostream>
// n1::n2::num;
namespace n1
{
int num = 1;
namespace n2
{
int num = 2;
namespace n3
{
int num = 3;
}
}
}
int main()
{
std::cout << n1::num << ' ' << n1::n2::num << ' ' << n1::n2::n3::num << std::endl;
}
输出:
[Running] cd "f:\VS Code Demo\" && g++ 1.cpp -o 1 && "f:\VS Code Demo\"1
1 2 3
7、可以给namespace(名字空间/命名空间)取别名
由于namespace(名字空间/命名空间)可以嵌套,这样就会导致在使用内层成员时过于麻烦,可以给
namespace(名字空间/命名空间)取别名来解决这类问题。
namespace n123 = n1::n2::n3;
std::cout << n123::num << std::endl;
四、C++的结构
1、不再需要 typedef ,在定义结构变量时,可以省略struct关键字(struct student s -> student s)
2、成员可以是函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->,但是C的结构成员可以是函数指针。
3、有一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)。
4、可以继承,可以设置成员的访问权限(面向对象)。
struct test
{
float a;
int b;
};
例程:
#include <iostream>
#include <cstring>
using namespace std;
struct Man
{
char id[18];
};
struct Student : public Man
{
char name[20];
char sex;
short age;
// 类Student的构造函数
Student(void)
{
cout << "我被调用了..." << endl;
}
void show(void)
{
cout << "我是" << name << " " << sex << " " << age << " " << id << endl;
}
};
int main()
{
Student stu;
strcpy(stu.name, "nico");
stu.sex = 'm';
stu.age = 12;
strcpy(stu.id, "1235214141231");
cout << stu.name << " " << stu.sex << " " << stu.age << endl;
cout << stu.id << endl;
stu.show();
}
输出:
[Running] cd "f:\VS Code Demo\" && g++ 1.cpp -o 1 && "f:\VS Code Demo\"1
我被调用了...
nico m 12
1235214141231
我是nico m 12 1235214141231
五、C++的联合
1、不再需要 typedef ,在定义结构变量时,可以省略union关键字(union test t -> test t)
2、成员可以是函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->,但是C的结构成员可以是函数指针。
3、有一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)。
union 共用体名{
数据类型 成员名;
数据类型 成员名;
...
} 变量名;
六、C++的枚举
1、定义、使用方法与C语言基本一致。
2、类型检查比C语言更严格
enum 类型名 {枚举常量表};
例程:
#include <iostream>
using namespace std;
enum Color
{
RED,
YELLOW,
BLUE,
WHITE,
BLACK
};
int main()
{
Color r;
r = BLUE;
// r = 0; // error: invalid conversion from 'int' to 'Color' 类型检查更严格
cout << r << endl;
}
输出:
[Running] cd "f:\VS Code Demo\" && g++ 1.cpp -o 1 && "f:\VS Code Demo\"1
2
C++的布尔类型与C语言的差异
C++中的布尔类型bool是关键字,true和false也是关键字,占用1字节空间。在C语言中,布尔类型需要引入头文件stdbool.h(C11标准后bool成为数据类型),true和false并非关键字,且通常占用4字节空间。
C++的void*类型安全改进
C语言中void*可以隐式inge转换为任意指针类型,而C++要求显式强制类型转换,但允许其他指针类型自动赋值给void benevolent 这种设计增强了类型安全性,避免潜在的错误指针操作。
操作符别名的标准化
国际标准化组织(ISO)在C95和C++98标准中引入了操作符别名,以支持特殊键盘布局(如缺少~、&符号的键盘)。例如:
and替代&&or替代||not替代!
这些别名遵循ISO-646标准,确保代码在不同键盘环境下仍可正确编写。
and &&
or ||
not !
{ <%
} %>
# :%
十、函数重载
1、函数重载
在同一作用域下,函数名相同,参数列表不同的函数,构成重载关系。
2、重载实现的机制
C++代码在编译时会把函数的参数类型添加到参数名中,借助这个方式来实现函数重载,也就是C++的函
数在编译期间经历换名的过程。
因此,C++代码不能调用C函数(C语言编译器编译出的函数)
3、extern "C" {}
告诉C++编译器按照C语言的方式声明函数,这样C++就可以调用C编译器编译出的函数了(C++目标文件
可以与C目标文件合并生成可执行程序)。
如果C想调用C++编译出的函数,需要将C++函数的定义用extern "C"包括一下。
注意:如果两个函数名一样,一定会冲突。
4、重载和作用域
函数的重载关系发生在同一作用域下,不同作用域下的同名函数,构成隐藏关系。
5、重载解析
当调用函数时,编译器根据实参的类型和形参的匹配情况,选择一个确定的重载版本,这个过程叫重载
解析。
实参的类型和形参的匹配情况有三种:
1、编译器找到与实参最佳的匹配函数,编译器将生成调用代码。
2、编译找不到匹配函数,编译器将给出错误信息。
3、编译器找到多个匹配函数,但没有一个最佳的,这种错误叫二义性。
在大多数情况下编译器都能立即找到一个最佳的调用版本,但如果没有,编译就会进行类型提升,这样
备选函数中就可能具有多个可调用的版本,这样就可能产生二义性错误。
6、确定存在函数的三个步骤
1)候选函数
函数调用的第一步就是确定所有可调用的函数的集合(函数名、作用域),该集合中的函数就是候选函数。
2)选择可行函数
从候选函数中选择一个或多个函数,选择的标准是参数个数相同,而且通过类型提升实参可被隐式转换为形参。
3)寻找最佳匹配
优先每个参数都完全匹配的方案,其次参数完全匹配的个数,再其次是浪费内存的字节数。
7、指针类型会对函数重载造成影响
C++函数的形参如果是指针类型,编译时函数名中会追加Px。
十一、默认形参
1、在C++中函数的形参可以设置默认值,调用函数,如果没有提供实参数,则使用默认形参。
2、如果形参只有一部分设置了默认形参,在某个提供了默认值的参数后面,所有的参数都必须提供默认值。
3、函数的默认形参是在编译阶段确定的,因此只能使用常量、常量表达式、全局变量数据作为默认值。
提问:如果函数的声明和定义需要分开,那么默认形参设置在声明、定义,还是声明定义都需要设置?
4、默认形参会对函数重载造成影响(二义性),设置默认形参时一定要慎重。
十二、
1、普通函数调用时是生成调用指令(跳转),然后当代码执行到调用位置时跳转到函数所在的代码段执行。
2、内联函数就把函数编译好的二进制指令直接复制到函数的调用位置。
3、内联函数的优点就是提高程序的运行速度(因为没有跳转,也不需要返回),但这样会导致可执行文件增大
(冗余),也就是牺牲空间来换取时间。
4、内联分为显示内联和隐式内联
显示内联:在函数前 inline(C语言C99标准也支持)
隐式内联:结构、类中内部直接定义的成员函数,则该类型函数会被优化成内联函数。
5、宏函数在调用时会把函数体直接替换到调用位置,与内联函数一样也是使用空间来换取时间,所以宏函数
与内联函数的区别(优缺点)?
1.宏函数不是真正的函数,只是代码替换,不会有参数压栈、出栈以及返回值,也不会检查参数类型,
因此所有类型都能使用,但这样会有安全隐患。
2.内联函数是真正的函数,被调用时会进行传参,会进行压栈、出栈,可以有返回值,并会严格检查参
数类型,这样就不能通用,如果想被多种类型调用需要重载。
6、内联适用的条件
由于内联会造成可执行文件变大,并增加内存开销,因此只有频繁调用的简单函数适合作为内联。
调用比较少的复杂函数,内联后并不显著提高性能,不足以抵消牺牲空间带来的损失,所以不适合内联。
带有递归特性和动态绑定特性的函数,无法实施内联,因此编译器会忽略声明部分的inline关键字。
十三、引用
引用就是取艺名(别名)。
1、引用的基本特性
引用就是取别名,声明一个标识符为引用,就表示该标识符是另一个对象的外号。
1.引用必须初始化,不存在空引用,但有悬空引用(变量死了,名还留着)。
2.可以引用无名对象(临时对象),但必须使用常引用。
3.引用不能更换目标
4.引用目标如果具有const属性,引用也需要具有const属性。
引用一旦完成了定义和初始化就和普通变量名一样,它就代表了目标,一经引用终身不能再引用其他目标。
2、引用型参数
引用当作函数的参数能达到指针同样的效果,但不具备指针的危险,还比指针方便。
引用可以非常简单的实现函数间共享变量的目的,而且是否使用引用由被调函数说了算。
引用当作函数的参数还能提高传递参数效率,指针至少还需要4字节内存,而引用只需要增加一条标识符
与内存之间的绑定(映射)。//有待商榷
3、引用型返回值
不要返回局部变量的引用,会造成悬空引用。
如果返回值是一个临时值(右值),如果非要使用引用接收的话,必须使用常引用。
注意:C++中的引用时一种取别名的机制,而C语言中的指针是一种数据类型(代表内存编号的无符号整数
(有待商榷))。
练习1:实现一个C++版本的swap函数。
指针和引用的相同点和不同点:
相同点:跨函数共享变量,优化传参效率,避免传参的时候调用拷贝构造
不同点:指针有自己的存储空间,借助指针可以使用堆内存,引用不行。引用取别名,指针是数据类型。
指针可以为空,引用不可以为空。指针可以不初始化,引用必须初始化。指针可以改变指向,引用不能引用其他
对象(可以定义指针的指针,不能定义引用的引用。可以定义指针的引用,不能定义引用的指针。可以定义指针
的数组,但不能定义引用的数组。可以定义数组的引用)。
#include <iostream>
using namespace std;
void swap(int& a,int& b) //引用
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a=3,b=4;
swap(a,b);
cout<<a<<" "<<b<<endl;
}
十四、C++的内存管理
内存管理运算符 new/delete 与 malloc/free 的区别
new 和 delete 是 C++ 中用于动态内存管理的运算符,功能类似于 C 语言的 malloc 和 free,但更智能和安全。
new 在分配内存时自动计算类型所需字节数,并返回具有类型信息的指针,同时调用构造函数初始化对象。
delete 释放内存时自动调用析构函数清理资源,确保对象生命周期正确结束。
混用 new/delete 和 malloc/free 会导致未定义行为,因为 new/delete 涉及构造和析构函数的调用,而 malloc/free 仅进行原始内存操作。
动态数组的分配与释放
动态数组通过 new[] 分配,需指定数组长度 n。若数组元素是类或结构体,new[] 会调用每个元素的构造函数。
释放动态数组必须使用 delete[],否则可能导致内存泄漏或资源未正确释放。new[] 返回的指针前 4 字节(取决于实现)通常存储数组长度信息,delete[] 依赖此信息正确释放内存。
重复释放与野指针处理
重复调用 delete 或 delete[] 释放同一块内存会导致程序崩溃或未定义行为。
释放野指针(指向无效内存的指针)后果不可预测,可能破坏堆结构或引发异常。
释放空指针是安全的,delete 和 delete[] 对 nullptr 无操作。
最佳实践
- 始终配对使用 new/delete 和 new[]/delete[]。
- 避免混用 C++ 和 C 的内存管理方式。
- 释放内存后将指针置为 nullptr,防止重复释放。
- 使用智能指针(如 std::unique_ptr、std::shared_ptr)替代裸指针,减少手动管理内存的风险。
#include <iostream>
using namespace std;
struct Student
{
Student(void)
{
cout<<"我是构造函数,创建对象时,我就会执行" << endl;
}
~Student(void)
{
cout<< "我是析构函数,释放对象时,我就会执行" << endl;
}
};
int main()
{
int* p = new int;
*p = 10;
cout<< *p <<endl;
Student stu;
Student* s = new Student;
delete(s);
cout << endl;
Student* a = new Student[3];
p = (int*)a;
cout << *(p-1) << endl;
delete[] a;
}
4、内存分配失败
当分配的内存过大,没有能满足需求的整块内存就会抛出异常,std::bad_alloc。
new/delete和C语言的malloc/free的相同点和不同点(区别)?
不同点:
身份 运算符 标准库函数
参数 类型(自动计算) 字节数(手动计算)
返回值 带类型的地址 void*地址
调用构造 自动调用 不能调用构造/析构函数
出错 抛出异常 返回NULL
相同点:
1、都能管理堆内存
2、不能重复释放
3、可以释放NULL
注意:在C++中尽量使用引用、new/delete
#include <iostream>
using namespace std;
int main()
{
int *p = NULL;
try{
p = new int[~0];
}
catch(std::bad_alloc& ex)
{
cout << "error" << endl;
}
}
十五、强制类型转换
扩展
面向过程编程:
关注是问题解决的过程步骤,算法
面向对象编程:
关注的是谁能解决问题(类),需要什么样的数据(成员变量),具备什么样的技能(成员函数)才能解决问题。
抽象:找出一个能够解决问题的“对象”(观察研究对象),找出解决所必须的数据(属性)、功能(成员函数)。
封装:把抽象的结果,归结为一个类(数据类型),然后实例化出类对象,设置对象的属性,调用对象的功能达到解决问题的目的。
继承:在解决问题前,先寻找之前的类能不能解决问题,或解决部分问题,如果可以则把旧的类继承后再次拓展,来缩短解决问题的时间,降低
解决问题的难度。
多态:对象的多种形态,外部看到一个对象发出指令,对象会根据自身情况做出独特的反应。
一、类和对象
1、通过分析对象的属性和行为设计出一个类。
2、类就是数据类型
简单类型:只能表示一个属性(变量),C/C++内建数据类型
数组类型:可以表示多个属性(变量),类型必须相同。
结构类型:可以表示多个属性(变量),但缺少行为(函数)。
类类型:即能表示属性,也能表示行为,一直复合数据类型。
3、对象就是类这种数据类型创建出的实例,相当于结构变量。
class Student
{
属性(成员变量);
行为(成员函数);
};
Student stu;
二、类的定义与实例化
1、类的一般形式
class 类名 : 继承方式 父类
{
public/private/protected: // 访问控制限制符
成员变量;
// 构造函数
类名(形参表)
{
}
// 析构函数
~类名(void)
{
}
};
2、类的访问控制限定符
public:公有成员,在任何位置都可以访问
private:私有成员,只能在类(自己)的成员函数中访问
protected:受保护成员,只能在类(自己)和子类中访问
注意:类中的成员变量、成员函数默认是 private,结构中的成员和成员函数默认是 public。
注意:C++中类和结构的区别只有成员函数和成员变量的默认访问权限不同。
3、构造函数
1)什么是构造函数:类的同名函数就是构造函数,没有返回值。
2)什么时候调用,谁调用,调用几次?
创建类对象时会被自动调用(每创建一个类对象,就会调用一次),对象整个生命周期中一定会被
调用一次,只能被调用一次。
3)负责干什么
成员变量的初始化,分配相关资源,设置对象的初始状态。
class 类名 : 继承方式 父类
{
// 构造函数
类名(形参表)
{
}
};
4、类型的创建过程
1.分配类型所需要空间,无论栈还是堆。
2.传递实参调用构造函数,完成如下任务:
1)根据继承表依次调用父类的构造函数
2)根据成员变量的顺序依次调用成员变量的构造函数。
3)执行构造函数体中的代码。
注意:执行构造函数的代码是整个构造函数的最后一步。
要保证构造函数代码所需要的一切资源和先决条件在该代码执行前已经准备充分,并得到正确的初始化。
5、对象的创建方法
1.在栈上创建:类名 对象;// 不需要括号
类名 对象(实参);
2.在堆上创建:类名* 对象指针 = new 类名;
类名* 对象指针 = new 类名(实参);
3.创建多个对象:
类名 对象 = {类名(实参),类名(实参),类名(实参)};
类名* 对象指针 = new 类名[n]{类名(实参),类名(实参)};
注意:通过malloc创建的类对象不能调用构造函数。
注意:通过new[]创建的对象,一定要通过delete[]释放。
6、类的声明、实现、调用
1.在头文件中声明
class 类名 : 继承方式 父类
{
成员变量;
public: // 访问控制限制符
// 构造函数
类名(形参表);
// 析构函数
~类名(void);
// 其他成员函数
返回值 函数名(参数列表);
};
2.源文件实现类的相关函数
返回值 类名::函数名(参数列表)
{
}
3.调用时只需要导入头文件,然后与类函数所在的源文件一起编译即可。
注意:如果一个类内容不多,可以考虑在头文件中完全实现。
也可以只在头文件中实现一些简单的成员函数。
注意:类中自动生成的函数,在源文件中实现时,也需要在头文件中声明。
class和struct的区别?
class的默认继承和访问权限是private,struct的默认继承和访问权限是public。class能做模板的参数,struct不行。
三、构造函数与初始化列表
1、构造函数可以被重载(同一个名字的函数有多个不同版本)
2、缺省构造是编译器自动生成的一个什么都不做的构造函数(唯一的作用就是避免编译错误)。
注意:当类实现一个有参构造时,缺省构造就不会再自动生成,如果有需要必须显示地写出来。
3、无参构造未必无参,当给有参构造的所有参数设置默认形参,调用这种构造函数就不需要传参。
注意:所谓的“编译器生成的某某函数”其实不是真正语法意义上的函数,而是功能意义上的函数,编译器作为可执行指令的生成者,它会直接生成具有某项功能的二进制指令,不需要借助高级语言语义上的函数完成此任务。
注意:如果一个类是其他类的成员变量,那么一定要保证它有一个无参构造,当B的构造函数执行时会执行成员变量的无参构造,而此时类B是无法给类A成员变量提供参数的。
4、单参构造与类型转换
如果构造函数的参数只有一个,那么Test t = n语句就不会出错,它会自动调用单参构造来达到类型转换的效果。
如果想禁止这种类型转换需要在单参构造前加 explicit
5、初始化列表
为类成员进行初始化用的。
构造函数(参数):成员1(参数1),成员2(参数2)...
const int num;
Test(int n):num(n)
{
}
通过初始化列表可以给类成员变量传递参数,以此调用类成员的有参构造。
初始化列表也可以给 const 成员、引用成员进行初始化。
#include <iostream>
using namespace std;
class A
{
public:
int num;
A(int _num)
{
num = _num;
cout<<"我A的有参构造"<<endl;
}
};
class Test
{
public:
string str;
const int num;
int& xiu;
A a;
Test(int num,const char* str,int& x):num(num),str(str),a(num),xiu(x)
{
cout<<"---"<<endl;
}
};
int main()
{
int x = 100;
Test t(10,"aa",x);
//Test t = {10};
//t.num = 100;
cout<< t.num << endl;
cout<<t.xiu<<endl;
x = 1000;
cout<<t.xiu<<endl;
}
成员的初始化顺序与初始化列表没有关系,而是在类中的定义顺序有关。
#include <iostream>
using namespace std;
class A
{
public:
A(int n)
{
cout<<"A"<<endl;
}
};
class B
{
public:
B(int n)
{
cout<<"B"<<endl;
}
};
class C
{
public:
C(int n)
{
cout<<"C"<<endl;
}
};
class Test
{
public:
A a;
C c;
B b;
//A a;
Test(int c1,int a1,int b1):c(c1),a(a1),b(b1)
{
}
};
int main()
{
// A a1;
// B b1;
// C c1;
Test t(1,2,3);
}
注意:初始化列表运行类成员变量还没有定义成功。
抛异常
throw 数据
抛异常对象
抛基本类型
注意:不能抛出局部对象的指针或引用(构造函数和析构函数不能抛出异常)。
注意:如果异常没有被捕获处理,程序就会停止。
捕获异常
try{
可以抛出异常的代码
}
catch(类型 变量名) // 根据数据类型进行捕获
{
处理异常,如果无法处理可以继续抛出异常
}
注意:捕获异常的顺序是自上而下的,而不是最精准的匹配,针对子类异常捕获时要放在父类的前面。
函数的异常声明:
返回值类型 函数名(参数列表)throw(类型1,类型2,...)
注意:如果不写异常声明表示什么类型的异常都可能抛出。
注意:如果写了异常声明表示只抛出某些类型的异常,一旦超出异常声明的范围,程序会直接停止,无法捕获。
注意:throw() 表示什么类型都不会抛出
设计异常类:
class Error
{
int errno;
char errmsg[255];
public:
Error(int errno = -1,const char* msg = "未知错误")
{
this->errno = errno;
strcpy(errmsg,msg);
}
int getError(void)
{
return errno;
}
const char* getErrmsg(void)
{
return errmsg;
}
}
更多推荐
所有评论(0)