C++ 基本语法
对象 - 对象具有状态和行为。例如:一只狗的状态 - 颜色、名称、品种,行为 - 摇动、叫唤、吃。对象是类的实例。
类 - 类可以定义为描述对象行为/状态的模板/蓝图。
方法 - 从基本上说,一个方法表示一种行为。一个类可以包含多个方法。可以在方法中写入逻辑、操作数据以及执行所有的动作。
即时变量 - 每个对象都有其独特的即时变量。对象的状态是由这些即时变量的值创建的。
C++ 关键字
下表列出了 C++ 中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称。
asm | else | new | this |
auto | enum | operator | throw |
bool | explicit | private | true |
break | export | protected | try |
case | extern | public | typedef |
catch | false | register | typeid |
char | float | reinterpret_cast | typename |
class | for | return | union |
const | friend | short | unsigned |
const_cast | goto | signed | using |
continue | if | sizeof | virtual |
default | inline | static | void |
delete | int | static_cast | volatile |
do | long | struct | wchar_t |
double | mutable | switch | while |
dynamic_cast | namespace | template |
1. asm
asm (指令字符串):允许在 C++ 程序中嵌入汇编代码。
2. auto
auto(自动,automatic)是存储类型标识符,表明变量"自动"具有本地范围,块范围的变量声明(如for循环体内的变量声明)默认为auto存储类型。
自 C++ 11 以来,auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
C++98标准中auto关键字用于自动变量的声明,但由于使用极少且多余,在 C++17 中已删除这一用法。
auto f=3.14; //double
auto s("hello"); //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型
3. bool
bool(布尔)类型,C++ 中的基本数据结构,其值可选为 true(真)或者 false(假)。C++ 中的 bool 类型可以和 int 混用,具体来说就是 0 代表 false,非 0 代表 true。bool 类型常用于条件判断和函数返回值。
4. break
break(中断、跳出),用在switch语句或者循环语句中。程序遇到 break 后,即跳过该程序段,继续后面的语句执行。
5. case
用于 switch 语句中,用于判断不同的条件类型。
6. catch
catch 和 try 语句一起用于异常处理。
7. char
char(字符,character)类型,C++ 中的基本数据结构,其值一般为 0~255 的 int。这 256 个字符对应着 256 个 ASCII 码。char 类型的数据需要用单引号 ' 括起来。
8.class
class(类)是 C++ 面向对象设计的基础。使用 class 关键字声明一个类。
9. const
const(常量的,constant)所修饰的对象或变量不能被改变,修饰函数时,该函数不能改变在该函数外面声明的变量也不能调用任何非const函数。在函数的声明与定义时都要加上const,放在函数参数列表的最后一个括号后。在 C++ 中,用 const 声明一个变量,意味着该变量就是一个带类型的常量,可以代替 #define,且比 #define 多一个类型信息,且它执行内链接,可放在头文件中声明;但在 C 中,其声明则必须放在源文件(即 .C 文件)中,在 C 中 const 声明一个变量,除了不能改变其值外,它仍是一具变量。
const double pi(3.14159);
const double pi = 3.14159;
// 常量引用。
#include <iostream>
#include <string.h>
using namespace std;
// 代码的统一性
typedef struct {
char name[20];
int age;
}Student;
// 插入数据库,Student的信息给插入数据库
// const常量引用 禁止对数据信息Student进行修改 只能读取
void insertStudent(const Student & student) {
// 有个内鬼 卧底 把数据信息name改成 "李元霸" 所以添加 const常量引用 禁止对数据信息进行修改
// strcpy(student.name, "李元霸"); 不能这样修改 报错
Student student2 = {"刘奋", 43};
// student = student2; 不能这样修改 报错
// 只读的了,可以安心插入数据库了
cout << student.name << "," << student.age << endl;
}
int main() {
// 用户提交的Student数据
Student student = {"张无忌", 30};
// Student的信息给插入数据库
insertStudent(student);
return 0;
}
11. continue
continue(继续)关键字用于循环结构。它使程序跳过代码段后部的部分,与 break 不同的是,continue 不是进入代码段后的部分执行,而是重新开始新的循环。因而它是"继续循环"之意,不是 break(跳出)。
12. default
default(默认、缺省)用于 switch 语句。当 switch 所有的 case 都不满足时,将进入 default 执行。default 只能放在 switch 语句所有的 case 之后,并且是可选的。
13. delete
delete(删除)释放程序动态申请的内存空间。delete 后面通常是一个指针或者数组 [],并且只能 delete 通过 new 关键字申请的指针,否则会发生段错误。
14. do
do-while是一类循环结构。与while循环不同,do-while循环保证至少要进入循环体一次。
15. double
double(双精度)类型,C++ 中的基本数据结构,以双精度形式存储一个浮点数。
18. enum枚举
enum(枚举)类型,给出一系列固定的值,只能在这里面进行选择一个。
由用户定义的若干枚举常量的集合。
如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型。所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数]
} 枚举变量;
如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始。
enum color { red, green, blue } c;
c = blue;
默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。例如,在下面的枚举中,green 的值为 5。
//blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0。
enum color { red, green=5, blue };
19. explicit
explicit(显式的)的作用是"禁止单参数构造函数"被用于自动型别转换,其中比较典型的例子就是容器类型。在这种类型的构造函数中你可以将初始长度作为参数传递给构造函数。
20. export
为了访问其他编译单元(如另一代码文件)中的变量或对象,对普通类型(包括基本数据类、结构和类),可以利用关键字 extern,来使用这些变量或对象时;但是对模板类型,则必须在定义这些模板类对象和模板函数时,使用标准 C++ 新增加的关键字 export(导出)。
22. false=0
false(假的),C++ 的基本数据结构 bool 类型的值之一。等同于 int 的 0 值。
23. float
float(浮点数),C++ 中的基本数据结构,精度小于 double。
25. friend
friend(友元)声明友元关系。友元可以访问与其有 friend 关系的类中的 private/protected 成员,通过友元直接访问类中的 private/protected 成员的主要目的是提高效率。友元包括友元函数和友元类。
26. goto
goto(转到),用于无条件跳转到某一标号处开始执行。
28. inline
inline(内联)函数的定义将在编译时在调用处展开。inline 函数一般由短小的语句组成,可以提高程序效率。
29. int
int(整型,integer),C++ 中的基本数据结构,用于表示整数,精度小于 long。
30. long
long(长整型,long integer),C++ 中的基本数据结构,用于表示长整数。
31. mutable
mutable(易变的)是 C++ 中一个不常用的关键字。只能用于类的非静态和非常量数据成员。由于一个对象的状态由该对象的非静态数据成员决定,所以随着数据成员的改变,对像的状态也会随之发生变化。如果一个类的成员函数被声明为 const 类型,表示该函数不会改变对象的状态,也就是该函数不会修改类的非静态数据成员。但是有些时候需要在该类函数中对类的数据成员进行赋值,这个时候就需要用到 mutable 关键字。
class Example {
public:
int get_value() const {
return value_; // const 关键字表示该成员函数不会修改对象中的数据成员
}
void set_value(int value) const {
value_ = value; // mutable 关键字允许在 const 成员函数中修改成员变量
}
private:
mutable int value_;
};
32. namespace
namespace(命名空间)用于在逻辑上组织类,是一种比类大的结构。
33. new
new(新建)用于新建一个对象。new 运算符总是返回一个指针。由 new 创建
34. operator
operator(操作符)用于操作符重载。这是 C++ 中的一种特殊的函数。
38.register
register(寄存器)声明的变量称着寄存器变量,在可能的情况下会直接存放在机器的寄存器中;但对 32 位编译器不起作用,当 global optimizations(全局优化)开的时候,它会做出选择是否放在自己的寄存器中;不过其它与 register 关键字有关的其它符号都对32位编译器有效。
void example_function(register int num) {
// register 关键字建议编译器将变量 num 存储在寄存器中的局部变量
// 以提高程序执行速度
// 但是实际上是否会存储在寄存器中由编译器决定
register int num;
}
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。以提高程序执行速度。
定义 'register' 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
41. short
short(短整型,short integer),C++ 中的基本数据结构,用于表示整数,精度小于 int。
42. signed
signed(有符号),表明该类型是有符号数,和 unsigned 相反。数字类型(整型和浮点型)都可以用 signed 修饰。但默认就是 signed,所以一般不会显式使用。
43. sizeof
由于 C++ 每种类型的大小都是由编译器自行决定的,为了增加可移植性,可以用 sizeof 运算符获得该数据类型占用的字节数。
44. static
static(静态的)静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为 0,使用时可改变其值。静态变量或静态函数,只有本文件内的代码才可访问它,它的名字(变量名或函数名)在其它文件中不可见。因此也称为"文件作用域"。在 C++ 类的成员变量被声明为 static(称为静态成员变量),意味着它被该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见;而类的静态成员函数也只能访问静态成员(变量或函数)。类的静态成员变量必须在声明它的文件范围内进行初始化才能使用,private 类型的也不例外。
void example_function() {
static int count = 0; // static 关键字使变量 count 存储在程序生命周期内都存在
count++;
}
46. struct
struct(结构)类型,类似于 class 关键字,与 C 语言兼容(class 关键字是不与 C 语言兼容的),可以实现面向对象程序设计。
48. template
template(模板),C++ 中泛型机制的实现。
49. this
this 返回调用者本身的指针。
50. throw
throw(抛出)用于实现 C++ 的异常处理机制,可以通过 throw 关键字"抛出"一个异常。
51. true!=0
true(真的),C++ 的基本数据结构 bool 类型的值之一。等同于 int 的非 0 值。
53. typedef声明
typedef(类型定义,type define),您可以使用 typedef 为一个已有的类型取一个新的名字。
typedef 类型 定义名;
typedef type newname;
typedef int feet; //feet 是 int 的另一个名称别名
feet distance; //为别名声明一个变量distance
类型说明定义了一个数据类型的新名字而不是定义一种新的数据类型。定义名表示这个类型的新名字。
54. typeid
指出指针或引用指向的对象的实际派生类型。
55. typename
typename(类型名字)关键字告诉编译器把一个特殊的名字解释成一个类型。在下列情况下必须对一个 name 使用 typename 关键字:
- 1. 一个唯一的name(可以作为类型理解),它嵌套在另一个类型中的。
- 2. 依赖于一个模板参数,就是说:模板参数在某种程度上包含这个name。当模板参数使编译器在指认一个类型时产生了误解.
56. union
union(联合),类似于 enum。不同的是 enum 实质上是 int 类型的,而 union 可以用于所有类型,并且其占用空间是随着实际类型大小变化的。
57. unsigned
unsigned(无符号),表明该类型是无符号数,和 signed 相反。
58. using
表明使用 namespace。
59. virtual
virtual(虚的),C++ 中用来实现多态机制。
60. void
void(空的),可以作为函数返回值,表明不返回任何数据;可以作为参数,表明没有参数传入(C++中不是必须的);可以作为指针使用。
61. volatile
修饰符 volatile 告诉该变量的值可能会被程序以外的因素改变,如硬件或其他线程。
volatile(不稳定的)限定一个对象可被外部进程(操作系统、硬件或并发线程等)改变,声明时的语法如下:
int volatile nVint;
这样的声明是不能达到最高效的,因为它们的值随时会改变,系统在需要时会经常读写这个对象的值。因此常用于像中断处理程序之类的异步进程进行内存单元访问。
volatile int num = 20; // 定义变量 num,其值可能会在未知的时间被改变
C++ 数据类型
使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当您创建一个变量时,就会在内存中保留一些空间。
您可能需要存储各种数据类型(比如字符型、宽字符型、整型、浮点型、双浮点型、布尔型等)的信息,操作系统会根据变量的数据类型,来分配内存和决定在保留内存中存储什么。
基本的内置类型
类型 | 关键字 |
---|---|
布尔型 | bool |
字符型 | char |
整型 | int |
浮点型 | float |
双浮点型 | double |
无类型 | void |
宽字符型 | wchar_t |
修饰符类型
C++ 允许在 char、int 和 double 数据类型前放置修饰符类型(signed unsigned short long )进行修饰。
修饰符是用于改变变量类型的行为的关键字,它更能满足各种情境的需求。
下面列出了数据类型修饰符:
signed:表示变量可以存储负数。对于整型变量来说,signed 可以省略,因为整型变量默认为有符号类型。
unsigned:表示变量不能存储负数。对于整型变量来说,unsigned 可以将变量范围扩大一倍。
short:表示变量的范围比 int 更小。short int 可以缩写为 short。
long:表示变量的范围比 int 更大。long int 可以缩写为 long。
long long:表示变量的范围比 long 更大。C++11 中新增的数据类型修饰符。
float:表示单精度浮点数。
double:表示双精度浮点数。
bool:表示布尔类型,只有 true 和 false 两个值。
char:表示字符类型。
wchar_t:表示宽字符类型,可以存储 Unicode 字符。
修饰符 signed、unsigned、long 和 short 可应用于整型,signed 和 unsigned 可应用于字符型,long 可应用于双精度型。
这些修饰符也可以组合使用,修饰符 signed 和 unsigned 也可以作为 long 或 short 修饰符的前缀。例如:unsigned long int。
C++ 允许使用速记符号来声明无符号短整数或无符号长整数。您可以不写 int,只写单词 unsigned、short 或 long,int 是隐含的
signed int num1 = -10; // 定义有符号整型变量 num1,初始值为 -10
unsigned int num2 = 20; // 定义无符号整型变量 num2,初始值为 20
short int num1 = 10; // 定义短整型变量 num1,初始值为 10
long int num2 = 100000; // 定义长整型变量 num2,初始值为 100000
long long int num1 = 10000000000; // 定义长长整型变量 num1,初始值为 10000000000
float num1 = 3.14f; // 定义单精度浮点数变量 num1,初始值为 3.14
double num2 = 2.71828; // 定义双精度浮点数变量 num2,初始值为 2.71828
bool flag = true; // 定义布尔类型变量 flag,初始值为 true
char ch1 = 'a'; // 定义字符类型变量 ch1,初始值为 'a'
wchar_t ch2 = L'你'; // 定义宽字符类型变量 ch2,初始值为 '你'
注意:不同系统会有所差异,一字节为 8 位。
默认情况下,int、short、long都是默认带signed符号
从上表可得知,变量的大小会根据编译器和所使用的电脑而有所不同。
下面实例会输出您电脑上各种数据类型的大小。
sizeof() 运算符用来获取各种数据类型的大小。
类型 | 位 | 范围 |
---|---|---|
char | 1 个字节 | -128 到 127 或者 0 到 255 |
unsigned char | 1 个字节 | 0 到 255 |
signed char | 1 个字节 | -128 到 127 |
int | 4 个字节 | -2147483648 到 2147483647 |
unsigned int | 4 个字节 | 0 到 4294967295 |
signed int | 4 个字节 | -2147483648 到 2147483647 |
short int | 2 个字节 | -32768 到 32767 |
unsigned short int | 2 个字节 | 0 到 65,535 |
signed short int | 2 个字节 | -32768 到 32767 |
long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
signed long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
unsigned long int | 8 个字节 | 0 到 18,446,744,073,709,551,615 |
float | 4 个字节 | 精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字) |
double | 8 个字节 | 双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字) |
long double | 16 个字节 | 长双精度型 16 个字节(128位)内存空间,可提供18-19位有效数字。 |
wchar_t | 2 或 4 个字节 | 1 个宽字符 |
类型的限定符关键字
类型限定符提供了变量的额外信息,用于在定义变量或函数时改变它们的默认行为的关键字。
限定符 | 含义 |
---|---|
const | const 定义常量,表示该变量的值不能被修改。。 |
volatile | 修饰符 volatile 告诉该变量的值可能会被程序以外的因素改变,如硬件或其他线程。。 |
restrict | 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 |
mutable | 表示类中的成员变量可以在 const 成员函数中被修改。 |
static | 用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。 |
register | 用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。 |
const 实例
const int NUM = 10; // 定义常量 NUM,其值不可修改
const int* ptr = &NUM; // 定义指向常量的指针,指针所指的值不可修改
int const* ptr2 = &NUM; // 和上面一行等价
volatile 实例
volatile int num = 20; // 定义变量 num,其值可能会在未知的时间被改变
mutable 实例
class Example {
public:
int get_value() const {
return value_; // const 关键字表示该成员函数不会修改对象中的数据成员
}
void set_value(int value) const {
value_ = value; // mutable 关键字允许在 const 成员函数中修改成员变量
}
private:
mutable int value_;
};
static 实例
void example_function() {
static int count = 0; // static 关键字使变量 count 存储在程序生命周期内都存在
count++;
}
register 实例
void example_function(register int num) {
// register 关键字建议编译器将变量 num 存储在寄存器中
// 以提高程序执行速度
// 但是实际上是否会存储在寄存器中由编译器决定
}
存储类
存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。
1.auto 存储类
自 C++ 11 以来,auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
根据初始化表达式自动推断被声明的变量的类型,如:
auto f=3.14; //double
auto s("hello"); //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型
2.register 存储类
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。
寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 'register' 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
{
register int miles;
}
3.static 存储类
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
在 C++ 中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。
#include <iostream>
// 函数声明
void func(void);
static int count = 10; /* 全局变量 */
int main()
{
while(count--)
{
func();
}
return 0;
}
// 函数定义
void func( void )
{
static int i = 5; // 局部静态变量
i++;
std::cout << "变量 i 为 " << i ;
std::cout << " , 变量 count 为 " << count << std::endl;
}
4.extern 存储类
extern(外部的)声明变量或函数为外部链接,即该变量或函数名在其它文件中可见。
被其修饰的变量(外部变量)是静态分配空间的,即程序开始时分配,结束时释放。
用其声明的变量或函数应该在别的文件或同一文件的其它地方定义(实现)。
在文件内声明一个变量或函数默认为可被外部使用。
在 C++ 中,还可用来指定使用另一语言进行链接,这时需要与特定的转换符一起使用。
目前仅支持 C 转换标记,来支持 C 编译器链接。使用这种情况有两种形式:
extern "C" 声明语句extern "C" { 声明语句块 }
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。
当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。
可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数
第一个文件:main.cpp
#include <iostream>
int count ;
// 声明文件support.cpp中已经定义的函数write_extern()
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
第二个文件:support.cpp
#include <iostream>
//extern声明 文件 main.cpp 中定义的变量 count
// 第二个文件中的 extern 关键字用于声明已经在第一个文件 main.cpp 中定义的 count
extern int count;
void write_extern(void)
{
std::cout << "Count is " << count << std::endl;
}
5.mutable 存储类
mutable 说明符仅适用于类的对象,这将在本教程的最后进行讲解。它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
6.thread_local 存储类
使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
thread_local 说明符可以与 static 或 extern 合并。
可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
以下演示了可以被声明为 thread_local 的变量:
thread_local int x; // 命名空间下的全局变量
class X
{
static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s; // X::s 是需要定义的
void foo()
{
thread_local std::vector<int> v; // 本地变量
}
C++ 引用(重要)
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
C++ 引用 vs 指针
引用很容易与指针混淆,它们之间有三个主要的不同:
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
C++ 中创建引用
& 读作引用
试想变量名称是变量附属在内存位置中的标签,您可以把引用当成是变量附属在内存位置中的第二个标签。因此,您可以通过原始变量名称或引用来访问变量的内容。例如:
//声明int类型变量i,初始化为17
int i = 17;
//为 i 声明引用变量r "r 是一个初始化为 i 的整型引用"
int& r = i;
// "s 是一个初始化为 d 的 double 型引用"
double& s = d;
1.原变量值 引用变量值一样 他们的内存地址指针也一样
#include <iostream>
using namespace std;
int main ()
{
// 声明简单的变量
int i;
// 声明引用变量
int& r = i;
i = 5;
cout << "i=" << i << endl; //i=5
cout << "r=" << r << endl; //r=5
double d;
double& s = d;
d = 11.7;
cout << "d=" << d << endl; //d=11.7
cout << "s=" << s << endl; // s=11.7
return 0;
}
#include <iostream>
using namespace std;
/**
引用的原理 &与 不会改变变量的内存指针地址 变量的数值可能会改变
*/
int mainT5() {
// 引用&
// todo 第一部分,不采用 引用 & n1 n2的数值一样 但是他们的内存地址不一样
int n1 = 999;
int n2 = n1;
cout << "n1值=" << n1 << " --- n2值=" << n2 << endl; //n1值=999 --- n2值=999
cout << "n1内存地址=" << &n1 << "--- n2内存地址=" << &n2 << endl; //n1内存地址=0x61fefc--- n2内存地址=0x61fef8
// todo 第二部分,采用引用 & n3 n4 的数值一样 他们的内存地址一样
int n3 =1000;
int & n4 = n3;
cout << "n3值=" << n3 << " --- n4值=" << n4 << endl; // n3值=1000 --- n4值=1000
cout << "n3内存地址=" << &n3 << "--- n4内存地址=" << &n4 << endl; // n3内存地址=0x61fef0--- n4内存地址=0x61fef0
n4 = 777;
cout << "再次输出 n3值=" << n3 << " --- n4值=" << n4 << endl; // 再次输出 n3值=777 --- n4值=777
cout << "再次输出 n3内存地址=" << &n3 << "--- n4内存地址=" << &n4 << endl; // 再次输出 n3内存地址=0x61fef0--- n4内存地址=0x61fef0
int & n9 = n3;
n9 = 9527;
cout << "再再次输出 n3值=" << n3 << " --- n4值=" << n4 << endl; // 再再次输出 n3值=9527 --- n4值=9527
cout << "再再次输出 n3内存地址=" << &n3 << "--- n4内存地址=" << &n4 << endl;//再再次输出 n3内存地址=0x61feec--- n4内存地址=0x61feec
return 0;
}
2. 把引用作为参数 7T5
C++ 支持把引用作为参数传给函数,这比传一般的参数更安全。
#include <iostream>
using namespace std;
void numberChange(int number1, int number2) {
int temp;
temp=number1;
number1=number2;
number2=temp;
}
// 接收number1number2的地址&number1=pInt1, &number2=pInt2 ,取改指针地址的值,来完成数值的互换
void numberChange2(int* pInt1, int* pInt2) {
int temp=0;
temp=*pInt1;
*pInt1=*pInt2;
*pInt2=temp;
}
// & 表示引用 int& x = number1 int& y =number2
//形参x 和 实参number1的内存指针地址一样的 形参y 和 实参number2的内存指针地址一样的
void numberChange3(int& x, int& y) {
int temp=0;
temp=x;
x=y;
y=temp;
}
/**
引用的原理 &与 不会改变变量的内存指针地址 变量的数值可能会改变
*/
int main() {
// 引用&
// todo 第一部分,不采用 引用 & n1 n2的数值一样 但是他们的内存地址不一样
int n1 = 999;
int n2 = n1;
cout << "n1值=" << n1 << " --- n2值=" << n2 << endl; //n1值=999 --- n2值=999
cout << "n1内存地址=" << &n1 << "--- n2内存地址=" << &n2 << endl; //n1内存地址=0x61fefc--- n2内存地址=0x61fef8
// todo 第二部分,采用引用 & n3 n4 的数值一样 他们的内存地址一样
int n3 =1000;
int & n4 = n3;
cout << "n3值=" << n3 << " --- n4值=" << n4 << endl; // n3值=1000 --- n4值=1000
cout << "n3内存地址=" << &n3 << "--- n4内存地址=" << &n4 << endl; // n3内存地址=0x61fef0--- n4内存地址=0x61fef0
n4 = 777;
cout << "再次输出 n3值=" << n3 << " --- n4值=" << n4 << endl; // 再次输出 n3值=777 --- n4值=777
cout << "再次输出 n3内存地址=" << &n3 << "--- n4内存地址=" << &n4 << endl; // 再次输出 n3内存地址=0x61fef0--- n4内存地址=0x61fef0
int & n9 = n3;
n9 = 9527;
cout << "再再次输出 n3值=" << n3 << " --- n4值=" << n4 << endl; // 再再次输出 n3值=9527 --- n4值=9527
cout << "再再次输出 n3内存地址=" << &n3 << "--- n4内存地址=" << &n4 << endl;//再再次输出 n3内存地址=0x61feec--- n4内存地址=0x61feec
int number1 = 10;
int number2 = 20;
//C C++ 如果把变量 number1 number2 直接传递给被调用函数numberChange(number1,number2) 是无法改变主函数传递的参数number1 number2的结果值的
numberChange(number1,number2);
cout << "number1= " << number1 << " number2=" << number2 <<endl; //number1= 10 number2=20
//如果传递指针&number1, &number2给被调用函数numberChange2(&number1, &number2) 是可以改变主函数传递的参数number1 number2的结果值的
numberChange2(&number1, &number2);
cout << "number1= " << number1 << " number2=" << number2 <<endl; //number1= 20 number2=10
//如果传递给被调用函数numberChange3(int& x, int& y)的形参参数 是实参参数的引用 那么 也可以达到 改变实参参数number1 number2的结果值的
numberChange3(number1, number2);
cout << "number1= " << number1 << " number2=" << number2 <<endl; //number1= 10 number2=20
return 0;
}
&引用的本质就是指针
使用&引用参数 互相换两个数; 使用指针参数 互相换两个数 16T1
// TODO 1.引用进阶。
#include <iostream>
#include <vector>
using namespace std;
// TODO [引用的本质就是指针]
// TODO 使用指针参数 互相换两个数
void changeNumber(int *n1, int *n2) {
int temp = *n1;
*n1 = *n2;
*n2 = temp;
}
// TODO 引用 互相换两个数 [&引用的本质就是指针]
void changeNumber2(int & n1, int & n2) { // 隐式:int * n1, int * n2
int temp = n1; // int temp = *n1;
n1 = n2; // *n1 = *n2;
n2 = temp; // *n2 = temp;
}
int main16T1(){
int number1 = 100;
int number2 = 200;
cout <<"number1="<< number1 << " ,number2= " << number2 << endl; //number1=100 ,number2= 200
changeNumber(&number1, &number2);
cout <<"number1="<< number1 << " ,number2= " << number2 << endl; //number1=200 ,number2= 100
// todo 我下面不需要去地址给他,为什么也可以,C、C++编译器 已经帮我们做了,所以无需写 &num 直接num
changeNumber2(number1, number2);
cout <<"number1="<< number1 << " ,number2= " << number2 << endl; //number1=100 ,number2= 200
return 0;
}
3.把引用作为返回值
把引用作为函数的返回值, 就像返回其他数据类型一样。
通过使用引用来替代指针,会使 程序更容易阅读和维护。 函数可以返回一个引用,方式与返回一个指针类似。
当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues(int i) {
double& ref = vals[i];
return ref; // 返回第 i 个元素的引用,ref 是一个引用变量,ref 引用 vals[i]
}
int main(){
// 把引用作为函数的返回值
for(int i=0;i<5;i++){
cout << "输出数组vals改变前的元素值vals[" << i << "] = "<<vals[i]<<endl;
}
/**
输出数组vals改变前的元素值vals[0] = 10.1
输出数组vals改变前的元素值vals[1] = 12.6
输出数组vals改变前的元素值vals[2] = 33.1
输出数组vals改变前的元素值vals[3] = 24.1
输出数组vals改变前的元素值vals[4] = 50
*/
// 传入角标i 改变元素值
for(int i=0;i<5;i++){
// double& result;
if(i==1){
double& result=setValues(i);
result= 20.23; // 改变第 2 个元素=20.23
}
if(i==3) {
double& result=setValues(3);
result= 70.8; // 改变第 4 个元素=70.8
}
cout << "再次输出数组vals改变后的元素值vals[" << i << "] = "<<vals[i]<<endl;
}
/**
再次输出数组vals改变后的元素值vals[0] = 10.1
再次输出数组vals改变后的元素值vals[1] = 20.23
再次输出数组vals改变后的元素值vals[2] = 33.1
再次输出数组vals改变后的元素值vals[3] = 70.8
再次输出数组vals改变后的元素值vals[4] = 50
*/
return 0;
}
}
4. 左值 右值 引用 16T1_2
getInfo函数的info 与 main函数的result 是旧与新的两个变量而已,他们是值传递,所以右值修改时,影响不了里面的旧变量info = "AAA"
getInfo函数的info 与 main函数的result 是引用关系,是同一块内存空间(同一个指向) 只不过有多个不同别名而已,所以右值修改时,直接影响旧变量变化
// TODO 1.引用进阶。
#include <iostream>
#include <vector>
using namespace std;
// TODO [左值 右值 引用]
class Student7 {
private:
string info = "AAA"; // 变量的默认旧值 "AAA"
// TODO 第一种情况【getInfo函数的info 与 main函数的result 是旧与新的两个变量而已,他们是值传递,所以右值修改时,影响不了里面的旧变量info = "AAA"】
public:
string getInfo() {
return this->info;
}
// TODO 第二种情况【getInfo函数的info 与 main函数的result 是引用关系,是同一块内存空间(同一个指向) 只不过有多个不同别名而已,所以右值修改时,直接影响旧变量变化】
string & getInfo_front() {
return this->info;
}
};
int main() { // 16T1_2
Student7 student;
string result_old= student.getInfo();
cout << "结构体 student的变量info 默认旧值= " << result_old << endl; // 结构体 student的变量info 默认旧值= AAA
// TODO 第一种情况
student.getInfo() = "九阳神功";
string result_new = student.getInfo();
cout << "第一种情况:结构体 student的变量info 新值 = " << result_new << endl; // 第一种情况:结构体 student的变量info 新值= AAA
// TODO 第二种情况
student.getInfo_front() = "三分归元气"; // 右值 修改内容
string result_new_2 = student.getInfo_front(); // 左值 获取内容
cout << "第二种情况:结构体 student的变量info 新值 = " << result_new_2 << endl; // 第二种情况:结构体 student的变量info 新值 = 三分归元气
}
C++ 变量类型
C++ 中每个变量都有指定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。
C++ 也允许定义各种其他类型的变量,比如枚举、指针、数组、引用、数据结构、类等等,这将会在后续的章节中进行讲解。
-
整数类型(Integer Types):
int
:用于表示整数,通常占用4个字节。short
:用于表示短整数,通常占用2个字节。long
:用于表示长整数,通常占用4个字节。long long
:用于表示更长的整数,通常占用8个字节。
-
浮点类型(Floating-Point Types):
float
:用于表示单精度浮点数,通常占用4个字节。double
:用于表示双精度浮点数,通常占用8个字节。long double
:用于表示更高精度的浮点数,占用字节数可以根据实现而变化。
-
字符类型(Character Types):
char
:用于表示字符,通常占用1个字节。wchar_t
:用于表示宽字符,通常占用2或4个字节。char16_t
:用于表示16位Unicode字符,占用2个字节。char32_t
:用于表示32位Unicode字符,占用4个字节。
-
布尔类型(Boolean Type):
bool
:用于表示布尔值,只能取true
或false
。
-
枚举类型(Enumeration Types):
enum
:用于定义一组命名的整数常量。
-
指针类型(Pointer Types):
type*
:用于表示指向类型为type
的对象的指针。
-
数组类型(Array Types):
type[]
或type[size]
:用于表示具有相同类型的元素组成的数组。
-
结构体类型(Structure Types):
struct
:用于定义包含多个不同类型成员的结构。
-
类类型(Class Types):
class
:用于定义具有属性和方法的自定义类型。
-
共用体类型(Union Types):
union
:用于定义一种特殊的数据类型,它可以在相同的内存位置存储不同的数据类型。
C++ 中的变量定义
变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储。
变量定义指定一个数据类型,并包含了该类型的一个或多个变量的列表.
type 必须是一个有效的 C++ 数据类型,可以是 char、wchar_t、int、float、double、bool 或任何用户自定义的对象,variable_list 可以由一个或多个标识符名称组成,多个标识符之间用逗号分隔。
type variable_list;
int i, j, k;
char c, ch;
float f, salary;
double d;
变量可以在声明的时候被初始化(指定一个初始值)
type variable_name = value;
extern int d = 3, f = 5; // d 和 f 的声明
int d = 3, f = 5; // 定义并初始化 d 和 f
byte z = 22; // 定义并初始化 z
char x = 'x'; // 变量 x 的值为 'x'
C++ 中的变量声明
当您使用多个文件且只在其中一个文件中定义变量时(定义变量的文件在程序连接时是可用的),变量声明就显得非常有用。您可以使用 extern 关键字在任何地方声明一个变量。虽然您可以在 C++ 程序中多次声明一个变量,但变量只能在某个文件、函数或代码块中被定义一次。
#include <iostream>
using namespace std;
// 变量声明
extern int a, b;
extern int c;
extern float f;
int main ()
{
// 变量定义
int a, b;
int c;
float f;
// 实际初始化
a = 10;
b = 20;
c = a + b;
cout << c << endl ;
f = 70.0/3.0;
cout << f << endl ;
return 0;
}
在函数声明时,提供一个函数名,而函数的实际定义则可以在任何地方进行。
// 函数声明
int func();
int main()
{
// 函数调用
int i = func();
}
// 函数定义
int func()
{
return 0;
}
C++ 变量作用域
一般来说有三个地方可以定义变量:
-
在函数或一个代码块内部声明的变量,称为局部变量。
-
在函数参数的定义中声明的变量,称为形式参数。
-
在所有函数外部声明的变量,称为全局变量。
作用域是程序的一个区域,变量的作用域可以分为以下几种:
-
局部作用域:在函数内部声明的变量具有局部作用域,它们只能在函数内部访问。局部变量在函数每次被调用时被创建,在函数执行完后被销毁。
-
全局作用域:在所有函数和代码块之外声明的变量具有全局作用域,它们可以被程序中的任何函数访问。全局变量在程序开始时被创建,在程序结束时被销毁。
-
块作用域:在代码块内部声明的变量具有块作用域,它们只能在代码块内部访问。块作用域变量在代码块每次被执行时被创建,在代码块执行完后被销毁。
-
类作用域:在类内部声明的变量具有类作用域,它们可以被类的所有成员函数访问。类作用域变量的生命周期与类的生命周期相同。
注意:如果在内部作用域中声明的变量与外部作用域中的变量同名,则内部作用域中的变量将覆盖外部作用域中的变量。
局部变量(系统不会默认自动初始化)
在函数或一个代码块内部声明的变量,称为局部变量。它们只能被函数内部或者代码块内部的语句使用。
#include <iostream>
using namespace std;
int main ()
{
// 局部变量声明
int a, b;
int c;
// 实际初始化
a = 10;
b = 20;
c = a + b;
cout << c;
return 0;
}
全局变量(系统会默认自动初始化)
在所有函数外部定义的变量(通常是在程序的头部),称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。
全局变量可以被任何函数访问。也就是说,全局变量一旦声明,在整个程序中都是可用的。
#include <iostream>
using namespace std;
// 全局变量声明
int g;
int main ()
{
// 局部变量声明
int a, b;
// 实际初始化
a = 10;
b = 20;
g = a + b;
cout << g;
return 0;
}
在程序中,局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值。
#include <iostream>
using namespace std;
// 全局变量声明
int g = 20;
int main ()
{
// 局部变量声明
int g = 10;
cout << g; //g=10
return 0;
}
全局变量系统默认自动初始化值
数据类型 | 初始化默认值 |
---|---|
int | 0 |
char | '\0' |
float | 0 |
double | 0 |
pointer | NULL |
块作用域
块作用域指的是在代码块内部声明的变量:
内部的代码块中声明了一个名为 a 的变量,它与外部作用域中的变量 a 同名。内部作用域中的变量 a 将覆盖外部作用域中的变量 a,在内部作用域中访问 a 时输出的是20,而在外部作用域中访问 a 时输出的是 20。
#include <iostream>
int main() {
int a = 10;
{
a = 20; // 块作用域变量
std::cout << "块变量a=" << a << std::endl; //块变量a=20
}
std::cout << "外部变量a=" << a << std::endl; //外部变量a=10
return 0;
}
类作用域
类作用域指的是在类内部声明的变量:
#include <iostream>
class MyClass {
public:
static int class_var; // 类作用域变量
};
int MyClass::class_var = 30;
int main() {
std::cout << "类变量MyClass::class_var=" << MyClass::class_var << std::endl;
return 0;
}
// 类变量MyClass::class_var=30
C++ 常量(同C常量)
C++ 运算符
1.算术运算符
2.关系运算符
3.逻辑运算符
4.位运算符
5.赋值运算符
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相 |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
6. 杂项运算符
6.1 sizeof运算符 返回变量的大小
sizeof 是一个关键字,它是一个编译时运算符,用于判断变量或数据类型的字节大小。
sizeof 运算符可用于获取类、结构、共用体和其他用户自定义数据类型的大小。
data type 是要计算大小的数据类型,包括类、结构、共用体和其他用户自定义数据类型。
sizeof (data type)
#include <iostream>
using namespace std;
int main()
{
cout << "Size of char : " << sizeof(char) << endl; //Size of char : 1
cout << "Size of int : " << sizeof(int) << endl; //Size of int : 4
cout << "Size of short int : " << sizeof(short int) << endl;//Size of short int : 2
cout << "Size of long int : " << sizeof(long int) << endl;//Size of long int : 4
cout << "Size of float : " << sizeof(float) << endl; //Size of float : 4
cout << "Size of double : " << sizeof(double) << endl; // Size of double : 8
cout << "Size of wchar_t : " << sizeof(wchar_t) << endl; // Size of wchar_t : 4
return 0;
}
6.2 Condition ? X : Y 条件运算符
? : 表达式的值取决于 Exp1 的计算结果。如果 Exp1 为真,则计算 Exp2 的值,且 Exp2 的计算结果则为整个 ? : 表达式的值。如果 Exp1 为假,则计算 Exp3 的值,且 Exp3 的计算结果则为整个 ? : 表达式的值。
Exp1 ? Exp2 : Exp3;
? 被称为三元运算符,因为它需要三个操作数,可以用来代替如下所示的 if-else 语句:
if(condition){
var = X;
}else{
var = Y;
}
// 如果 y 小于 10,则 var 被赋值为 30,如果 y 不小于 10,则 var 被赋值为 40
if(y < 10){
var = 30;
}else{
var = 40;
}
或者
var = (y < 10) ? 30 : 40;
6.3 .(点)运算符 和 ->(箭头)运算符
.(点)运算符 和 ->(箭头)运算符 用于引用类、结构和共用体的成员。
struct Employee {
char first_name[16];
int age;
} emp;
// (.)点运算符
// 把值 "zara" 赋给对象 emp 的 first_name 成员
strcpy(emp.first_name, "zara");
// (->)箭头运算符
// p_emp 是一个指针,指向类型为 Employee 的对象,则要把值 "zara" 赋给对象 emp 的 first_name 成员,
strcpy(p_emp->first_name, "zara");
6.4 Cast强制转换运算符(很重要)
Cast强制转换运算符把一种数据类型转换为另一种数据类型
(type) expression
(1.)常量转换 const_cast<type_id> (expression)
常量转换用于将 const 类型的对象转换为非 const 类型的对象。
常量转换只能用于转换掉 const 属性,不能改变对象的类型。
const_cast<type_id> (expression)
const_cast 运算符用于修改类型的 const / volatile 属性。除了 const 或 volatile 属性之外,目标类型必须与源类型相同。这种类型的转换主要是用来操作所传对象的 const 属性,可以加上 const 属性,也可以去掉 const 属性。
常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,
并且仍然指向原来的对象;常量对象被转换成非常量对象。
常量转换,将const int转换为int
#include <iostream>
#include <iostream>
#include <string>
int main(){
const int i = 10;
int& r = const_cast<int&>(i); // 常量转换,将const int转换为int
std::cout << "r=" <<r<< std::endl; // r=10
return 0;
}
将 常量指针const Person* 转成 非常量指针Person * 17T7
// 3.四种类型转换。 const_cast const修饰的 都可以去转换
#include <iostream>
using namespace std;
class Person {
public:
string name = "default";
};
int main17T7() {
//常量 指针
const Person* p1 = new Person();
// p1->name = "Derry"; // 报错:常量指针,不能修改值
cout << "p1->name="<<p1->name << endl; //p1->name=default
Person* p2 = const_cast<Person *>(p1); // 转成 非常量指针 Person *
p2->name="Derry";
cout << "p1->name="<<p1->name << endl; //p1->name=Derry
cout << "p2->name="<<p2->name << endl; //p2->name=Derry
return 0;
}
(2.)动态转换 dynamic_cast<type> (expr)
动态转换通常用于将一个基类指针或引用转换为派生类指针或引用。
动态转换在运行时进行类型检查,如果不能进行转换则返回空指针或引发异常。
dynamic_cast 在运行时执行转换,验证转换的有效性。如果转换未执行,则转换失败,表达式 expr 被判定为 null。dynamic_cast 执行动态转换时,type 必须是类的指针、类的引用或者 void*,如果 type 是类指针类型,那么 expr 也必须是一个指针,如果 type 是一个引用,那么 expr 也必须是一个引用。
dynamic_cast<type> (expr)
将基类指针转换为派生类指针
#include <iostream>
#include <iostream>
#include <string>
class Base {
public:
virtual ~Base() {
}
public:
std::string baseName;
};
class Derived : public Base {
public:
std::string derivedName;
};
int main(){
Base* ptr_base = new Derived;
ptr_base->baseName="父";
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base);
ptr_derived->derivedName="子";
std::cout << "ptr_derived->derivedName=" <<ptr_derived->derivedName<< std::endl; //ptr_derived->derivedName=子
return 0;
}
动态转换 dynamic_cast 父类转子类 17T9
// 3.四种类型转换。 dynamic 动态转换 字符类多态 运行期 转换
#include <iostream>
using namespace std;
class FuClass {
public:
//父类 动态转换必须让父类的函数成为 虚函数
virtual void show() {
cout << "FuClass show" << endl;
}
};
class ZiClass : public FuClass {
public:
//重写了父类的show()函数
void show(){
cout << "ZiClass show" << endl;
}
};
int main17T9(){
// 动态类型转换的时候,在运行期 由于fuClass 是new 父类的,已成定局,就不能转换子类
// FuClass * fuClass = new FuClass(); // 失败
FuClass* fuClass = new ZiClass(); // 已成定局 是子类
fuClass->show(); //ZiClass show
ZiClass* ziClass = dynamic_cast<ZiClass *>(fuClass);
// 动态转换是有返回值, null 转换失败
if (ziClass) { // ziClass != null
cout << "恭喜,转换成功 " ;
ziClass->show(); //恭喜,转换成功 Z iClass show ok
} else {
cout << "不恭喜 转换失败" << endl ;
}
return 0;
}
(3.)重新解释转换 reinpreter_cast<type-id> (expression)
reinterpret_cast 运算符把某种指针改为其他类型的指针。它可以把一个指针转换为一个整数,也可以把一个整数转换为一个指针。
重新解释转换将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换。
重新解释转换不进行任何类型检查,因此可能会导致未定义的行为。
type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。
它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针
(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。reinpreter_cast<type-id> (expression)
int main(){
int i = 10;
float f = reinterpret_cast<float&>(i); // 重新解释将int类型转换为float类型
std::cout << "f=" <<f<< std::endl; // f=1.4013e-044
return 0;
}
17T10
/*
四种类型转换之reinterpret_cast强制转换
reinterpret_cast 强制转换 比 static_cast要强大,
static_cast能够做的事情,reinterpret_cast强制转换都可以,同时并且附加 新功能
*/
#pragma once
#include <iostream>
#include <iostream>
#include <iostream>
#include <iostream>
#include <iostream>
#include <iostream>
using namespace std;
class DerryPlayer {
public:
void show() {
cout << "DerryPlayer show" << endl;
}
};
int main17T10(){
DerryPlayer * derryPlayer = new DerryPlayer();
printf("指针derryPlayer=%p\n", derryPlayer); //指针derryPlayer=012F0C60
// 把对象derryPlayer 变成数值 playerValue 牛逼
long playerValue = reinterpret_cast<long>(derryPlayer);
cout << "playerValue="<< playerValue << endl ; //playerValue=21236832
// 通过数值playerValue 变成对象 derryPlayer2
DerryPlayer * derryPlayer2 = reinterpret_cast<DerryPlayer *>(playerValue);
printf("指针derryPlayer2=%p\n", derryPlayer2); //指针derryPlayer2=012F0C60
derryPlayer2->show(); // DerryPlayer show
// 前面的只是:为什么不一样:因为指针存放地址,同时指针有自己的地址,而你打印了自己的的地址,能一样? 不一样
printf("derryPlayer:%p\n", &derryPlayer); //derryPlayer:00A3FF08
printf("derryPlayer2:%p\n", &derryPlayer2); //derryPlayer2:00A3FF04
}
(4.)静态转换 static_cast < type-id > ( expression )
静态转换是将一种数据类型的值强制转换为另一种数据类型的值。
静态转换不进行任何运行时类型检查,因此可能会导致运行时错误。
static_cast 运算符执行非动态转换,没有运行时类检查来保证转换的安全性。例如,它可以用来把一个基类指针转换为派生类指针。
该运算符把 expression 转换为 type-id 类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
① 用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
② 用于基本数据类型之间的转换,如把 int 转换成 char,把 int 转换成 enum。这种转换的安全性也要开发人员来保证。
③ 把空指针转换成目标类型的空指针。
④ 把任何类型的表达式转换成void类?注意 static_cast 不能转换掉 expression 的 const、volitale、或者 __unaligned 属性。
double aa = 21.09399;
float bb = 10.20;
int cc=0 ;
cout << " cc = " << cc << endl ; // cc = 0
cc = (int) aa;
cout << " cc = (int) aa=" << cc << endl ; // cc = (int) aa=21
cc = (int) bb;
cout << " cc = (int) bb=" << cc << endl ; // cc = (int) bb=10
静态转换static_cast 父类转子类 17T8
// 3.四种类型转换。 static_cast 指针相关的操作 可以用 static_cast
#include <iostream>
using namespace std;
class FuClass {
public:
void show() {
cout << "FuClass show" << endl;
}
};
class ZiClass : public FuClass {
public:
//重写了父类的show()函数
void show(){
cout << "ZiClass show" << endl;
}
};
int main17T8(){
int n=88;
void* pVoid =&n;
int* numberP = static_cast<int *>(pVoid);
int number = *numberP;
cout <<"number=" << number << endl;
FuClass * fuClass = new FuClass();
fuClass->show(); //FuClass show
ZiClass* ziClass = static_cast<ZiClass *>(fuClass);
ziClass->show(); //ZiClass show
delete fuClass; // 回收规则:一定是谁new了,我就delete谁
return 0;
}
6.5 取指针地址运算符 &
取指针地址运算符 & : 指向一个普通变量的指针地址。
指针是一个指向了另一个变量地址的变量。变量可以是任意的数据类型,包括对象、结构或者指针。
调用时, 变量var前加 "&",使用取地址运算符&获取该普通变量var 的指针地址p
int var; // 声明一个int类型的变量var ,未初始化
int *ptr;//声明一个内存指针变量ptr,未初始化
var = 3000; //初始化 变量var为3000
// 先使用取地址运算符 & 获取变量var的指针地址&var,再把该指针地址&var赋值给指针变量ptr
ptr =&var; //指针ptr=0x62ff04
注意区别:
声明时,变量前加 "基本类型 &" :声明引用变量。它是某个已存在变量的别名,即该引用变量名称与原始变量名称都代表同一个变量。
int var; // 声明一个int类型的变量var ,未初始化
int & ref1 = var; // 声明引用变量ref1, ref1是变量var的别名(引用必须在创建时被初始化)
6.6 间接寻址运算符* (* 取内存指针地址的数值内容 )
返回操作数所指定地址的变量的数值。
声明时 变量前加 "基本类型 *" : 声明指针变量。
该指针变量指向另一个普通变量的指针地址 。该指针变量的值是另一个普通变量的指针地址。
int var; // 声明一个int类型的变量var ,未初始化
int *ptr;//声明一个内存指针变量ptr,未初始化
var = 3000; //初始化 变量var为3000
// 先使用取地址运算符 & 获取变量var的指针地址&var,再把该指针地址&var赋值给指针变量ptr
ptr =&var; //指针ptr=0x62ff04
调用时 指针变量前加 "*" 使用间接寻址运算符* 获取该指针变量 所指向的普通变量内容
int var; // 声明一个int类型的变量var ,未初始化
int *ptr;//声明一个内存指针变量ptr,未初始化
int val;// 声明一个int类型的变量val ,未初始化
var = 3000; //初始化 变量var为3000
//先使用取地址运算符 & 获取变量var的指针地址&var,再把该指针地址&var赋值给指针变量ptr
ptr =&var; //指针ptr=0x62ff04
// 先使用 间接寻址运算符* 获取指针变量ptr 所指向的变量内容*ptr(即普通变量var=3000)
// 在把这个 变量内容*ptr(即普通变量var=3000) 赋值给变量 val
val=*ptr;//变量val=3000
6.7 二级指针变量 “ 基本类型 ** ”
声明时,变量前加 "基本类型 **":声明一个二级指针变量pptr。
该二级指针变量pptr 是 另一个一级"基本类型 *"指针变量ptr的指针地址 (指针的指针)。
int var; // 声明一个int类型的变量var ,未初始化
int *ptr;//声明一个内存指针变量ptr,未初始化
var = 3000; //初始化 变量var为3000
// 先使用取地址运算符 & 获取变量var的指针地址&var,再把该指针地址&var赋值给指针变量ptr
ptr =&var; //指针ptr=0x62ff04
int ** pptr; // 声明一个二级指针变量pptr
pptr = &ptr; // 先使用 & 运算符获取 一级指针变量ptr 的指针地址&ptr,再把该指针地址&ptr赋值给二级指针变量pptr
调用时,二级指针变量pptr前加 "**" :获取该二级指针变量pptr所指向的指针所指向的变量内容值**pptr
int i= **pptr; // 获取该二级指针变量pptr所指向的指针所指向的变量内容值**pptr 并赋值给变量i
7.运算符优先级
运算符的优先级确定表达式中项的组合。这会影响到一个表达式如何计算。某些运算符比其他运算符有更高的优先级,例如,乘除运算符具有比加减运算符更高的优先级。
C++ 循环
循环类型
1.while 循环
只要给定的条件为真,while 循环语句会重复执行一个目标语句。
condition 可以是任意的表达式,当为任意非零值时都为真。当条件为真时执行循环。
当条件被测试且结果为假时,会跳过循环主体,直接执行紧接着 while 循环的下一条语句。
while(condition)
{
.... // 执行循环体里的业务逻辑代码
}........ // while循环体的下一条语句
2.for 循环
for (表达式1;表达式2;表达式3) {
// 循环体语句
}
3.do...while 循环
条件表达式出现在循环的尾部,所以循环中的 statement(s) 会在条件被测试之前至少执行一次。
如果条件为真,控制流会跳转回上面的 do,然后重新执行循环中的 statement(s)。这个过程会不断重复,直到给定条件变为假为止。
do
{
statement(s);}while( condition );
循环控制语句
1.break 语句
C++ 中 break 语句有以下两种用法:
(1) break 语句,会立即终止整个循环,程序将会继续执行紧接着循环外的下一条语句。
如果您使用的是嵌套循环(即一个循环内嵌套另一个循环),break 语句会停止执行最内层的循环,然后开始执行该块之后的下一行代码。
(2)它可用于终止 switch 语句中的一个 case。
#include <iostream>
using namespace std;
int main ()
{
// 局部变量声明
int a = 10;
// do 循环执行
do
{
cout << "a 的值:" << a << endl;
a = a + 1;
if( a > 15)
{
// 当a>15时,终止这个循环
break;
}
}while( a < 20 );
return 0;
}
//a>15的数值不会输出打印
a 的值: 10
a 的值: 11
a 的值: 12
a 的值: 13
a 的值: 14
a 的值: 15
2.continue 语句
continue 会跳出当前执行的这次循环中的代码,跳到下一次循环继续执行。
break 与 continue 的区别
break 语句,会立即终止整个循环;
continue 会跳出当前执行的这次循环中的代码,跳到下一次循环继续执行
#include <iostream>
using namespace std;
int main ()
{
// 局部变量声明
int a = 10;
// do 循环执行
do
{
if( a == 15)
{
//当a=15时, 跳过当前这次循环
a = a + 1;
continue;
}
cout << "a 的值:" << a << endl;
a = a + 1;
}while( a < 20 );
return 0;
}
// //当a=15时, 跳过当前这次循环 不会打印输出a=15值
a 的值: 10
a 的值: 11
a 的值: 12
a 的值: 13
a 的值: 14
a 的值: 16
a 的值: 17
a 的值: 18
a 的值: 19
3.goto 语句
无限循环
如果条件永远不为假,则循环将变成无限循环。for 循环在传统意义上可用于实现无限循环。
由于构成循环的三个表达式中任何一个都不是必需的,您可以将某些条件表达式留空来构成一个无限循环 。
for (; ;) {…}
一般情况下,C++ 程序员偏向于使用 for(;;) 结构来表示一个无限循环。
#include <iostream>
using namespace std;
int main ()
{
for( ; ; )
{
printf("This loop will run forever.\n");
}
return 0;
}
C++ 判断语句
1.if 语句
任何非零和非空的值假定为 true
零或 null 为 false
如果boolean_expression= true,则 if 语句内的代码块将被执行。
否则boolean_expression= false if 语句内代码块不会执行
if(boolean_expression)
{
// 如果布尔表达式为真将执行的语句
}
#include <iostream>
using namespace std;
int main ()
{
// 局部变量声明
int a = 10;
// if 语句检查布尔条件 a < 20 为true 执行if里面的语句
if( a < 20 )
{
// 判断条件为真, 输出下面的语句
cout << "a 小于 20" << endl;
}
cout << "a 的值是 " << a << endl;
return 0;
}
a 小于 20
a 的值是 10
2.if...else 语句
如果布尔表达式为 true,则执行 if 块内的代码。如果布尔表达式为 false,则执行 else 块内的代码。
if(boolean_expression)
{
// 如果布尔表达式为真boolean_expression=true,将执行的语句
}
else
{
// 如果布尔表达式为假boolean_expression=false 将执行的语句
}
#include <iostream>
using namespace std;
int main ()
{
// 局部变量声明
int a = 100;
// 检查布尔条件 a < 20为false 执行else 语句
if( a < 20 )
{
// 如果条件为真,则输出下面的语句
cout << "a 小于 20" << endl;
}
else
{
// 如果条件为假,则输出下面的语句
cout << "a 大于 20" << endl;
}
cout << "a 的值是 " << a << endl;
return 0;
}
a 大于 20
a 的值是 100
if...else if...else if...else 语句
一个 if 后可跟零个或一个 else,else 必须在所有 else if 之后。
一个 if 后可跟零个或多个 else if,else if 必须在 else 之前。
一旦if 或者某个 else if 匹配成功,其他的 else if 或 else 将不会执行。
if(boolean_expression 1)
{
// 当布尔表达式 1 为真时执行
}
else if( boolean_expression 2)
{
// 当布尔表达式 2 为真时执行
}
else if( boolean_expression 3)
{
// 当布尔表达式 3 为真时执行
}
else
{
// 当上面条件都不为真时执行else 语句
}
#include <iostream>
using namespace std;
int main ()
{
// 局部变量声明
int a = 100;
// 检查布尔条件
if( a == 10 )
{
// 如果 if 条件为真,则输出下面的语句
cout << "a 的值是 10" << endl;
}
else if( a == 20 )
{
// 如果 else if 条件为真,则输出下面的语句
cout << "a 的值是 20" << endl;
}
else if( a == 30 )
{
// 如果 else if 条件为真,则输出下面的语句
cout << "a 的值是 30" << endl;
}
else
{
// 如果上面条件都不为真,则输出下面的语句
cout << "没有匹配的值" << endl;
}
cout << "a 的准确值是 " << a << endl;
return 0;
}
// 最终 If 所有的else if判断都是false 都不执行 最终执行else语句
没有匹配的值
a 的准确值是 100
3.switch 语句
一个 switch 语句可以有一个可选的 default case,出现在 switch 的结尾。default case 可用于在上面所有 case 都不为真时执行一个任务。default case 中的 break 语句不是必需的。
switch(expression){
case constant-expression :
statement(s);
break; // 可选的
case constant-expression :
statement(s);
break; // 可选的
// 您可以有任意数量的 case 语句
default : // 可选的
statement(s);
}
#include <iostream>
using namespace std;
int main ()
{
// 局部变量声明
char grade = 'D';
switch(grade)
{
case 'A' :
cout << "很棒!" << endl;
break;
case 'B' :
case 'C' :
cout << "做得好" << endl;
break;
case 'D' :
cout << "您通过了" << endl;
break;
case 'F' :
cout << "最好再试一下" << endl;
break;
default :
cout << "无效的成绩" << endl;
}
cout << "您的成绩是 " << grade << endl;
return 0;
}
C++ 函数
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。
定义函数
return_type 是函数返回的值的数据类型
function_name 函数的实际名称
parameter list 参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
{...}函数主体包含一组定义函数执行任务的语句。
return_type function_name( parameter list )
{
body of the function
}
// 函数返回两个数中较大的那个数
int max(int num1, int num2)
{
// 局部变量声明
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
函数声明
函数声明会告诉编译器函数名称及如何调用函数。函数的实际主体可以另外单独定义。
return_type function_name( parameter list );
函数声明
int max(int num1, int num2);
在函数声明中,参数的名称并不重要,可以省略,只有参数的类型是必需的
int max(int, int);
调用函数
当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。
调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值。
#include <iostream>
using namespace std;
// max函数声明 未实现
int max(int num1, int num2);
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
int ret;
// 调用max函数返回最大值,赋值给变量ret
ret = max(a, b);
cout << "Max value is : " << ret << endl;
return 0;
}
//max函数的实现 函数返回两个数中较大的那个数
int max(int num1, int num2)
{
// 局部变量声明
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
函数参数
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数就像函数内的声明的其他局部变量一样,在进入函数时被创建,退出函数时被销毁。
1.传值调用
把参数的实际值赋值给函数的形式参数。
在这种情况下,函数内的形式参数值的修改改变, 不会改变调用函数的传递的实际参数数值
#include <iostream>
using namespace std;
// 函数swap声明
void swap(int x, int y);
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
// 调用函数来交换值
swap(a, b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
// 函数swap定义 形参x y的数值的改变 不会改变传递的实际参数a b的数值
void swap(int x, int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 100
交换后,b 的值: 200
2.指针调用
把 实际参数变量的指针地址 传递并赋值给 形式参数
在函数内 形式参数 就是 实际参数变量 的 指针地址变量
所以函数内修改改变 形式参数 就会改变 实际参数变量值
#include <iostream>
using namespace std;
// 函数swap声明
void swap(int *x, int *y);
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值
* &a 表示指向 a 的指针,即变量 a 的地址
* &b 表示指向 b 的指针,即变量 b 的地址
*/
swap(&a, &b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
// 函数swap定义
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 x 赋值给 y */
return;
}
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100
3.引用调用
把 实际参数的引用变量 赋值给 形式参数。
在函数中 形式参数 ==引用变量, 引用变量 会访问(指向) 调用函数时传递的实际参数
所以修改形式参数 就会改变 实际参数数值。
#include <iostream>
using namespace std;
// 函数swap声明
void swap(int &x, int &y);
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值 */
swap(a, b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
// 函数swap定义 形式参数x(引用变量) 指向 实际参数a 形式参数y(引用变量) 指向 实际参数a
void swap(int &x, int &y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100
函数的参数的默认值
当您定义一个函数,您可以为形式参数列表中后边的每一个形式参数指定默认值。当调用函数时,如果实际参数的值留空,则使用这个默认值。
调用函数时,如果未传递实际参数的值,形式参数则会使用默认值,如果指定了值,则会忽略默认值,使用传递的值。
#include <iostream>
using namespace std;
//如果第二个实际参数没传递值 则形式参数b就使用默认值20
int sum(int a, int b=20)
{
int result;
result = a + b;
return (result);
}
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
int result;
// 调用sum函数 传递实际参数 a b
result = sum(a, b);
cout << "Total value is :" << result << endl;
// 再次调用sum函数 只传递实际参数 a
result = sum(a);
cout << "Total value is :" << result << endl;
return 0;
}
Total value is :300
Total value is :120
Lambda 函数与表达式(比较难理解)
C++11 提供了对匿名函数的支持,称为 Lambda 函数(也叫 Lambda 表达式)。
Lambda 表达式把函数看作对象。Lambda 表达式可以像对象一样使用,
比如可以把Lambda 表达式赋给变量;
也可以把Lambda 表达式作为参数进行传递;
还可以像函数一样对其求值。
Lambda 表达式具体形式如下:
[capture](parameters)->return-type{body}
[](int x, int y){ return x < y ; }
如果没有返回值可以表示为:
[capture](parameters){body}
[]{ ++global_x; }
[](int x, int y) -> int { int z = x + y; return z + x; }
[] // 沒有定义任何变量。使用未定义变量会引发错误。
[x, &y] // x以传值方式传入(默认),y以引用方式传入。
[&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。
对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入:
[this]() { this->someFunc(); }();
函数的可变参数 10T1 10T2
案例1:
#include <iostream>
#include <stdarg.h> // 可变参数的支持
using namespace std;
// count的第一个用处:内部需要一个 存储地址用的参考值,如果没有第二个参数,内部他无法处理存放参数信息
void sum(int count,...) {
// 定义一个可变参数的动作行为
va_list vp;
// 参数一:可变参数开始的动作vp
// 参数二:内部需要一个 存储地址用的参考值,如果没有第二个参数,内部他无法处理存放参数信息
va_start(vp, count);
// 取出可变参数vp的第一个值
int number = va_arg(vp, int);
cout << "取出可变参数vp的第一个值vp="<<number << endl; //取出可变参数vp的第一个值vp=6
// 取出可变参数vp的第二个值
number = va_arg(vp, int);
cout << "取出可变参数vp的第二个值vp="<<number << endl; //取出可变参数vp的第二个值vp=7
// 取出可变参数vp的第三个值
number = va_arg(vp, int);
cout << "取出可变参数vp的第三个值vp="<<number << endl; // 取出可变参数vp的第三个值vp=8
// 取出可变参数vp的第四个值 【取不到后,越界,会取系统值 乱码】
number = va_arg(vp, int);
cout << "取出可变参数vp的第四个值vp="<<number << endl; //取出可变参数vp的第四个值vp=7536396
// 关闭阶段
va_end(vp);
}
// 1.可变参数
int main10T1() {
sum(546, 6,7,8);
return 0;
}
案例2:
#include <iostream>
#include <stdarg.h> // 可变参数的支持
using namespace std;
// count变量的第二个用处,用于循环遍历长度
void sum2(int count,...) {
// 定义一个可变参数的动作行为
va_list vp;
// 参数一:可变参数开始的动作vp
// 参数二:内部需要一个 存储地址用的参考值,如果没有第二个参数,内部他无法处理存放参数信息
va_start(vp, count);
for (int i=0;i<count;i++){
int number = va_arg(vp, int);
cout << "取出可变参数vp的第"<<i<<" 个值vp="<< number << endl;
}
// 关闭阶段(规范:例如:file文件一样 要关闭)
va_end(vp);
}
// 1.可变参数
int main10T2() {
sum2(3, 6,7,8); // 真实开发过程的写法
return 0;
}
/*
取出可变参数vp的第0 个值vp=6
取出可变参数vp的第1 个值vp=7
取出可变参数vp的第2 个值vp=8
*/
C++ 数字
C++ 定义数字
// 数字定义
short s;
int i;
long l;
float f;
double d;
// 数字赋值
s = 10;
i = 1000;
l = 1000000;
f = 230.47;
d = 30949.374;
C++ 数学运算
int abs(int);
该函数返回整数的绝对值。double fabs(double);
该函数返回任意一个浮点数的绝对值。
C++ 随机数
在许多情况下,需要生成随机数。关于随机数生成器,有两个相关的函数。一个是 rand(),该函数只返回一个伪随机数。生成随机数之前必须先调用 srand() 函数。
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
int main ()
{
int i,j;
// 设置种子
// 使用了 time() 函数来获取系统时间的秒数
srand( (unsigned)time( NULL ) );
/* 生成 10 个随机数 */
for( i = 0; i < 10; i++ )
{
// 生成实际的随机数 //调用 rand() 函数来生成随机数
j= rand();
cout <<"随机数: " << j << endl;
}
return 0;
}
C++ 数组
C++ 支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 numbers[0]、numbers[1]、...、numbers[99] 来代表一个个单独的变量。数组中的特定元素可以通过索引访问。
所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
声明数组
arraySize 必须是一个大于零的整数常量,type 可以是任意有效的 C++ 数据类型。
type arrayName [ arraySize ];
balance 是一个可用的数组,可以容纳 10 个类型为 double 的数字
double balance[10];
初始化数组
您可以逐个初始化数组,也可以使用一个初始化语句.
大括号 { } 之间的值的数目不能大于我们在数组声明时在方括号 [ ] 中指定的元素数目
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
省略掉了数组[]的大小,数组的大小则为初始化时元素的个数。
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
所有的数组都是以 0 作为它们第一个元素的角标索引,也被称为基索引,数组的最后一个角标索引是数组的总大小减去 1
访问数组元素
数组元素可以通过数组名称加角标索引进行访问。元素的角标索引是放在方括号内,跟在数组名称的后边。
double salary = balance[9];
#include <iostream>
using namespace std;
#include <iomanip>
using std::setw;
int main ()
{
// 声明一个数组n
int n[ 10 ]; // n 是一个包含 10 个整数的数组
// 初始化数组元素赋值
for ( int i = 0; i < 10; i++ )
{
n[ i ] = i + 100; // 设置元素 i 为 i + 100
}
cout << "Element" << setw( 13 ) << "Value" << endl;
// 输出数组中每个元素的值
for ( int j = 0; j < 10; j++ )
{
cout << setw( 7 )<< j << setw( 13 ) << n[ j ] << endl;
}
return 0;
}
C++ 中数组详解
1.多维数组(二维数组)
(1)二维数组的定义
多维数组最简单的形式是二维数组。一个二维数组,在本质上,是一个一维数组的列表。声明一个 x 行 y 列的二维整型数组
type 可以是任意有效的 C++ 数据类型
arrayName 是一个有效的 C++ 标识符
type arrayName [ x ][ y ];
一个二维数组可以被认为是一个带有 x 行和 y 列的表格。下面是一个二维数组,包含 3 行和 4 列:
因此,数组中的每个元素是使用形式为 a[ i , j ] 的元素名称来标识的,其中 a 是数组名称,i 和 j 是唯一标识 a 中每个元素的下标。
(2)初始化二维数组
多维数组可以通过在括号内为每行指定值来进行初始化。
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
(3)访问二维数组元素
二维数组中的元素是通过使用下标(即数组的行索引和列索引)来访问的。
int val = a[2][3];
#include <iostream>
using namespace std;
int main ()
{
// 一个带有 5 行 2 列的数组
int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};
// 输出数组中每个元素的值
for ( int i = 0; i < 5; i++ )
for ( int j = 0; j < 2; j++ )
{
cout << "a[" << i << "][" << j << "]: ";
cout << a[i][j]<< endl;
}
return 0;
}
a[0][0]: 0
a[0][1]: 0
a[1][0]: 1
a[1][1]: 2
a[2][0]: 2
a[2][1]: 4
a[3][0]: 3
a[3][1]: 6
a[4][0]: 4
a[4][1]: 8
2.指向数组的指针(跳过等学了C++指针)
您可以通过指定不带索引的数组名称来生成一个指向数组中第一个元素的指针
数组名是指向数组中第一个元素的常量指针.
runoobAarray 是一个指向 &runoobAarray[0] 的指针,即数组 runoobAarray 的第一个元素的地址。
double runoobAarray[50];
把数组runoobAarray 的第一个元素的指针地址runoobAarray赋值给指针 p:
一旦您把第一个元素的地址存储在 p 中,您就可以使用 *p、*(p+1)、*(p+2) *(p+3) 等来访问数组元素数值
double *p;
double runoobAarray[10];p = runoobAarray;
使用数组名作为常量指针是合法的,反之亦然。因此,*(runoobAarray + 4) 是一种访问 runoobAarray[4] 数据的合法方式。
#include <iostream>
using namespace std;
int main()
{
// 带有 5 个元素的双精度浮点型数组
double runoobAarray[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
double *p; // p 是一个指向 double 型的指针,它可以存储一个 double 类型的指针变量
// 把数组runoobAarray 的第一个元素的指针地址runoobAarray赋值给指针p,
p = runoobAarray;
// 输出数组中每个元素的值
//方法一: 使用指针p的数组值
for ( int i = 0; i < 5; i++ ){
// *p 将给出存储在 p 中相应地址的值 *p
cout << "*(p + " << i << ") ="<<*(p+i)<<endl;
}
/*
*(p + 0) =1000
*(p + 1) =2
*(p + 2) =3.4
*(p + 3) =17
*(p + 4) =50
*/
//使用 runoobAarray 作为地址的数组值
for ( int i = 0; i < 5; i++ )
{
cout << "*(runoobAarray + " << i << ")="<<*(runoobAarray+i)<<endl;
}
/*
*(runoobAarray + 0)=1000
*(runoobAarray + 1)=2
*(runoobAarray + 2)=3.4
*(runoobAarray + 3)=17
*(runoobAarray + 4)=50
*/
}
3.传递数组给函数
您可以通过传递一个不带索引的数组名arr给函数,传递的是一个指向数组的指针arr。
传递一个数组给函数,数组类型自动转换为指针类型,因而传的实际是地址。
如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。
方式 1:
形式参数是一个指针:
void myFunction(int *param)
{}
方式 2:
形式参数是一个已定义大小的数组:
void myFunction(int param[10])
{}
方式 3:
形式参数是一个未定义大小的数组:
void myFunction(int param[])
{}
#include <iostream>
using namespace std;
// 它把数组作为参数
void getAveraged(int arr[], int size) {
for (int i = 0; i < size; ++i)
{
cout << arr[i] << endl;
}
}
int main ()
{
// 带有 5 个元素的整型数组
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
// 传递一个指向数组的指针balance作为参数
getAveraged( balance, 5 ) ;
}
/*
* 1000
2
3
17
50
*/
4.函数返回一个数组(重要特殊)
C++ 不允许返回一个完整的数组作为函数的参数。但是,您可以通过指定不带索引的数组名来返回一个指向数组的指针。
如果您想要从函数返回一个一维数组,您必须声明一个返回指针的函数:
int* myFunction()
{
int myArray[3] = {1, 2, 3};
return myArray;
}
注意:你不能简单地返回指向局部数组的指针,因为当函数结束时,局部数组将被销毁,指向它的指针将变得无效。
C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
为了避免以上情况,你可以使用静态数组或者动态分配数组。
使用静态数组需要在函数内部创建一个静态数组,并将其地址返回。
int* myFunction()
{
static int myArray[3] = {1, 2, 3};
return myArray;
}
getRandom()函数返回一个指针p ,指针p表示返回数组
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
// getRandom()函数返回一个指针p ,指针p表示返回数组
int * getRandom() {
//声明一个静态数组r
static int arr[10];
// 设置种子
srand( (unsigned)time( NULL ) );
for (int i = 0; i < 10; ++i)
{
arr[i]=rand();
}
return arr;
}
int main ()
{
// 定义一个指向整数的指针
int *p;
//getRandom()函数返回一个指针p ,指针p表示返回数组
p = getRandom();
for ( int i = 0; i < 10; i++ )
{
cout << "*(p + " << i << ")="<<*(p+i)<<endl;
}
}
/*
*(p + 0)=28812
*(p + 1)=13572
*(p + 2)=1650
*(p + 3)=30231
*(p + 4)=31242
*(p + 5)=14720
*(p + 6)=3424
*(p + 7)=15978
*(p + 8)=15103
*(p + 9)=5399
*/
使用动态分配数组需要在函数内部使用 new 运算符来分配一个数组,并在函数结束时使用 delete 运算符释放该数组。
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int *myFunction() {
//使用动态分配数组需要在函数内部使用 new 运算符来分配一个数组,
int* myArray = new int[3];
myArray[0] = 1;
myArray[1] = 2;
myArray[2] = 3;
return myArray;
}
int main()
{
// 定义一个指向整数的指针result
int *result ;
result=myFunction();
delete[] result; //释放该数组
return 0;
}
我们声明了一个名为 createArray 的函数,并返回一个由整数填充的整数数组arr,
我们使用 new 运算符在堆上动态分配了一个数组arr,
最后,函数createArray返回一个指向数组的指针arr。
在 main 函数中,我们调用了 createArray 函数,并将返回的数组指针arr赋值给了指针变量 myArray ,然后我们遍历了数组并打印了每个元素的值。
最后,我们使用 delete[] 运算符释放了 myArray 所占用的内存,以避免内存泄漏。
#include <iostream>
using namespace std;
int *createArray(int size) {
//使用new 在内存里动态分配一个数组 大小 size
int* arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i]=i+1000;
}
return arr;
}
int main() {
int* myArray = createArray(5);
for (int i = 0; i < 5; i++) {
cout << "myArray[" << i << "]="<< myArray[i]<<endl;
}
delete[] myArray; // 释放内存
return 0;
}
/*
myArray[0]=1000
myArray[1]=1001
myArray[2]=1002
myArray[3]=1003
myArray[4]=1004
*/
C++ 字符串
C++ 提供了以下两种类型的字符串表示形式:
- C 风格字符串
- C++ 引入的 string 类类型
1.C 风格字符串
C 风格的字符串起源于 C 语言,并在 C++ 中继续得到支持。字符串实际上是使用 null 字符 \0 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
声明和初始化创建了一个 RUNOOB 字符串
由于在数组的末尾存储了空字符'\0',所以字符数组的大小比单词 RUNOOB 的字符数多一个。
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
或者写成
char site[] = "RUNOOB";
其实,您不需要把 null 字符放在字符串常量的末尾。C++ 编译器会在初始化数组时,自动把 \0 放在字符串的末尾。
int main ()
{
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
cout << "菜鸟教程=";
cout << site << endl; // 菜鸟教程=RUNOOB
return 0;
}
C++ 中有大量的函数用来操作以 null 结尾的字符串:
序号 | 函数 & 目的 |
---|---|
1 | strcpy(s1, s2); 复制字符串 s2 到字符串 s1。 |
2 | strcat(s1, s2); 把字符串 s2 连接/拼接到字符串 s1 的末尾。连接字符串也可以用 + 号,例如: string str1 = "runoob"; string str2 = "google"; string str = str1 + str2; |
3 | strlen(s1); |
4 | strcmp(s1, s2); 如果 s1<s2 则返回值小于 0; 如果 s1>s2 则返回值大于 0。 |
5 | strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
6 | strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
#include <iostream>
#include <cstring>
using namespace std;
int main ()
{
char str1[13] = "runoob";
char str2[13] = "google";
char str3[13];
int len ;
// 复制 str1 到 str3
strcpy( str3, str1);
cout << "str3[13]=" << str3 << endl; //str3[13]=runoob
cout << "str1=" << str1 << ", str1.len=" << strlen(str1)<<endl; //str1=runoob, str1.len=6
// 把字符串 s2 连接/拼接到字符串 s1 的末尾。
strcat( str1, str2);
// 拼接后,str1 的总长度
len = strlen(str1);
cout << "拼接后的str1=" << str1 << ", 拼接后的str1的长度str1.len=" << len <<endl; // 拼接后的str1=runoobgoogle, 拼接后的str1的长度str1.len=12
return 0;
}
2.C++ 中的 String 类
C++ 标准库提供了 string 类类型,支持上述所有的操作,另外还增加了其他更多的功能。
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str1 = "runoob";
string str2 = "google";
string str3;
int len ;
// 复制 str1 到 str3
str3 = str1;
cout << "str3 : " << str3 << endl;
// 连接 str1 和 str2
str3 = str1 + str2;
cout << "str1 + str2 : " << str3 << endl;
// 连接后,str3 的总长度
len = str3.size();
cout << "str3.size() : " << len << endl;
return 0;
}
C++ 指针
学习 C++ 的指针既简单又有趣。通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C++ 程序员,学习指针是很有必要的。
正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。
int var1;
char var2[10];
cout << "var1 变量的地址= "<< &var1 << endl; //var1 变量的地址= 0x62ff00
cout << "var2 变量的地址="<< &var2 << endl; //var2 变量的地址=0x62fef6
什么是指针?
指针是一个变量,其值为另一个普通变量的在内存位置中地址。
就像其他变量或常量一样,您必须在使用指针存储其他变量在内存中的地址之前,对其进行声明。
所有指针的值的实际数据类型, 都是一样的,都是一个代表内存地址的长的十六进制数。
type 是指针的基类型,它必须是一个有效的 C++ 数据类型,
var-name 是指针变量的名称。
*星号是用来指定一个变量是指针。
type *var-name;
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
C++ 中使用指针
使用指针时会频繁进行以下几个操作:
定义一个指针变量、
把变量地址赋值给指针变量、
访问指针变量中指向的内存地址的值。
#include <iostream>
using namespace std;
int main(){
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 把普通变量var的在内存中的地址&var 赋值给指针变量ip
int i=*ip; // 访问指针变量ip的内存地址的对应的数值*ip 赋值给变量i
cout << "var= "<< var << endl; // var= 20
cout << "ip="<< ip << endl; // ip=0x63ff04
cout << "i="<< *ip << endl; // i=20
}
C++ 指针详解
1.C++ Null 指针
C++ 支持空指针NULL。
NULL 指针是一个定义在标准库中的值为零的常量。
在指针变量声明的时候,如果没有确切的指针地址可以赋值,为指针变量赋一个 NULL空 指针是一个良好的编程习惯。
在大多数的操作系统上,程序不允许访问指针地址为 0(NULL) 的内存,因为该内存是操作系统保留的。
然而,内存地址0(NULL) 有特别重要的意义,它表明该指针变量不指向一个可访问的内存位置。
int * ptr = NULL;
cout << "指针变量ptr="<< ptr<< endl ; // 指针变量ptr=0
if(ptr){
cout << "指针变量ptr非空"<< endl ;
}
if(!ptr){
cout << "指针变量ptr为空"<< endl ; // 输出指针变量ptr为空
}
2.C++指针的算数运算
指针变量是一个用十六进制数值表示的内存地址。因此,您可以对指针变量进行 ++、--、+、-算术运算。
假设 pInt 是一个整型指针,指向内存地址是1000 ,
由于是一个 32 位整数指针,每个整数占据 4 个字节,执行pInt ++ 后会将指针 pInt 向前移动 4 个字节,指向下一个整型元素的指针地址1004。
int ii=1000;
int* pInt = ⅈ
cout << "输出指针变量pInt="<< pInt<< endl ; //输出指针变量pInt=0x63fef8
cout << "输出指针变量pInt的内存地址对应的数值变量="<< *pInt<< endl ; //输出指针变量pInt的内存地址对应的数值变量=1000
pInt++;
cout << "再次输出指针变量pInt="<< pInt<< endl ; //再次输出指针变量pInt=0x63fefc
cout << "再次输出指针变量pInt的内存地址对应的数值变量="<< *pInt<< endl ;// 再次输出指针变量pInt的内存地址对应的数值变量=20
如果 ptr 指向一个地址为 1000 的字符,执行 ptr++ 指针 ptr 的值会增加,指向下一个字符元素的地址,由于 ptr 是一个字符指针,每个字符占据 1 个字节,因此 ptr++ 会将 ptr 的值增加 1,执行后 ptr 指向地址 1001。
加法运算:可以对指针进行加法运算。当一个指针p加上一个整数n时,结果是指针p向前移动n个元素的大小。例如,如果p是一个int类型的指针,每个int占4个字节,那么p + 1将指向p所指向的下一个int元素。
减法运算:可以对指针进行减法运算。当一个指针p减去一个整数n时,结果是指针p向后移动n个元素的大小。例如,如果p是一个int类型的指针,每个int占4个字节,那么p - 1将指向p所指向的前一个int元素。
指针与指针之间的减法运算:可以计算两个指针之间的距离。当从一个指针p减去另一个指针q时,结果是两个指针之间的元素个数。例如,如果p和q是两个int类型的指针,每个int占4个字节,那么p - q将得到两个指针之间的元素个数。
指针与整数之间的比较运算:可以将指针与整数进行比较运算。可以使用关系运算符(如<、>、<=、>=)对指针和整数进行比较。这种比较通常用于判断指针是否指向某个有效的内存位置。
(1)递增一个指针
在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,因为数组是一个常量指针。下面的程序递增变量指针,以便顺序访问数组中的每一个元素:
#include <iostream>
using namespace std;
const int MAX = 3;
int main(){
int varArr[MAX] = {10, 100, 200};
for (int i = 0; i < MAX; i++){
cout << "varArr[" << i << "] = "<< varArr[i] << endl;
}
/*
varArr[0] = 10
varArr[1] = 100
varArr[2] = 200
*/
int *ptr;//声明一个指针变量ptr
// 初始化指针为数组首地址varArr
ptr = varArr;
for (int i = 0; i < MAX; i++){
cout << "数组每个元素的指针地址varArr[" << i << "] = "<<ptr<<endl;
cout << "数组每个元素的指针地址的数值varArr[" << i << "] = "<<*ptr<<endl;
ptr++;//挪动指针位置
}
/*
数组每个元素的指针地址varArr[0] = 0x63fef8
数组每个元素的指针地址的数值varArr[0] = 10
数组每个元素的指针地址varArr[1] = 0x63fefc
数组每个元素的指针地址的数值varArr[1] = 100
数组每个元素的指针地址varArr[2] = 0x63ff00
数组每个元素的指针地址的数值varArr[2] = 200
*/
return 0;
}
(2)递减一个指针
对指针进行递减运算,即把值减去其数据类型的字节数.
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr;
// 指针中最后一个元素的地址
ptr = &var[MAX-1];
for (int i = MAX; i > 0; i--)
{
cout << "Address of var[" << i << "] = ";
cout << ptr << endl;
cout << "Value of var[" << i << "] = ";
cout << *ptr << endl;
// 移动到下一个位置
ptr--;
}
return 0;
}
Address of var[3] = 0xbfdb70f8
Value of var[3] = 200
Address of var[2] = 0xbfdb70f4
Value of var[2] = 100
Address of var[1] = 0xbfdb70f0
Value of var[1] = 10
3.C++ 指针 vs 数组
指针和数组是密切相关的。事实上,指针和数组在很多情况下是可以互换的。例如,一个指向数组开头的指针,可以通过使用指针的算术运算或数组索引来访问数组。
#include <iostream>
using namespace std;
const int MAX = 3;
int main(){
int varArr[MAX] = {10, 100, 200};
for (int i = 0; i < MAX; i++){
cout << "varArr[" << i << "] = "<< varArr[i] << endl;
}
/*
varArr[0] = 10
varArr[1] = 100
varArr[2] = 200
*/
int *ptr;//声明一个指针变量ptr
// 初始化指针为数组首地址varArr
ptr = varArr;
for (int i = 0; i < MAX; i++){
cout << "数组每个元素的指针地址varArr[" << i << "] = "<<ptr<<endl;
cout << "数组每个元素的指针地址的数值varArr[" << i << "] = "<<*ptr<<endl;
ptr++;//挪动指针位置
}
/*
数组每个元素的指针地址varArr[0] = 0x63fef8
数组每个元素的指针地址的数值varArr[0] = 10
数组每个元素的指针地址varArr[1] = 0x63fefc
数组每个元素的指针地址的数值varArr[1] = 100
数组每个元素的指针地址varArr[2] = 0x63ff00
数组每个元素的指针地址的数值varArr[2] = 200
*/
return 0;
}
4.指针数组
我们想要让数组存储指向 int 或 char 或其他数据类型的指针
一个指向整数的指针数组的声明:
把 ptr 声明为一个指针数组,由 MAX 个整数指针组成,
指针数组ptr 中的每个元素,都是一个指向 int 值的指针元素。
int *ptr[MAX];
下面的实例用到了三个整数,将这三个整数的指针地址存储在一个指针数组中,如下所示:
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int varArr[MAX] = {10, 100, 200};
int * pVarArr[MAX];
for(int i=0;i<MAX;i++){
pVarArr[i]=&varArr[i];
}
for(int i=0;i<MAX;i++){
cout << "指针数组pVarArr第 "<< i << " 元素(指针)="<<pVarArr[i]<<" ,对应的内存的变量值="<<*pVarArr[i]<<endl;
}
/*
指针数组pVarArr第 0 元素(指针)=0x63feec ,对应的内存的变量值=10
指针数组pVarArr第 1 元素(指针)=0x63fef0 ,对应的内存的变量值=100
指针数组pVarArr第 2 元素(指针)=0x63fef4 ,对应的内存的变量值=200
*/
}
定义一个指向字符的指针数组names,存储的每一个元素都是字符串常量
const char *names[MAX2] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali"
};
for (int i = 0; i < MAX2; i++){
cout << "names[" << i << "] = "<< names[i] << endl;
}
/*
names[0] = Zara Ali
names[1] = Hina Ali
names[2] = Nuha Ali
names[3] = Sara Ali
*/
5.C++ 指向指针的指针(多级间接寻址)
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。
指针的指针 就是将 一个指针的内存指针地址 存放在 另一个指针里面。
一个指向指针的指针变量var必须如下声明,即在变量名前放置两个星号。
声明了一个指向 int 类型指针 的指针 var
int **var;
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符。
#include <iostream>
using namespace std;
int main ()
{
int var;
int *ptr;
int **pptr;
var = 3000;
// 获取变量 var 的指针地址 ptr
ptr = &var;
// 使用运算符 & 获取指针 ptr 的 指针地址&ptr 赋值给 pptr
pptr=&ptr;
cout << "var 值为 :" << var << endl; // var 值为 :3000
cout << "*ptr 值为:" << *ptr << endl; //*ptr 值为:3000
cout << "**pptr 值为:" << **pptr << endl; //**pptr 值为:3000
}
6.C++传递指针给函数
把 引用或地址 当做参数 传递给函数,使传递的参数在调用函数中被改变。
C++ 允许您传递指针给函数,只需要简单地声明函数的形式参数为指针类型即可。
接受指针作为函数的参数
#include <iostream>
#include <ctime>
using namespace std;
// 在写函数时应习惯性的先声明函数,然后在定义函数
void getSeconds(unsigned long *par);
int main ()
{
unsigned long sec;
getSeconds( &sec );
// 输出实际值
cout << "Number of seconds :" << sec << endl;
return 0;
}
void getSeconds(unsigned long *par)
{
// 获取当前的秒数
*par = time( NULL );
return;
}
把 指针数组 作为 函数的参数
#include <iostream>
using namespace std;
void getAverage(int *pArr, int size) ;
int main ()
{
// 带有 5 个元素的整型数组
int balance[5] = {1000, 2, 3, 17, 50};
int * pInt;
double avg;
// 传递一个指向数组的指针pInt作为参数
pInt=balance;
getAverage( pInt, 5 ) ;
}
void getAverage(int * pArr, int size) {
for(int i=0;i<size;i++){
cout << "指针数组pArr每个元素(指针地址) 的数值pArr[" << i << "] = "<<*(pArr+i)<<endl;
}
};
/*
指针数组pArr每个元素(指针地址) 的数值pArr[0] = 1000
指针数组pArr每个元素(指针地址) 的数值pArr[1] = 2
指针数组pArr每个元素(指针地址) 的数值pArr[2] = 3
指针数组pArr每个元素(指针地址) 的数值pArr[3] = 17
指针数组pArr每个元素(指针地址) 的数值pArr[4] = 50
*/
7.函数返回一个指针
C++ 允许函数返回一个指针变量到局部变量、静态变量和动态内存分配。
声明一个返回指针的函数
int * myFunction(){}
注意:C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static变量。
常量 指针 const int*
指针 常量 int* const
常量 指针常量 const int* const
8T5 10T6
常量 指针 【指针地址numberP1可以修改 地址对应的值*numberP1不能修改】
const int* numberP1=&number;
指针 常量 【指针地址numberP2不能修改 地址对应的值*numberP2可以修改 】
int* const numberP2 = &number;
常量 指针常量 【指针地址numberP3不能修改 地址对应的值*numberP3不能修改】
const int* const numberP3 = &number;
// 指针常量 常量指针 常量指针常量
#include <iostream>
#include <string.h>
#include <string.h>
using namespace std;
int mainT5() {
int number = 9;
int number2 = 8;
//todo 常量 指针 【指针地址numberP1可以修改 地址对应的值*numberP1不能修改】
const int* numberP1=&number;
cout << "常量指针 numberP1= " <<numberP1<< endl; // 常量指针 numberP1= 0x71ff04
// *numberP1 = 100; // 报错,不允许去修改【常量指针numberP1】存放地址所对应的数值*numberP1
numberP1=&number2; // OK,允许重新指向【常量指针numberP1】存放的地址
cout << "常量指针 numberP1= " <<numberP1<< endl;//常量指针 numberP1= 0x71ff04
//todo 指针 常量 【指针地址numberP2不能修改 地址对应的值*numberP2可以修改 】
int* const numberP2 = &number;
cout << "指针常量 numberP2= " <<numberP2<< endl;// 指针常量 numberP2= 0x71ff04
*numberP2=100; // OK,允许去修改【指针常量numberP2】存放地址所对应的数值*numberP2
//numberP2 = &number2; // 报错,不允许重新指向【指针常量numberP2】存放的地址
//todo 常量 指针常量 【指针地址numberP3不能修改 地址对应的值*numberP3不能修改】
const int* const numberP3 = &number;
cout << "常量 指针常量numberP3= " <<numberP3<< endl; //常量 指针常量numberP3= 0x71ff00
// *numberP3=100; // 报错,不允许去修改【常量指针常量numberP3】存放地址所对应的数值*numberP3
// numberP3 = &number2; // 报错,不允许重新指向【常量指针常量numberP3】存放的地址
return 0;
}
#include <iostream>
using namespace std;
// const修饰函数的this意义何在。
class Worker {
public:
char * name;
int age = NULL; // 要给个默认值,否则就是系统值 -64664
// 指针 常量 :指针地址对应的值能改,指针地址不可以修改
int* const num1=&age;
// 常量 指针 : 指针地址可以修改,指针地址对应的值不能改
const int * num2;
/*
* C++ 默认有一个 隐士的 【类型* const this】 指针常量this 指向当前对象
* */
// 默认有一个 隐士的 Worker* const this 指针常量this 指向当前对象
//todo this 是指针常量 【指针地址 不能修改 地址对应的值 可以修改 】
void change1() {
// this = 0x6546;// 编译不通过,地址不能被修改
// this = 0x43563; // 编译不通过,地址不能被修改
this->age = 100; //修改指针地址对应的值
this->name = "JJJ";//修改指针地址对应的值
}
//todo 如果 this 等价于 const Student * const 常量指针常量【地址不能改,地址对应的值不能改】
void changeAction() const {
// 地址不能改
// this = 0x43563;
// 地址对应的值不能改
// this->age = 100;
}
// const 类型 * const 常量指针常量
void showInfo() const {
// this->name = ""; // 地址对应的值不能改
// this->age = 88; // 地址对应的值不能改
// 只读的
cout << "age:" << age << endl;
}
};
int main10T6(){
Worker worker;
worker.name="哈哈";
cout << " worker.age=" << worker.age << " worker.name=" << worker.name << endl; // worker.age=0 worker.name=哈哈
cout <<" 指针 常量 int* const num1 =" << worker.num1 <<" 对应的数值*(worker.num1)="<<*(worker.num1)<<endl; //指针 常量 int* const num1 =0x74fef4 对应的数值*(worker.num1)=0
*(worker.num1)=88;
cout <<" 指针 常量 int* const num1 =" << worker.num1 <<" 对应的数值*(worker.num1)="<<*(worker.num1)<<endl;//指针 常量 int* const num1 =0x74fef4 对应的数值*(worker.num1)=88
return 0;
}
C++ 日期 & 时间
C++ 标准库没有提供所谓的日期类型。C++ 继承了 C 语言用于日期和时间操作的结构和函数。为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用 <ctime> 头文件。
有四个与时间相关的类型:clock_t、time_t、size_t 和 tm。类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数。
结构类型 tm 把日期和时间以 C 结构的形式保存,tm 结构的定义如下:
struct tm {
int tm_sec; // 秒,正常范围从 0 到 59,但允许至 61
int tm_min; // 分,范围从 0 到 59
int tm_hour; // 小时,范围从 0 到 23
int tm_mday; // 一月中的第几天,范围从 1 到 31
int tm_mon; // 月,范围从 0 到 11
int tm_year; // 自 1900 年起的年数
int tm_wday; // 一周中的第几天,范围从 0 到 6,从星期日算起
int tm_yday; // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起
int tm_isdst; // 夏令时
};
获取当前日期和时间
#include <iostream>
#include <ctime>
using namespace std;
int main( )
{
// 基于当前系统的当前日期/时间
time_t now = time(0);
// 把 now 转换为字符串形式
char* dt = ctime(&now);
cout << "本地日期和时间:" << dt << endl;
// 把 now 转换为 tm 结构
tm *gmtm = gmtime(&now);
dt = asctime(gmtm);
cout << "UTC 日期和时间:"<< dt << endl;
}
使用结构 tm 格式化时间
tm 结构在 C/C++ 中处理日期和时间相关的操作时,显得尤为重要。
tm 结构以 C 结构的形式保存日期和时间。大多数与时间相关的函数都使用了 tm 结构。下面的实例使用了 tm 结构和各种与日期和时间相关的函数。
#include <iostream>
#include <ctime>
using namespace std;
int main( )
{
// 基于当前系统的当前日期/时间
time_t now = time(0);
cout << "1970 到目前经过秒数:" << now << endl;
tm *ltm = localtime(&now);
// 输出 tm 结构的各个组成部分
cout << "年: "<< 1900 + ltm->tm_year << endl;
cout << "月: "<< 1 + ltm->tm_mon<< endl;
cout << "日: "<< ltm->tm_mday << endl;
cout << "时间: "<< ltm->tm_hour << ":";
cout << ltm->tm_min << ":";
cout << ltm->tm_sec << endl;
}
1970 到目前时间:1503564157
年: 2017
月: 8
日: 24
时间: 16:42:37
C++ 数据结构
结构可以用来存储不同类型的数据项。
定义结构
为了定义结构,您必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
type_name 是结构体类型的名称,
member_type1 member_name1 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。
object_names 结构体的变量 /别名
struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;
结构体类型 Books,变量为 book:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
访问结构成员
为了访问结构的成员,我们使用成员访问运算符(.)
实例中定义了结构体类型 Books 及其两个变量 Book1 和 Book2。
#include <iostream>
#include <cstring>
using namespace std;
// 声明一个结构体类型 Books
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
Books Book1; // 定义结构体类型 Books 的变量 Book1
Books Book2; // 定义结构体类型 Books 的变量 Book2
// Book1 详述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "编程语言");
Book1.book_id = 12345;
// Book2 详述
strcpy( Book2.title, "CSS 教程");
strcpy( Book2.author, "Runoob");
strcpy( Book2.subject, "前端技术");
Book2.book_id = 12346;
// 输出 Book1 信息
cout << "第一本书标题 : " << Book1.title <<endl;
cout << "第一本书作者 : " << Book1.author <<endl;
cout << "第一本书类目 : " << Book1.subject <<endl;
cout << "第一本书 ID : " << Book1.book_id <<endl;
// 输出 Book2 信息
cout << "第二本书标题 : " << Book2.title <<endl;
cout << "第二本书作者 : " << Book2.author <<endl;
cout << "第二本书类目 : " << Book2.subject <<endl;
cout << "第二本书 ID : " << Book2.book_id <<endl;
return 0;
}
第一本书标题 : C++ 教程
第一本书作者 : Runoob
第一本书类目 : 编程语言
第一本书 ID : 12345
第二本书标题 : CSS 教程
第二本书作者 : Runoob
第二本书类目 : 前端技术
第二本书 ID : 12346
结构作为函数参数
您可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。
#include <iostream>
#include <cstring>
using namespace std;
void printBook( struct Books book );
// 声明一个结构体类型 Books
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
Books Book1; // 定义结构体类型 Books 的变量 Book1
Books Book2; // 定义结构体类型 Books 的变量 Book2
// Book1 详述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "编程语言");
Book1.book_id = 12345;
// Book2 详述
strcpy( Book2.title, "CSS 教程");
strcpy( Book2.author, "Runoob");
strcpy( Book2.subject, "前端技术");
Book2.book_id = 12346;
// 输出 Book1 信息
printBook( Book1 );
// 输出 Book2 信息
printBook( Book2 );
return 0;
}
void printBook( struct Books book )
{
cout << "书标题 : " << book.title <<endl;
cout << "书作者 : " << book.author <<endl;
cout << "书类目 : " << book.subject <<endl;
cout << "书 ID : " << book.book_id <<endl;
}
书标题 : C++ 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
书标题 : CSS 教程
书作者 : Runoob
书类目 : 前端技术
书 ID : 12346
指向结构的指针
您可以定义一个指向结构的指针,方式与定义指向其他类型变量的指针相似,
// 定义结构体类型 Books 的变量 Book1
Books Book1;
//定义一个指向结构Books的指针struct_pointer
struct Books *struct_pointer;
//给指针变量struct_pointer中存储结构变量的地址&Book1
struct_pointer = &Book1;
#include <iostream>
#include <cstring>
using namespace std;
void printBook( struct Books *book );
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
Books Book1; // 定义结构体类型 Books 的变量 Book1
Books Book2; // 定义结构体类型 Books 的变量 Book2
// Book1 详述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "编程语言");
Book1.book_id = 12345;
// Book2 详述
strcpy( Book2.title, "CSS 教程");
strcpy( Book2.author, "Runoob");
strcpy( Book2.subject, "前端技术");
Book2.book_id = 12346;
// 通过传 Book1 的地址来输出 Book1 信息
printBook( &Book1 );
// 通过传 Book2 的地址来输出 Book2 信息
printBook( &Book2 );
return 0;
}
// 该函数以结构指针作为参数
void printBook( struct Books *book )
{
cout << "书标题 : " << book->title <<endl;
cout << "书作者 : " << book->author <<endl;
cout << "书类目 : " << book->subject <<endl;
cout << "书 ID : " << book->book_id <<endl;
}
书标题 : C++ 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
书标题 : CSS 教程
书作者 : Runoob
书类目 : 前端技术
书 ID : 12346
typedef 关键字
typedef 可以为创建的类型取一个"别名"
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
}book1;
类 & 对象
C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,是一种用户自定义的数据类型,它是一种封装了数据和函数的组合。类中的数据称为成员变量,函数称为成员函数。类可以被看作是一种模板,可以用来创建具有相同属性和行为的多个对象。
类定义
定义一个类需要使用关键字 class,然后指定类的名称, 类的主体是包含在一对花括号中,主体包含类的成员变量和成员函数。
定义一个类,本质上是定义一个数据类型的蓝图,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
使用关键字 class 定义 Box 数据类型,包含了三个成员变量 length、breadth 和 height:
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
定义 对象
对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Box 的两个对象:
对象 Box1 和 Box2 都有它们各自的数据成员。
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
访问数据成员
类的对象的公共数据成员可以使用直接成员访问运算符 . 来访问。
Box.h
class Box{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double get(void);// 成员函数声明
void set(double len, double bre, double hei );// 成员函数声明
};
// 成员函数定义
double Box::get(void) {
return length * breadth * height;
}
void Box::set(double len, double bre, double hei) {
length = len;
breadth = bre;
height = hei;
}
class.cpp
#include <iostream>
#include "Box.h"
using namespace std;
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double volume = 0.0; // 用于存储体积
// box 1 详述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box 1 的体积
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的体积:" << volume <<endl; // Box1 的体积:210
// box 2 详述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// box 2 的体积
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的体积:" << volume <<endl; //Box2 的体积:1560
// box 3 详述
Box3.set(16.0, 8.0, 12.0);
volume = Box3.get();
cout << "Box3 的体积:" << volume <<endl; //Box3 的体积:1536
}
类 & 对象详解
1.类的成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。
在类中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。所以您可以按照如下方式定义 getVolume() 函数:
double getVolume(void){
return length * breadth * height;
}
在类的外部使用范围解析运算符 :: 定义 函数set
void BoxBig::set(double len, double bre, double hei) {
length = len;
breadth = bre;
height = hei;
}
BoxBig.h
#ifndef XX4_NDK_CPP_BOX2_H
#define XX4_NDK_CPP_BOX2_H
#endif //XX4_NDK_CPP_BOX2_H
class BoxBig{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数可以定义在类定义内部
//在类中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。所以您可以按照如下方式定义 getVolume() 函数:
double getVolume(void){
return length * breadth * height;
}
// 成员函数声明
void set(double len, double bre, double hei );
void setLength( double len );
void setBreadth( double bre );
void setHeight( double hei );
};
// 在类的外部使用范围解析运算符 :: 定义 函数set
// 成员函数定义
void BoxBig::set(double len, double bre, double hei) {
length = len;
breadth = bre;
height = hei;
}
void BoxBig::setLength( double len )
{
length = len;
}
void BoxBig::setBreadth( double bre )
{
breadth = bre;
}
void BoxBig::setHeight( double hei)
{
height = hei;
}
class2.cpp
#include <iostream>
#include "BoxBig.h"
using namespace std;
int main( )
{
BoxBig myBox; // 创建一个对象
myBox.set(16.0, 8.0, 12.0);
double volume = myBox.getVolume(); // 调用该对象的成员函数
cout << "BoxBig 的体积1:" << volume <<endl; //BoxBig 的体积1:1536
myBox.setLength(6.0);
myBox.setBreadth(7.0);
myBox.setHeight(5.0);
double volume2 = myBox.getVolume();
cout << "BoxBig 的体积2:" << volume2 <<endl; // BoxBig 的体积2:210
}
2.类访问修饰符
访问修饰符关键字
类成员可以被定义为 public、private 或 protected。默认情况下是定义为 private。
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。
类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。
一个类可以有多个 public、protected 或 private 标记区域。每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是 private。
1公有(public)成员
公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值,
#include <iostream>
using namespace std;
class Line
{
public:
double length;
void setLength( double len );
double getLength( void );
};
// 成员函数定义
double Line::getLength(void)
{
return length ;
}
void Line::setLength( double len )
{
length = len;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl; //Length of line : 6
// 不使用成员函数设置长度
line.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of line : " << line.length <<endl;//Length of line : 10
return 0;
}
2 私有(private)成员
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
默认情况下,类的所有成员都是私有的。例如在下面的类中,width 是一个私有成员,这意味着,如果您没有使用任何访问修饰符,类的成员将被假定为私有成员:
#include <iostream>
using namespace std;
class Box
{
public:
double length;
void setWidth( double wid );
double getWidth( void );
private:
double width;
};
// 成员函数定义
double Box::getWidth(void)
{
return width ;
}
void Box::setWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
Box box;
// 不使用成员函数设置长度
box.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of box : " << box.length <<endl;
// 不使用成员函数设置宽度
// box.width = 10.0; // Error: 因为 width 是私有的
box.setWidth(10.0); // 使用成员函数设置宽度
cout << "Width of box : " << box.getWidth() <<endl;
return 0;
}
3protected(受保护)成员
protected(受保护)成员变量或函数与私有成员十分相似,但有一点不同,protected(受保护)成员在派生类(即子类)中是可访问的。
我们从父类 Box 派生了一个子类 smallBox。
下面的实例与前面的实例类似,在这里 width 成员可被派生类 smallBox 的任何成员函数访问。
#include <iostream>
using namespace std;
//父类
class Box
{
protected:
// 在这里 width 成员可被派生类 smallBox 的任何成员函数访问。
double width;
};
// SmallBox 是派生类
class SmallBox:Box
{
public:
void setSmallWidth( double wid );
double getSmallWidth( void );
};
// 子类的成员函数setSmallWidth
void SmallBox::setSmallWidth(double wid) {
width = wid;
}
// 子类的成员函数 getSmallWidth
double SmallBox::getSmallWidth(void) {
return width ;
}
// 程序的主函数
int main( )
{
SmallBox smallBox;
// 使用成员函数设置宽度
smallBox.setSmallWidth(5.0);
cout << "Width of box : "<< smallBox.getSmallWidth() << endl; // Width of box : 5
return 0;
}
类继承中的特点
有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。
.public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private
但无论哪种继承方式,上面两点都没有改变:
1.private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
2.protected 成员可以被派生类访问。
1 继承 public父类
#include<iostream>
#include<assert.h>
using namespace std;
class A{
public:
int a;
A(){
a1 = 1;
a2 = 2;
a3 = 3;
a = 4;
}
void fun(){
cout << a << endl; //正确
cout << a1 << endl; //正确
cout << a2 << endl; //正确
cout << a3 << endl; //正确
}
public:
int a1;
protected:
int a2;
private:
int a3;
};
class B : public A{
public:
int a;
B(int i){
A();//可以访问父类的public成员函数
a = i;
}
void fun(){
cout << a << endl; //正确,public成员
cout << a1 << endl; //正确,可以访问父类基类的public成员a1,在派生类中仍是public成员。
cout << a2 << endl; //正确,可以访问父类基类的protected成员a2,在派生类中仍是protected可以被派生类访问。
//cout << a3 << endl; //错误,不能访问父类基类的private成员a3 不能被派生类访问。
}
};
int main(){
B b(10);
cout << b.a << endl;
cout << b.a1 << endl; //正确
//cout << b.a2 << endl; //错误,类外不能访问protected成员
// cout << b.a3 << endl; //错误,类外不能访问private成员
system("pause");
return 0;
}
2继承 protected 父类
#include<iostream>
#include<assert.h>
using namespace std;
class A{
public:
int a;
A(){
a1 = 1;
a2 = 2;
a3 = 3;
a = 4;
}
void fun(){
cout << a << endl; //正确
cout << a1 << endl; //正确
cout << a2 << endl; //正确
cout << a3 << endl; //正确
}
public:
int a1;
protected:
int a2;
private:
int a3;
};
class B : protected A{
public:
int a;
B(int i){
A();//可以访问父类的public成员函数
a = i;
}
void fun(){
cout << a << endl; //正确,public成员
cout << a1 << endl; //正确,派生类可以访问父类基类的public成员a1,
cout << a2 << endl; //正确,派生类可以访问父类基类的protected成员a2,
//cout << a3 << endl; //错误,派生类不能访问父类基类的private成员a3
}
};
int main(){
B b(10);
cout << b.a << endl; //正确。public成员
// cout << b.a1 << endl; //错误, 类外不能访问 protected 父类的 public 成员a1 。
// cout << b.a2 << endl; //错误,类外不能访问 protected 父类的 protected成员a2
// cout << b.a3 << endl; //错误, 类外不能访问 protected 父类的 private成员a3
system("pause");
return 0;
}
3继承private父类
#include<iostream>
#include<assert.h>
using namespace std;
class A{
public:
int a;
A(){
a1 = 1;
a2 = 2;
a3 = 3;
a = 4;
}
void fun(){
cout << a << endl; //正确
cout << a1 << endl; //正确
cout << a2 << endl; //正确
cout << a3 << endl; //正确
}
public:
int a1;
protected:
int a2;
private:
int a3;
};
class B : private A{
public:
int a;
B(int i){
A();//可以访问父类的public成员函数
a = i;
}
void fun(){
cout << a << endl; //正确,public成员
cout << a1 << endl; //正确, 父类基类public成员a1,在派生类中变成了private,可以被派生类访问。
cout << a2 << endl; //正确,父类基类的protected成员a2,在派生类中变成了private,可以被派生类访问。
//cout << a3 << endl; //错误,派生类不能访问父类基类的private成员a3
}
};
int main(){
B b(10);
cout << b.a << endl; //正确。public成员
// cout << b.a1 << endl; //错误, 类外不能访问 private成员a1 。
// cout << b.a2 << endl; //错误,类外不能访问 private成员a2
// cout << b.a3 << endl; //错误, 类外不能访问 private成员a3
system("pause");
return 0;
}
3.构造函数&析构函数
3.1 类的构造函数
类的构造函数是一种特殊的函数,在创建一个新的对象时调用。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
构造函数也可以带有参数。
3.2类的析构函数
类的析构函数也是一种特殊的函数,在删除所创建的对象时调用。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数
Line(double len); // 这是构造函数
~Line(); // 这是析构函数声明
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
// 成员函数定义,包括构造函数
Line::Line( double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
// 析构函数
Line::~Line(void) {
cout << "Object is being deleted" << endl;
}
void Line::setLength(double len) {
length = len;
}
double Line::getLength(void) {
return length;
}
// 程序的主函数
int main( )
{
Line line;// 无参构造函数
Line line(10.0); // 有参构造函数
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl; //Length of line : 6
return 0;
}
4.拷贝构造函数
拷贝构造函数,是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
拷贝构造函数通常用于:
-
通过使用另一个同类型的对象来初始化新创建的对象。
-
复制对象把它作为参数传递给函数。
-
复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义生成一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:
obj 是一个对象引用,该对象是用于初始化另一个对象
classname (const classname &obj) {
// 构造函数的主体
}
#include <iostream>
using namespace std;
class Line
{
public:
int getLenth( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int* ptr;//声明一个指针变量ptr
};
// 构造函数定义 , 成员函数定义
Line::Line(int len) {
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
// 创建拷贝构造函数,并为指针 ptr 分配内存
Line::Line(const Line &obj) {
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
// 为指针分配内存
ptr = new int;
* ptr= * obj.ptr; // 拷贝值
}
// 析构函数 释放内存
Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr; //释放内存
}
int Line::getLenth( void )
{
return *ptr;
}
void display(Line obj)
{
cout << "line 大小 : " << obj.getLenth() <<endl;
}
int main( )
{
Line line1(10);
Line line2 = line1; // 这里也调用了拷贝构造函数
display(line1);
display(line2);
return 0;
}
/*
调用构造函数
调用拷贝构造函数并为指针 ptr 分配内存
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
释放内存
释放内存
*/
多个构造函数 析构函数 拷贝构造函数的案例:
// 1.构造函数详讲, 2.析构函数, 3.拷贝构造函数
#include <iostream>
#include <string.h>
using namespace std;
class Student {
// 构造函数
public:
// 空参数构造函数
Student() {
cout << "Student 空参数构造函数" << endl;
}
// 一个参数的构造函数
// :Student(name, 87) 等价 1.调用两个参数的构造函数, 2.再调用当前一个参数的构造函数
// 先调用两个参数的,再调用一个的
Student(char *name) :Student(name, 87){
cout << "Student 一个参数的构造函数" << endl;
this->name=name;
}
// 两个参数的构造函数
Student(char *name, int age) {
cout << "Student 两个参数的构造函数" << endl;
this->name=name;
this->age=age;
}
// 三个参数的构造函数
Student(char *name, int age,int sex) : name(name), age(age) {
cout << "三个参数构造函数" << endl;
}
// 析构函数 Student对象的,临终遗言,Student对象被回收了,你做一些释放工作
// delete stu 的时候,我们的析构函数一定执行
// free不会执行析构函数,也意味着,你没法在析构函数里面,做释放工作, malloc也不会调用构造函数
~Student() {
cout << "~Student 析构函数" << endl;
// 必须释放 堆区开辟的成员
if(this->name){
free(this->name);
this->name=NULL; // 执行NULL的地址,避免出现悬空指针
}
}
// 以前是默认有一个拷贝构造函数,stu2 = stu1 默认赋值 【隐士 你看不到】
// 拷贝构造函数,它默认有,我们看不到,一旦我们写拷贝构造函数,会覆盖她
// 对象1 = 对象2
// 覆盖拷贝构造函数
Student(const Student& obj){ // const Student& 常量引用:只读的,不让你修改
cout << "Student 自定义拷贝构造函数" << endl;
// 我们自己赋值
// 为什么要自己赋值,自己来控制,就可以 例如:-10
this->name=obj.name;
this->age=obj.age-10;
cout << "Student 自定义拷贝构造函数 内存地址&obj= " << &obj<< endl; // Student 自定义拷贝构造函数 内存地址&obj= 0x71fedc
}
// 私有属性
private:
char *name;
int age;
// 公开的 set get 函数
public:
char *getName() const {
return this->name;
}
void setName(char *name) {
this->name = name;
}
int getAge() const {
return this->age;
}
void setAge(int age) {
this->age = age;
}
};
struct Person {
int age;
char *name;
};
int mainT24(){
// TODO =========== 在栈区 开辟空间的
Student stu; // 调用 空参数构造函数
stu.setAge(34);
stu.setName("李元霸");
cout << "name:" << stu.getName() << ", age:" << stu.getAge() << endl; //Student 空参数构造函数 name:李元霸, age:34
Student stu2("李连杰");
cout << "name:" << stu2.getName() << ", age:" << stu2.getAge() << endl; //Student 两个参数的构造函数 Student 一个参数的构造函数 name:李连杰, age:87
Student stu3("雄霸", 30);
cout << "name:" << stu3.getName() << ", age:" << stu3.getAge() << endl; //Student 两个参数的构造函数 name:雄霸, age:30
// TODO =========== 堆区 开辟空间的 堆区必须手动释放,否则内存占用越来
// *student -> 调用一级指针的成员
// C++中,必须使用 new/delete 一套
Student* student = new Student("杜子腾", 26);
cout << "name:" << student->getName() << ", age:" << student->getAge() << endl; //Student 两个参数的构造函数 name:杜子腾, age:26
delete student;
//todo ===========Student拷贝构造函数 demo==========================================
Student student1("厉鬼",100);
Student student2=student1; // todo 这样赋值是会调用 自定义拷贝构造函数 Student(const Student& obj),我们自己赋值
// Student student3;
// student3= student1; // todo 这样赋值是不会调用 自定义拷贝构造函数Student(const Student& obj),但是会调用默认赋值
cout <<"student2.getName()="<< student2.getName() << " , " <<"student2.getName()="<< student2.getAge() << endl; //Student 自定义拷贝构造函数 内存地址= 0x71fedc student2.getName()=厉鬼 , student2.getName()=90
cout << "main &student1= " << &student1 << endl; // main &student1= 0x71fedc 地址的打印是一样的, 注意:cnetos的环境 地址打印有差异,要注意
//todo =========================================
Student *student11 = new Student("杜子腾", 39);
cout <<"student11->getName()="<< student11->getName() << " , " <<"student11->getAge()="<< student11->getAge() << endl; //student11->getName()=杜子腾 , student11->getAge()=39
Student *student22 = student11; //todo 这样压根就不会执行自定义拷贝构造函数Student(const Student& obj)(这个是指针指向问题,和我们刚刚那个 对象2=对象1 (Student student2=student1)是两回事) 数据信息没发生变化
cout <<"student22->getName()="<< student22->getName() << " , " <<"student22->getAge()="<< student22->getAge() << endl; //todo student22->getName()=杜子腾 , student22->getAge()=39
student22->setName("王腾子");
student22->setAge(100);
cout <<"student22->getName()="<< student22->getName() << " , " <<"student22->getAge()="<< student22->getAge() << endl; //todo student22->getName()=王腾子 , student22->getAge()=100(student22 数据才发生了改变)
cout <<"student11->getName()="<< student11->getName() << " , " <<"student11->getAge()="<< student11->getAge() << endl; // student11->getName()=王腾子 , student11->getAge()=100 (student11 数据才发生了改变)
//todo ===========Person拷贝构造函数 demo==========================================
Person person1 = {100, "张三丰"};
cout << "person1.name="<<person1.name << ", " <<" person1.age="<< person1.age << endl; //person1.name=张三丰, person1.age=100
// = 你看起来,没有什么特殊,隐士的代码:你看不到 C/C++编译器 会把person1的成员值赋值给person2成员
Person person2 = person1;
cout << "person2.name="<<person2.name << ", " <<" person2.age="<< person2.age << endl; // person2.name=张三丰, person2.age=100
cout << "&person1="<<&person1 << endl; // &person1=0x71fedc
cout << "&person2="<<&person2 << endl; //&person2=0x71fed4
// 思考:对象 对象2 person2 = 对象1 person1 默认的 拷贝构造函数
return 0;
}
什么时候使用浅拷贝和深拷贝?使用 浅拷贝的问题
当你的类中有堆成员this->name= static_cast<char *>(malloc(sizeof(char *) * 10)) 需要使用深拷贝构造函数,否则使用浅拷贝。
浅拷贝的问题: 类默认有一个浅拷贝构造函数(有坑会崩溃) 隐士的 我们看不见
浅拷贝,新地址name == 旧地址name 指向同一个空间,会造成,多次重复释放free的问题,会引发奔溃.为了解决这个问题就需要使用深拷贝。
#include<iostream>
#include<string.h>
using namespace std;
class Student3{
public:
int age;
char * name;//有堆成员this->name= static_cast<char *>(malloc(sizeof(char *) * 10)) 需要使用深拷贝构造函数
Student3() {cout << "空参数构造函数" << endl; }
Student3(char *name) :Student3(99,name ){
cout << "一个参数构造函数 this=" <<(int)this<< endl;
this->name=name;
}
Student3(int age, char *name) : age(age), name(name) {
cout << "二个参数构造函数 this=" << (int)this << endl;
//this->name=name;
this->name= static_cast<char *>(malloc(sizeof(char *) * 10));
strcpy(this->name,name);
this->age=age;
}
~Student3() {
cout << "析构函数执行 释放新地址 this->name:" << (int)this->name << endl;
free(this->name);
this->name = NULL;
}
// 默认有一个浅拷贝构造函数(有坑会崩溃) 隐士的 我们看不见
// 一旦复写了自定义深拷贝构造函数,默认的还在吗? 默认浅拷贝构造函数被覆盖了
// 自定义深拷贝构造函数 如果有堆成员,必须采用深拷贝
Student3(const Student3& stu){
// stu 旧地址
// this 新地址
// s2 = 新地址
cout << "拷贝构造函数旧地址 &stu=" << (int)&stu << " this=" << (int)this << endl;
/**
* 【浅拷贝】:新地址name == 旧地址name 指向同一个空间,会造成,多次重复释放free的问题,会引发奔溃
新地址name = 旧地址 (浅拷贝)
*/
// todo this新地址的name = 旧地址obj 的 name(浅拷贝)
// this->name = stu.name;
// 【深拷贝】
this->name = (char *)malloc(sizeof(char *)* 10);
strcpy(this->name, name);
this->age = stu.age;
cout << "拷贝构造函数 新地址 this->name=" << ((int) this->name) << " stu.name=" << (int)stu.name << endl;
}
};
void showStudent(Student3 stu) {
cout << "showStudent函数:输出stu的新地址 stu的指针&stu=" << (int)&stu << " stu.name=" << stu.name << ",stu.age=" << stu.age<< endl;
}
int main(){
Student3 stu(31,"刘奋");
cout << " main 函数 stu的指针 &stu=" << (int)&stu << " ,stu.name=" << stu.name << " ,stu.age=" << stu.age<< endl;
showStudent(stu); // 弹栈后 新地址name释放一遍
// showStudent(stu); // 弹栈后 新地址name释放一遍
showStudent(stu);
showStudent(stu);
showStudent(stu);
getchar(); // 不要一闪而过,让程序停留
}
5.友元函数
友元函数可以访问类的 private 和 protected 成员。
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明某个函数为当前类的友元函数,需要在类定义中该函数原型前使用关键字 friend
class Box
{
double width;
public:
double length;
friend void printWidth( Box box ); // 友元函数friend class ClassTwo;//友元类ClassTwo void setWidth( double wid );};
友元函数 友元类的使用
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth( Box box );// 友元函数
friend class BigBox; // 友元类
void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth(Box box) {
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "在友元函数printWidth(Box box)中输出Box类的成员box.width=" << box.width <<endl;
}
class BigBox{
public:
// Box &box 拷贝构造函数
void Print(int width, Box &box){
// BigBox是Box的友元类,在BigBox类中可以直接访问Box类的任何成员
box.setWidth(width);
cout << "在友元类BigBox中输出Box类的成员box.width=" << box.width << endl;
}
};
int main( ) {
Box box;
BigBox bigBox;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth(box); //Width of box : 10
// 使用友元类中的方法Print(int width, Box &box)设置宽度
bigBox.Print(20,box);
getchar();
return 0;
}
/*
在友元函数printWidth(Box box)中输出Box类的成员box.width=10
在友元类BigBox中输出Box类的成员box.width=20
*/
享学友元函数的案例: 10T7
#include <iostream>
using namespace std;
// 友元函数
// 老外:你是它的好朋友,那就可以拿私有成员给好朋友
class Person {
private: // 私有的age,外界不能访问
int age = 0;
public:
Person() {}
Person(int age) {
this->age = age;
}
int getAge() const {
return age;
}
// 定义友元函数 (声明,没有实现)
friend void updateAge(Person *pPerson, int age);
};
// 友元函数的实现,可以访问所以私有成员
void updateAge(Person *pPerson, int age) {
// 默认情况下:不能修改 私有的age
// 友元函数updateAge() 可以访问类Person的所有私有成员
pPerson->age=age;
};
int main10T7() {
Person* person = new Person(9); //todo 堆区创建对象 需要指针 需要手动释放
cout << "person->getAge()="<< person->getAge() << endl; // person->getAge()=9
updateAge(person,88);
cout << "person->getAge()="<< person->getAge() << endl; // person->getAge()=88
Person person1 = Person(11);// todo 栈区创建对象 不需要指针 自动释放
cout << "person1.getAge()="<< person1.getAge() << endl; // person1.getAge()=11
updateAge(&person1,22);
cout << "person1.getAge()="<< person1.getAge() << endl; //person1.getAge()=22
}
6.内联函数inline
C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。
在类中定义的函数都是内联函数,即使没有使用 inline 说明符。
引入内联函数的目的是为了解决程序中函数调用的效率问题,这么说吧,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的节省。所以内联函数一般都是1-5行的小函数。在使用内联函数时要留神:
1.在内联函数内不允许使用循环语句和开关语句;
2.内联函数的定义必须出现在内联函数第一次调用之前;
3.类结构中内部定义的所有函数是内联函数。
#include <iostream>
using namespace std;
inline int Max(int x, int y)
{
return (x > y)? x : y;
}
// 程序的主函数
int main( )
{
cout << "Max (20,10): " << Max(20,10) << endl;
cout << "Max (0,200): " << Max(0,200) << endl;
cout << "Max (100,1010): " << Max(100,1010) << endl;
return 0;
}
Max (20,10): 20
Max (0,200): 200
Max (100,1010): 1010
7.C++ 中的 this 指针
每个对象都有一个特殊的指针 this,它指向对象本身。
在 C++ 中,this 指针是一个特殊的指针,它指向当前对象的实例。
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。
this是一个隐藏的指针,可以在类的成员函数中使用,它可以用来指向调用对象。
当一个对象的成员函数被调用时,编译器会隐式地传递该对象的地址作为 this 指针。
友元函数没有 this 指针,因为友元函数不是类的成员,只有成员函数才有 this 指针。
我们定义了一个名为 MyClass 的类,它有一个私有成员变量 value。
类中的 setValue() 函数用于设置 value的 值,而 printValue() 函数用于打印 value 的值。
在 setValue() 函数中,我们使用 this 指针来引用当前对象的成员变量 value,并将传入的形式参数值赋给它
,这样可以明确地告诉编译器我们想要访问当前对象的成员变量value,而不是函数参数或局部变量。
在 printValue() 函数中,我们同样使用 this 指针来引用当前对象的成员变量 value,并将其打印出来。
在 main() 函数中,我们创建了一个 MyClass 的对象 obj,然后使用 setValue(int value) 函数设置 value 的值为 42,并通过 printValue() 函数打印出来。
通过使用 this 指针,我们可以在成员函数中访问当前对象的成员变量,即使它们与函数参数或局部变量同名,这样可以避免命名冲突,并确保我们访问的是正确的变量。
#include <iostream>
class MyClass {
private:
int value;
public:
void setValue(int value){
this->value=value;
}
void printValue() {
std::cout << "value=" << this->value << std::endl;
}
};
int main() {
MyClass obj;
obj.setValue(42);
obj.printValue(); // value=42
return 0;
}
引入 this:
当我们调用成员函数时,实际上是替某个对象调用它。
成员函数通过一个名为 this 的额外隐式参数来访问调用它的那个对象,当我们调用一个成员函数时,用请求该函数的对象地址初始化 this。
例如,如果调用 total.isbn()则编译器负责把 total 的地址传递给 isbn 的隐式形参 this,可以等价地认为编译器将该调用重写成了以下形式:
//伪代码,用于说明调用成员函数的实际执行过程
Sales_data::isbn(&total)
其中,调用 Sales_data 的 isbn 成员时传入了 total 的地址。
在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无须通过成员访问运算符来做到这一点,因为 this 所指的正是这个对象。
任何对类成员的直接访问都被看作是对 this 的隐式引用,
也就是说,当 isbn 使用 bookNo 时,它隐式地使用 this 指向的成员,就像我们书写了 this->bookNo 一样。
对于我们来说,this 形参是隐式定义的。实际上,任何自定义名为 this 的参数或变量的行为都是非法的。
我们可以在成员函数体内部使用 this,因此尽管没有必要,我们还是能把 isbn 定义成如下形式:
std::string isbn() const { return this->bookNo; }
因为 this 的目的总是指向“这个”对象,所以 this 是一个常量指针(参见2.4.2节,第56页),我们不允许改变 this 中保存的地址。
#include <iostream>
using namespace std;
class Box{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0){
cout <<"调用构造函数。" << endl;
length = l;
breadth = b;
height = h;
}
Box(){;}//无参构造函数
~Box(){;} //西构造函数
//返回this, this的指针的类型可理解为 Box*
Box* get_this_address(){
return this;
}
double Volume()
{
return length * breadth * height;
}
int compare(Box box)
{
return this->Volume() > box.Volume();
}
private:
double length; // 宽度
double breadth; // 长度
double height; // 高度
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
int result = Box1.compare(Box2);
if(result){
cout << "Box2 的体积比 Box1 小" <<endl;
}else{
cout << "Box2 的体积大于或等于 Box1" <<endl;
}
// Box* 定义指针box1P 接受对象Box1的get_address()成员函数的返回值
Box* box1P = Box1.get_this_address();
cout << "打印Box1的指针地址=" <<box1P<< endl;
Box* box2P = Box2.get_this_address();
cout << "打印Box2的指针地址=" <<box2P<< endl;
return 0;
}
/*
调用构造函数。
调用构造函数。
Box2 的体积大于或等于 Box1
打印Box1的指针地址=0x68fed0
打印Box2的指针地址=0x68feb8
*/
8.C++ 中指向类的指针
指向类的指针
访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。
指向类的指针的概念:
#include <iostream>
using namespace std;
class Box{
public:
// 构造函数定义带参数
Box(double l=2.0, double b=2.0, double h=2.0){
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 声明 Box1
Box Box2(8.5, 6.0, 2.0); // 声明 Box1
Box * ptrBox; // 声明一个指向类的指针ptrBox
// 保存第一个对象Box1的指针地址给ptrBox
ptrBox = &Box1;
double result = ptrBox->Volume();
// 现在尝试使用成员访问运算符->来访问成员
cout << "Box1:ptrBox->Volume()=" << result << endl; //Box1:ptrBox->Volume()=5.94
// 保存第二个对象Box2的指针地址给ptrBox
ptrBox=&Box2;
// 现在尝试使用成员访问运算符->来访问成员
cout << "Box2:ptrBox->Volume()=" <<ptrBox->Volume() << endl; //Box2:ptrBox->Volume()=102
return 0;
}
9.C++ 类的静态成员
我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount; //静态变量
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时静态变量objectCount 增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount()
{
return objectCount;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员objectCount
int Box::objectCount = 0;
int main(void) {
// 在创建对象之前输出对象的总数
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 在创建对象之后输出对象的总数
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
};
/*
nital Stage Count: 0
调用构造函数。
调用构造函数。
Final Stage Count: 0
*/
C/C++ 中 static 的用法全局变量与局部变量
1. 什么是static?
static 是 C/C++ 中很常用的修饰符,它被用来控制变量的存储方式和可见性。
1.1 static 的引入
在函数内部定义的变量,当程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义为全局的变量,但定义一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅只受此函数控制)。static 关键字则可以很好的解决这个问题。
另外,在 C++ 中,需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见时,可将其定义为静态数据。
1.2 静态数据的存储
全局(静态)存储区:分为 DATA 段和 BSS 段。DATA 段(全局初始化区)存放初始化的全局变量和静态变量;BSS 段(全局未初始化区)存放未初始化的全局变量和静态变量。程序运行结束时自动释放。其中BBS段在程序执行之前会被系统自动清0,所以未初始化的全局变量和静态变量在程序执行之前已经为0。
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。
在 C++ 中 static 的内部实现机制:静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。
这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的 main() 函数前的全局数据声明和定义处。
静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的"尺寸和规格",并不进行实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。
static 被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。
优势:可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。
2. 在 C/C++ 中static的作用
2.1 总的来说
(1)在修饰变量的时候,static 修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。
(2)static 修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是 extern 外部声明也不可以。
(3)static 修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。static 修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存。初始化的时候自动初始化为 0。
(4)不想被释放的时候,可以使用static修饰。比如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调用结束释放可以使用 static 修饰。
(5)考虑到数据安全性(当程序想要使用全局变量的时候应该先考虑使用 static)。
2.2 静态变量与普通变量
2.2.1 静态全局变量有以下特点:
1)静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量;
2)未经初始化的静态全局变量会被程序自动初始化为0(在函数体内声明的自动变量的值是随机的,除非它被显式初始化,而在函数体外被声明的自动变量也会被初始化为 0);
3)静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的。
4)静态全局变量不能被其它文件所用;其它文件中可以定义相同名字的变量,不会发生冲突。
2.2.2全局变量和全局静态变量的区别
1)全局变量是不显式用 static 修饰的全局变量,全局变量默认是有外部链接性的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过 extern 全局变量名的声明,就可以使用全局变量。
2)全局静态变量是显式用 static 修饰的全局变量,作用域是声明此变量所在的文件,其他的文件即使用 extern 声明也不能使用。
2.3 静态局部变量有以下特点:
(1)该变量在全局数据区分配内存;
(2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
(3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为 0;
(4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。
//example:
#include <stdio.h>
#include <stdlib.h>
int k1 = 1;
int k2;
static int k3 = 2;
static int k4;
int main()
{
int i = 1;
printf("栈区-变量地址 &i=%p\n", &i); //栈区-变量地址 &i=0069FF1C
char* p;
p = (char *)malloc(100);
free(p);
printf("栈区-变量地址 &p=%p\n", &p); //栈区-变量地址 &p=0069FF18
printf("堆区地址-动态申请:p=%p\n", p); //堆区地址-动态申请:p=00ED1458
char str[10] = "hello";
printf("栈区-变量 str=%s\n", str); //栈区-变量 str=hello
printf("栈区-变量第一个字符 str[0]=%c\n", str[0]); //栈区-变量第一个字符 str[0]=h
printf("栈区-变量地址 str=%p\n", str); // 栈区-变量地址 str=0069FF0E
printf("栈区-变量地址 &str=%p\n", &str); //栈区-变量地址 &str=0069FF0E
char* q = "hello";
printf("栈区-变量 q=%s\n", q); //栈区-变量 q=hello
printf("栈区-变量 q[0]=%c\n", q[0]); //栈区-变量 q[0]=h
printf("栈区-变量地址 &q=%p\n", &q); //栈区-变量地址 &q=0069FF08
printf(" 字符串常量地址:q=%p\n", q); //字符串常量地址:q=0040999B
printf("全局外部有初值地址 &k1=%p\n", &k1);//全局外部有初值地址 &k1=00408060
printf(" 外部无初值 k2地址 &k2=%p\n", &k2); // 外部无初值 k2地址 &k2=0040E154
printf("外部静态变量有初始值 k3地址 &k3=%p\n", &k3); //静态外部变量有初始值 k3地址 &k3=00408064
printf(" 外部静态变量无初始化值 k4地址 &k4=%p\n", &k4); //外部静态变量无初始化值 k4地址 &k4=0040E104
static int m1 = 2;
printf(" 内部静态变量有初始化值m1地址 &m1=%p\n", &m1); //内部静态变量有初始化值m1地址 &m1=00408068
static int m2;
printf(" 内部静态变量无初始化值m2地址 &m2=%p\n", &m2); // 内部静态变量无初始化值m2地址 &m2=0040E108
return 0;
}
3. static 用法
3.1 在 C++ 中static 关键字最基本的用法是:
(1)被 static 修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要 new 出一个类来
(2)被 static 修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要 new 出一个类来
被 static 修饰的变量、被 static 修饰的方法统一属于类的静态资源,是类实例之间共享的,换言之,一处变、处处变。
在 C++ 中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。
静态成员的定义或声明要加个关键 static。静态成员可以通过双冒号来使用即 <类名>::<静态成员名>。
3.2 静态类相关使用案例
通过类名调用静态成员函数和非静态成员函数:
结论 1:类名可以调用类的静态成员函数<类名>::<静态成员名>。编译通过。
类名不能调用类的非静态成员函数<类名>::<非静态成员名>。报错。
类的对象可以调用静态成员函数和非静态成员函数。编译通过。
结论 2:在类的静态成员函数中不能调用类的非静态成员。
因为静态成员函数属于整个类,在类实例化对象之前就已经分配空间了,而类的非静态成员必须在类实例化对象后才有内存空间,所以这个调用就出错了,就好比没有声明一个变量却提前使用它一样。
结论3: 类的非静态成员函数中可以调用类的静态成员(函数 变量)。
结论 5:类的静态成员变量必须先初始化再使用。
静态资源属于类,但是是独立于类存在的。从 类的加载机制的角度讲,静态资源是类初始化的时候加载的,而非静态资源是类实例化对象的时候加载的。
类的初始化早于类实例化对象,比如 Class.forName("xxx") 方法,就是初始化了一个类,但是并没有实例化对象,只是加载这个类的静态资源罢 了。所以对于静态资源来说,它是不可能知道一个类中有哪些非静态资源的;但是对于非静态资源来说就不一样了,由于它是实例化对象出来之后产生的,因此属于类的这些东西它都能认识。所以上面的几个问题答案就很明确了:
3.3 总结
(1)静态成员函数中不能调用非静态成员。
(2)非静态成员函数中可以调用静态成员。因为静态成员属于类本身,在类的对象产生之前就已经存在了,所以在非静态成员函数中是可以调用静态成员的。
(3)静态成员变量使用前必须先初始化(如 int MyClass::m_nNumber = 0;),否则会在 linker 时出错。
(4)在类中,static 可以用来修饰静态数据成员和静态成员方法。
4.静态数据成员
(1)静态数据成员可以实现多个对象之间的数据共享,它是类的所有对象的共享成员,它在内存中只占一份空间,如果改变它的值,则各对象中这个数据成员的值都被改变。
(2)静态数据成员是在程序开始运行时被分配空间,到程序结束之后才释放,只要类中指定了静态数据成员,即使不定义对象,也会为静态数据成员分配空间。
(3)静态数据成员可以被初始化,但是只能在类体外进行初始化,若未对静态数据成员赋初值,则编译器会自动为其初始化为 0。
4)静态数据成员既可以通过对象名引<类的对象>.<静态成员名>,也可以通过类名引用<类名>::<静态成员名>。
5.静态成员函数
(1)静态成员函数和静态数据成员一样,他们都属于类的静态成员,而不是对象成员。
(2)非静态成员函数有 this 指针,而静态成员函数没有 this 指针。
(3)静态成员函数主要用来访问静态数据成员而不能访问非静态成员。
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
class Point
{
public:
Point()
{
m_nPointCount++;
}
~Point()
{
m_nPointCount--;
}
//非静态函数
void init(){
// 类的非静态成员函数中可以调用类的静态成员(函数 变量)
output();
m_nPointCount++;
}
//静态函数
static void output(){
// 在类的静态成员函数中不能调用类的非静态成员。
printf("%d\n", m_x);//报错
printf("%d\n", m_nPointCount);
}
private:
// 非静态变量
int m_x;
//静态变量
static int m_nPointCount;
};
int main() {
Point::init(); //不能通过类名来调用类的非静态成员函数<类名>::<静态成员名>。 //报错
Point::output();// 静态成员可以通过双冒号 <类名>::<静态成员名> 调用。
//通过类的对象调用静态成员函数和非静态成员函数。编译通过.
Point point;
point.init();
point.output();
return 0;
}
6.享学案例static的正确用法:
static关键字。 正确的写法
静态的总结:
1.可以直接通过类名::静态成员(字段/函数)
2.静态的属性必须要先声明,然后再初始化实现(规则)
3.静态的函数只能取操作静态的属性和方法(Java)
// 为什么需要 this。
#include <iostream> // iostream.h 早期C++的方式
using namespace std;
class Student {
private:
char *name;
int age;
public:
static int id; // 先声明
char *getName() const {
return this->name;
}
void setName(char *name) {
this-> name = name;
}
int getAge() const {
return this->age ;
}
void setAge(int age) {
this-> age = age;
}
};
//再初始化实现静态成员id
int Student::id = 9527;
int main(){ // 10T5
// ======= 常规使用下而已
Student student;
student.setAge(99);
student.setName("戴瑞");
cout << student.getName() << " , " << student.getAge()<< endl; // 戴瑞 , 99
// ========== this 纠结
Student student1;
student1.setAge(88);
student1.id=880;//全局静态修改ID
Student student2;
student2.setAge(99);
student2.id=990; //全局静态修改ID
// 它怎么知道是获取student1的age
cout << " student1.getAge=" << student1.getAge() << endl; // student1.getAge=88
// 它怎么知道是获取student2的age
cout << " student2.getAge=" << student2.getAge() << endl; // student2.getAge=99
cout << "student1.id=" << student1.id << endl; //student1.id=990
cout << "student2.id=" << student2.id << endl; //student2.id=990
cout << "Student::id=" << Student::id << endl; //Student::id=990
Student::id = 111; //全局静态id修改后
cout << "全局静态id修改后 student1.id=" << student1.id << endl; //全局静态id修改后 student1.id=111
cout << "全局静态id修改后 student2.id=" << student2.id << endl; //全局静态id修改后 student2.id=111
cout << "全局静态id修改后 Student::id=" << Student::id << endl; //全局静态id修改后 Student::id=111
return 0;
}
C++ 继承
面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
继承代表了 is a 关系。例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。
// 基类
class Animal {
// eat() 函数
// sleep() 函数
};
//派生类
class Dog : public Animal {
// bark() 函数
};
基类 & 派生类
访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,默认为 private。
base-class 基类
derived-class 派生类
class derived-class: access-specifier base-class
#include <iostream>
using namespace std;
// 基类
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生类
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
访问控制和继承
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
访问 | public | protected | private |
---|---|---|---|
同一个类 | yes | yes | yes |
派生类 | yes | yes | no |
外部的类 | yes | no | no |
一个派生类继承了所有的基类方法,但下列情况除外:
(1)基类的构造函数、析构函数和拷贝构造函数不能继承。
(2)基类的重载运算符不能继承。
(3)基类的友元函数不能继承。
继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。
访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,默认为 private。
base-class 基类
derived-class 派生类
class derived-class: access-specifier base-class
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
access-specifier=公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
access-specifier=保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
access-specifier=私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
C++对象继承案例: 11T4
// C++对象继承。
#include <iostream>
using namespace std;
class Person {
public:
char *name;
int age;
public:
Person(char *name, int age) : name(name) {
this->age = age;
cout << "Person 构造函数" << endl;
}
void print() {
cout << this->name << " , " << this->age << endl;
}
};
// 1.父类默认是私有的 : private Person
// 2.父类必须公开继承,才可以访问父类的成员name age
// 3.私有继承父类:在子类里面是可以访问私有父类的成员,但是在类的外面不能访问私有父类的成员
class Student:public Person{
private:
char * course;
public:
// :父类 , 给自己子类成员初始化
Student(char *name, int age, char *course) : Person(name, age), course(course) {
cout << "Student 构造函数" << endl;
}
void test() {
cout << name << endl;
cout << age << endl;
cout << course << endl;
print();
}
public:
char *getCourse() const {
return course;
}
};
int main11T4() {
Student student("李元霸", 99, "C++");
cout << "student.name="<<student.name<<" student.age="<<student.age<<" student.getCourse()="<<student.getCourse()<< endl;//student.name=李元霸 student.age=99 student.getCourse()=C++
// 公开继承,才可以拿父类的成员name age
student.name = "李四";
student.age=11;
cout << "student.name="<<student.name<<" student.age="<<student.age<<" student.getCourse()="<<student.getCourse()<< endl; //student.name=李四 student.age=11 student.getCourse()=C++
Student* student1 = new Student("浦上", 55, "Java");
cout << "student1->name="<<student1->name<<" student1->age="<<student1->age<<" student1->getCourse()="<<student1->getCourse()<< endl; //student1->name=浦上 student1->age=55 student1->getCourse()=Java
delete student1;
student1=NULL;
return 0;
}
子类继承父类的 构造函数的几种写法方式: 12T1
//源码中属性初始化的方式。
#include <iostream>
using namespace std; // 已经声明了
// 人类
class Person {
protected:
// 注意:string 是 std 命名空间里面的成员,C++源码是这种写法std::string
// string内部其实就是对 char*的封装
string name;
int age;
public:
string getName() {return name;}
int getAge() {return age;}
public:
Person( string name, int age) : name(name), age(age) {
//this->name=name;
//this->age=age;
}
};
// 课程类
class Course {
private:
string name;
public:
string getName() {return name;}
public:
Course( string name) : name(name) {
//this->name=name;
}
};
class Student:public Person {
private:
// 如果定义的是对象成员,必须这样初始化(构造函数的后面 : 对象成员(内容)) 使用我们的第二种方式
Course course; // 对象成员
public:
Course getCourse() { return course; }
public:
// 第一种方式(对象=对象) 编译阶段不认可,无法监测到你是否真的给course对象成员初始化了
// Student(string name, int age, Course course) : Person(name, age){
// this->course=course;
// }
// 第二种方式,编译阶段认可的 对象=对象 对象直接的赋值而已
Student(string name, int age, Course course) : Person(name, age), course(course) {
};
// 第三种方式, 对象(string内容) 直接初始化Course对象 --- 构造函数
Student(string name, int age, string courseNameInfo) : Person(name, age),course(courseNameInfo) {
};
};
int main12T1() {
//第一种方式实现
// Course course("C++");
// Student student("戴瑞", 30, course);
// 第二种方式实现
Course course("C++");
Student student1("戴瑞",30, course);
cout <<"student1.getName()="<<student1.getName() <<" student1.getAge()="<<student1.getAge()<<" student1.getCourse().getName()="<<student1.getCourse().getName()<< endl;
//student1.getName()=戴瑞 student1.getAge()=30 student1.getCourse().getName()=C++
// 第三种方式实现
// Course course3();
Student student3("derry",33,"NDK内容真多");
cout <<"student3.getName()="<<student3.getName() <<" student3.getAge()="<<student3.getAge()<<" student3.getCourse().getName()="<<student3.getCourse().getName()<< endl;
//student3.getName()=derry student3.getAge()=33 student3.getCourse().getName()=NDK内容真多
return 0;
}
多继承
C++多继承会出现二义性的问题.在真实开发过程中,严格避免出现 二义性
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,… { <派生类类体> };
#include <iostream>
using namespace std;
// 基类 Shape
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 基类 PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
}
};
// 派生类
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;
// 输出总花费
cout << "Total paint cost: $" << Rect.getCost(area) << endl;
return 0;
}
Total area: 35
Total paint cost: $2450
C++多继承会出现二义性的问题.在真实开发过程中,严格避免出现 二义性
11T5
// C++是有多继承的
// Java语言不允许多继承,多继承有歧义,如果Java语言多继承 就会导致代码不健壮,(二义性)
// Java多实现:做的非常棒,严格避免出现 二义性问题(歧义)
#include <iostream>
using namespace std;
class BaseActivity1 {
public:
void onCreate() {
cout << "BaseActivity1 onCreate" << endl;
}
void onStart() {
cout << "BaseActivity1 onStart" << endl;
}
void show() {
cout << "BaseActivity1 show" << endl;
}
};
class BaseActivity2 {
public:
void onCreate() {
cout << "BaseActivity2 onCreate" << endl;
}
void onStart() {
cout << "BaseActivity2 onStart" << endl;
}
void show() {
cout << "BaseActivity2 show" << endl;
}
};
class BaseActivity3 {
public:
void onCreate() {
cout << "BaseActivity3 onCreate" << endl;
}
void onStart() {
cout << "BaseActivity3 onStart" << endl;
}
void show() {
cout << "BaseActivity3 show" << endl;
}
};
// 子类 继承 三个父类
class MainActivity1: public BaseActivity1, public BaseActivity2,public BaseActivity3{
public:
void onCreate() {
cout << "MainActivity1 onCreate" << endl;
}
void onStart() {
cout << "MainActivity1 onStart" << endl;
}
void showSonInfo() {
cout << "MainActivity1 showSonInfo" << endl;
}
// 解决方案二: 子类上 重写父类的show函数
void show() {
cout << "MainActivity1 show" << endl;
}
};
int main11T5() {
// 这个是优先寻找子类的函数,因为特别明确,没有问题,还没有产生歧义(二义性)
MainActivity1* mainActivity1=new MainActivity1();
mainActivity1->onCreate();//ok
mainActivity1->onStart(); //ok
mainActivity1->showSonInfo(); //ok
// error: request for member 'show' is ambiguous
// 不明确,二义性,歧义 不知道是哪个父类的show()函数
mainActivity1->show();
// 解决方案一: 明确指定父类 ::
mainActivity1->BaseActivity3::show();
mainActivity1->BaseActivity2::show();
mainActivity1->BaseActivity1::show();
// 解决方案二: 子类上 重写父类的show函数
mainActivity1->show();
delete mainActivity1;
mainActivity1=NULL;
return 0;
}
11T6
// 多继承 二义性2:
// 在真实开发过程中,严格避免出现 二义性
#include <iostream>
using namespace std;
// 祖父类
class Object {
public:
int number;
};
// 父类1
class BaseActivity1 : public Object {
};
// 父类2
class BaseActivity2 : public Object {
};
// 子类
class Son : public BaseActivity1, public BaseActivity2 {
// 第二种解决方案: 在子类中定义同名成员number,覆盖掉父类的相关成员number
public:
int number;
};
int main11T6() {
Son son;
// error: request for member 'show' is ambiguous 二义性 歧义
// son.number = 2000;
// cout << "son.number=" <<son.number<< endl; //son.number=2000
// 第一种解决方案: :: 明确指定
son.BaseActivity1::number = 1000;
son.BaseActivity2::number=200;
// 第二种解决方案: 在类中定义同名成员,覆盖掉父类的相关成员
son.number = 3000;
cout << "son.number=" <<son.number<< endl; // son.number=3000
// 第三种解决方案: 【虚基类】 属于 虚继承的范畴
return 0;
}
定义虚基类 给基类加关键字virtual 让子类虚继承 这个虚基类 11T7
// 第三种解决方案: 【虚基类】 属于 虚继承的范畴
// 真实C++开始,是很少出现,二义性(歧义) 如果出现, 系统源码(系统用 第三种解决方案)
#include <iostream>
using namespace std;
// 祖父类
class Object{
public:
int number;
void show() {
cout << "Object show run..." << endl;
}
};
// virtual 的原理是什么 解决二义性歧义
// 父类1
class BaseActivity1 : virtual public Object {
// public:int number; // 人为制作二义性 error: request for member 'number' is ambiguous
};
// 父类2
class BaseActivity2 : virtual public Object {
// public:int number; // 人为制作二义性 error: request for member 'number' is ambiguous
};
// 子类
class Son : public BaseActivity1, public BaseActivity2 {
};
int main() {
Object object;
BaseActivity1 baseActivity1;
BaseActivity2 baseActivity2;
Son son;
object.number=100;
baseActivity1.number = 200;
baseActivity2.number = 300;
son.number = 400;
cout <<"object.number=" <<object.number << endl; //object.number=100
cout <<"baseActivity1.number="<< baseActivity1.number << endl; //baseActivity1.number=200
cout <<"baseActivity2.number="<< baseActivity2.number << endl; //baseActivity2.number=300
cout <<"son.number="<< son.number << endl; //son.number=400
object.show();
baseActivity1.show();
baseActivity2.show();
son.show();
return 0;
}
定义虚基类 给基类加关键字virtual 让子类虚继承 这个虚基类 12T2
// 虚继承,二义性。 在开发过程中,不准出现,如果出现,要知道怎么回事
#include <iostream>
using namespace std; // 已经声明了
// 祖父类
class Object2 {
public: string info;
};
// 父类1 父类2
//todo 父类Base1 Base2 都加上 virtual 虚继承祖父类Object2 不具有祖父类Object2的成员
class Base1 : virtual public Object2 {};
class Base2 : virtual public Object2 {};
// 子类
class Main1:public Base1,public Base2{};
int main12T2(){
Object2 object; // 在栈区开辟,就会有一个this指针,假设指针是1000H,会有指向的能力
object.info = "A";
cout << "object.info="<<object.info << endl;
Base1 base1; // 在栈区开辟,就会有一个this指针,假设指针是2000H,会有指向的能力
base1.info = "B";
cout << "base1.info="<<base1.info << endl;
Base2 base2; // 在栈区开辟,就会有一个this指针,假设指针是300H,会有指向的能力
base2.info = "C";
cout << "base2.info="<< base2.info << endl;
Main1 main1; // 在栈区开辟,就会有一个this指针,假设指针是4000H,会有指向的能力
main1.info = "D"; //todo 这里会报错 出现二义性 子类不知道是哪个父类Base1 Base2的 info , 所以给父类Base1 Base2 都加上 virtual 虚继承 就去掉了二义性
cout << "main1.info="<< main1.info << endl;
exit(0);
}
C++ 重载运算符和重载函数
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
C++ 中的函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
// 输出整数
pd.print(5);
// 输出浮点数
pd.print(500.263);
// 输出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
C++ 中的运算符重载
您可以重定义或重载大部分 C++ 内置的运算符。这样,您就能使用自定义类型的运算符。
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
Box operator+(const Box&);
声明加法运算符用于把两个 Box 对象相加,返回最终的 Box 对象。
Box operator+(const Box&, const Box&);
下面的实例使用成员函数演示了运算符重载的概念。对象作为参数进行传递,对象的属性使用 this 运算符进行访问。
#include <iostream>
using namespace std;
class Box{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b){
Box box;
box.length=this->length+b.length;
box.breadth=this->breadth+b.breadth;
box.height=this->height+b.height;
return box;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 程序的主函数
int main( ){
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double volume = 0.0; // 把体积存储在该变量中
// Box1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box1 的体积
volume = Box1.getVolume();
cout << "Box1 Volume= " << volume <<endl;
// Box2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box2 的体积
volume = Box2.getVolume();
cout << "Box2 Volume =" << volume <<endl;
// 把两个对象相加,得到 Box3
//对 +运算符 进行重载
Box3 = Box1 + Box2;
// Box3 的体积
volume = Box3.getVolume();
cout << "Box3 Volume=" << volume <<endl;
return 0;
}
可重载运算符/不可重载运算符
下面是可重载的运算符列表:
双目算术运算符 | + (加),-(减),*(乘),/(除),% (取模) |
关系运算符 | ==(等于),!= (不等于),< (小于),> (大于),<=(小于等于),>=(大于等于) |
逻辑运算符 | ||(逻辑或),&&(逻辑与),!(逻辑非) |
单目运算符 | + (正),-(负),*(指针),&(取地址) |
自增自减运算符 | ++(自增),--(自减) |
位运算符 | | (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移) |
赋值运算符 | =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>= |
空间申请与释放 | new, delete, new[ ] , delete[] |
其他运算符 | ()(函数调用),->(成员访问),,(逗号),[](下标) |
下面是不可重载的运算符列表:
- .:成员访问运算符
- .*, ->*:成员指针访问运算符
- :::域运算符
- sizeof:长度运算符
- ?::条件运算符
- #: 预处理符号
运算符重载实例
1. 一元运算符重载
一元运算符只对一个操作数进行操作,下面是一元运算符的实例:
一元减运算符,即负号( - )
逻辑非运算符( ! )
一元运算符通常出现在它们所操作的对象的左边,比如 !obj、-obj 和 ++obj,但有时它们也可以作为后缀,比如 obj++ 或 obj--。
下面的实例演示了如何重载一元减运算符( - )
#include <iostream>
using namespace std;
class Distance{
private:
int feet; // 0 到无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance(){
feet = 0;
inches = 0;
}
Distance(int f, int i){
feet = f;
inches = i;
}
// 显示距离的方法
void displayDistance()
{
cout << "F: " << feet << " I:" << inches <<endl;
}
// 重载负运算符( - )
Distance operator- ()
{
feet = -feet;
inches = -inches;
return Distance(feet, inches);
}
};
int main()
{
Distance D1(11, 10), D2(-5, 11);
-D1; // 取相反数 - 调用 重载负运算符( - )
D1.displayDistance(); // 距离 D1
-D2; // 取相反数
D2.displayDistance(); // 距离 D2
return 0;
}
2. 二元运算符重载 11T1 T2
二元运算符需要两个参数,下面是二元运算符的实例。我们平常使用的加运算符( + )、减运算符( - )、乘运算符( * )和除运算符( / )都属于二元运算符。就像加(+)运算符。
类外+运算符重载案例:
// 类外运算符重载。
#include <iostream>
using namespace std;
class Derry {
private:
int x, y;
// 系统C++源码,大量使用此方式 :x(x), y(y)
public:
Derry(int x, int y) : x(x), y(y) {}
// set get 函数
public:
int getX() {
return this->x;
}
void setX(int x) {
this->x = x;
}
int getY() {
return this->y;
}
void setY(int y) {
this->y = y;
}
};
// 在真实开发过程中,基本上都是写在类的里面的,为什么? 娓娓道来 答:外部是不能获取内部的私有成员的
// 把+重载 运算符重载
Derry operator + (Derry derry1,Derry derry2){
int x = derry1.getX() + derry2.getX();
int y = derry1.getY() + derry2.getY();
Derry derry(x,y);
return derry;
}
int main() {
// 对象1 + 对象2 C++默认不支持的, Java也不支持,Kotlin也不支持
// C++/Kotlin 运算符重载 + 把+重载掉
Derry derry1(1000,2000);
Derry derry2(3000,4000);
Derry derry3 = derry1+derry2; // 重载+前 C++/Java/Kotlin 都会报错
cout << derry3.getX() << " , " << derry3.getY() << endl; //4000 , 6000
}
类里+ -运算符重载案例:
类里+号运算符重载
当前类的this指针所指的对象默认传递进去了,所以只需要传递一个参数 这个参数必须使用常量引用来定义 提高性能
// 2.类里运算符重载。
#include <iostream>
using namespace std;
class Derry {
private:
int x, y;
// 系统C++源码,大量使用此方式 :x(x), y(y)
public:
Derry(int x, int y) : x(x), y(y) {}
// set get 函数
public:
int getX() {
return this->x;
}
void setX(int x) {
this->x = x;
}
int getY() {
return this->y;
}
void setY(int y) {
this->y = y;
}
/*
// +号,运算符重载
//Overloaded 'operator+' must be a unary or binary operator (has 3 parameters)
//原因:当前类的this指针所指的对象默认传递进去了。
Derry operator + (Derry derry1,Derry derry2){
int x = derry1.getX() + derry2.getX();
int y = derry1.getY() + derry2.getY();
Derry derry(x,y);
return derry;
}
*/
/*
// +号,运算符重载
//当前类的this指针所指的对象默认传递进去了,所以只需要传递一个参数就行了
Derry operator + (Derry derry1){
int x = this->x + derry1.getX() ;
int y = this->y + derry1.getY() ;
Derry derry(x,y);
return derry;
}
*/
// +号,运算符重载
// 常量引用:不允许修改,只读模式,性能更快
// const 不允许修改,只读模式,
// & 为了性能的提高,如果没有& 运行+ 构建新的副本,会浪费性能
// 如果增加了 & 引用是给这块内存空间取一个别名而已
//当前类的this指针所指的对象默认传递进去了,所以只需要传递一个参数 这个参数必须使用常量引用来定义 提高性能
Derry operator + (const Derry& derry1){
int x = this->x + derry1.x ;// 我在类的里面,是可以拿私有成员的x
int y = this->y + derry1.y ;// 我在类的里面,是可以拿私有成员的y
Derry derry(x,y);
return derry;
}
// 运算符- 重载
Derry operator - (const Derry& derry1){
int x = this->x - derry1.x ;
int y = this->y - derry1.y ;
Derry derry(x,y);
return derry;
}
};
int main11T2() {
// 对象1 + 对象2 C++默认不支持的, Java也不支持,Kotlin也不支持
// C++/Kotlin 运算符重载 + 把+重载掉
Derry derry1(1000,2000);
Derry derry2(3000,4000);
Derry derry3 = derry1+derry2;
cout << derry3.getX() << " , " << derry3.getY() << endl; //4000 , 6000
Derry derry4 = derry2-derry1;
cout << derry4.getX() << " , " << derry4.getY() << endl; //2000 , 2000
}
3. 关系运算符重载
C++ 语言支持各种关系运算符( < 、 > 、 <= 、 >= 、 == << 等等),它们可用于比较 C++ 内置的数据类型。
重载 输出运算符<<案例:
// 2.类里运算符重载。
#include <iostream>
using namespace std;
class Derry {
private:
int x, y;
// 系统C++源码,大量使用此方式 :x(x), y(y)
public:
Derry(int x, int y) : x(x), y(y) {}
// set get 函数
public:
int getX() {
return this->x;
}
void setX(int x) {
this->x = x;
}
int getY() {
return this->y;
}
void setY(int y) {
this->y = y;
}
//重载 输出运算符<<
// istream 输入 系统的 ostream 输出 系统的
// 重载 输出运算符<< 输出 Derry 对象
friend void operator << (ostream & _START, Derry derry1) {
// 输出换行:<< endl;
_START << "开始输出了 " << "对象derry1.x ="<<derry1.x << " , " << "对象derry1.y ="<< derry1.y << " 输出结束了 " << endl;
}
};
int main11T2() {
// 对象1 + 对象2 C++默认不支持的, Java也不支持,Kotlin也不支持
// C++/Kotlin 运算符重载 + 把+重载掉
Derry derry1(1000,2000);
cout << endl; // 系统的换行
// 自定义 重载 输出运算符<<
// 输出 Derry 对象
cout<< derry1; //开始输出了 对象derry1.x =1000 , 对象derry1.y =2000 输出结束了
cout << endl; // 系统的换行
}
4. 输入/输出运算符重载(很少用到)
5. ++ 和 -- 运算符重载(重要)
递增运算符( ++ )和递减运算符( -- )是 C++ 语言中两个重要的一元运算符。
下面的实例演示了如何重载递增运算符( ++ ),包括前缀和后缀两种用法。
重载前缀递增++运算符 operator ++ ()
重载后缀递增++运算符 operator++( int )注意:int 在 括号内是为了向编译器说明这是一个后缀形式,而不是表示整数。
#include <iostream>
using namespace std;
class Time
{
private:
int hours; // 0 到 23
int minutes; // 0 到 59
public:
// 所需的构造函数
Time(){
hours = 0;
minutes = 0;
}
Time(int h, int m){
hours = h;
minutes = m;
}
// 显示时间的方法
void displayTime()
{
cout << "H: " << hours << " M:" << minutes <<endl;
}
// 重载前缀递增运算符( ++ )
Time operator++ ()
{
++minutes; // 变量minutes加 1
if(minutes >= 60)
{
++hours; // 变量hours加 1
minutes -= 60; // 把变量minutes=minutes-60 =0
}
return Time(hours, minutes);
}
// 重载后缀递增运算符( ++ )
Time operator++( int ){
// 保存原始值
Time T(hours, minutes);
// 变量minutes加 1
++minutes;
if(minutes >= 60)
{
++hours; // 变量hours加 1
minutes -= 60; // 把变量minutes=minutes-60 =0
}
// 返回旧的原始值
return T;
}
};
int mainchongzai3()
{
Time T1(11, 59);
T1.displayTime(); //H: 11 M:59
++T1;//重载前缀递增运算符( ++ ) // T1 加 1
T1.displayTime(); // 显示 T1 //H: 12 M:0
++T1; // T1 再加 1
T1.displayTime(); // 显示 T1 //H: 12 M:1
Time T2(10,40);
T2.displayTime(); //H: 10 M:40
T2++; // 重载后缀递增运算符( ++ ) // T2 加 1
T2.displayTime(); // 显示 T2 //H: 10 M:41
T2++; // T2 再加 1
T2.displayTime(); // 显示 T2 //H: 10 M:42
return 0;
}
#include <iostream>
using namespace std;
/*
* 实例 (++ 重载)
* */
class Check
{
private:
int i;
public:
Check(): i(0) { }
//前置递增就是增加当前对象的变量i的值,并且返回当前对象heck temp
Check operator ++ ()
{
Check temp;
temp.i = ++i;
return temp;
}
//后置递增就是增加当前对象的i的值,并且返回增加i之前的该对象Check temp
Check operator ++ (int)
{
Check temp;
temp.i = i++;
return temp;
}
void Display()
{ cout << "i = "<< i <<endl; }
};
int mainchongzai4()
{
Check obj, obj1;
obj.Display(); // i = 0
obj1.Display(); //i = 0
// 调用运算符++函数(operator ++ ()),然后将 obj 的值赋给 obj1
obj1 = ++obj;
obj.Display(); //i = 1
obj1.Display(); //i = 1
// 将 obj 赋值给 obj1, 然后再调用运算符函数 (operator ++ (int))
obj1 = obj++;
obj.Display(); //i = 2
obj1.Display(); //i = 1
return 0;
}
#include <iostream>
using namespace std;
/*
实例 (-- 重载)
* */
class Check
{
public:
int i;
public:
Check(): i(3) { }
Check operator -- ()
{
Check temp;
temp.i = --i;
return temp;
}
// 括号中插入 int 表示后缀
Check operator -- (int)
{
Check temp;
temp.i = i--;
return temp;
}
void Display()
{ cout << "i = "<< i <<endl; }
};
int main()
{
Check obj,obj1;
obj.i=3;
obj1.i=3;
obj.Display(); //i = 3
obj1.Display(); //i = 3
// 调用运算符函数,然后将 obj 的值赋给 obj1
obj1 = --obj;
obj.Display(); //i = 2
obj1.Display(); //i = 2
// 将 obj 赋值给 obj1, 然后再调用运算符函数
obj1 = obj--;
obj.Display(); //i = 1
obj1.Display(); //i = 2
return 0;
}
对象++ 重载
++对象 重载 11T2
// 2.类里运算符重载。
#include <iostream>
using namespace std;
class Derry {
private:
int x, y;
// 系统C++源码,大量使用此方式 :x(x), y(y)
public:
Derry(int x, int y) : x(x), y(y) {}
// set get 函数
public:
int getX() {
return this->x;
}
void setX(int x) {
this->x = x;
}
int getY() {
return this->y;
}
void setY(int y) {
this->y = y;
}
// ++ 运算符 重载
// 对象++ 重载
void operator ++ (int){
this->x=this->x+1;
this->y=this->y+1;
}
// ++对象 重载
void operator ++(){
this->x=this->x+1;
this->y=this->y+1;
}
};
int main11T2() {
// 对象1 + 对象2 C++默认不支持的, Java也不支持,Kotlin也不支持
// C++/Kotlin 运算符重载 + 把+重载掉
Derry derry5(1, 2);
cout << derry5.getX() << " , " << derry5.getY() << endl; //1 , 2
derry5++;
cout << derry5.getX() << " , " << derry5.getY() << endl;// 2 , 3
Derry derry6(4, 4);
cout << derry6.getX() << " , " << derry6.getY() << endl; //4 , 4
++derry6;
cout << derry6.getX() << " , " << derry6.getY() << endl; //5 , 5
}
6. 函数调用运算符 () 重载(重要)
函数调用运算符 () 可以被重载用于类的对象。当重载 () 时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance(){
feet = 0;
inches = 0;
}
Distance(int f, int i){
feet = f;
inches = i;
}
// 重载函数调用运算符()
Distance operator()(int a,int b){
Distance D;
D.feet=a;
D.inches=b;
return D;
};
// 重载函数调用运算符()
Distance operator()(int a, int b, int c){
Distance D;
D.feet = a+c ;
D.inches = b +c;
return D;
};
// 显示距离的方法
void displayDistance()
{
cout << "F: " << feet << " I:" << inches <<endl;
}
};
int main()
{
Distance D1;
D1.displayDistance(); // F: 0 I:0
Distance D2(11,10);
D2.displayDistance(); //F: 11 I:10
Distance D3;
D3.displayDistance();//F: 0 I:0
D3=D3(9,8);
D3.displayDistance(); //F: 9 I:8
Distance D4(7,6);//调用构造函数 Distance(int f, int i)
D4.displayDistance(); //F: 7 I:6
D4=D4(5,4); //执行 重载函数运算符 operator()(int a,int b)
D4.displayDistance(); //F: 5 I:4
D4=D4(3,2,1); //执行 重载函数运算符 operator()(int a, int b, int c)
D4.displayDistance(); //F: 4 I:3
}
谓词 == 仿函数 本质上就是重载了(0/1/2/3个参数)运算符
谓词分:空谓词(没有参数) 一元谓词(1个参数) 二元谓词(2个参数) 三元谓词(3个参数)
为什么叫仿函数?在使用中,谓词非常像函数的调用 即叫 仿函数
14T3
#include <iostream>
#include <set> // STL包
#include <algorithm> // 算法包
using namespace std;
// 谓词前戏
// 谓词 == 仿函数 本质上就是重载了(0/1/2/3个参数)运算符
//谓词分:空谓词(没有参数) 一元谓词(1个参数) 二元谓词(2个参数) 三元谓词(3个参数)
// 为什么叫仿函数?在使用中,谓词非常像函数的调用 即叫 仿函数
class ComPareObject {
public:
todo 本质上就是重载了()运算符
这里的仿函数 ,是个空谓词(没有参数)
void operator()() {
cout << "调用 仿函数调用 空谓词(没有参数)" << endl;
}
void operator()(int x){
for (int i = 0; i < x; i++)
cout << "调用 仿函数调用 一元谓词(1个参数), i="<<i<< endl;
}
这里的仿函数 ,是个 二元谓词(2个参数)
int operator()(int number,int number2){
cout << "仿函数调用 二元谓词(2个参数)" << endl;
return number+number;
}
// ComPareObject类内 普通函数
void innerMethod(){
cout << "ComPareObject类内 普通函数的调用" << endl;
}
}com2;
// ComPareObject类外 普通函数
void fun2() {
cout << "ComPareObject类外 普通函数的调用" << endl;
}
int main14T3() { // 14T3
ComPareObject comPare;
//todo 第一种调用方法
// 仿函数的调用 (空谓词(没有参数))
comPare(); //todo 调用 仿函数调用 空谓词(没有参数)
// 仿函数的调用 (一元谓词(1个参数))
comPare(2); //todo 调用 仿函数调用 一元谓词(1个参数), i=0 i=1
// 仿函数的调用(二元谓词(2个参数))
int i= comPare(1,2);
cout << "调用 仿函数的调用(二元谓词(2个参数)):i= " << i<<endl; //todo 调用 仿函数的调用(二元谓词(2个参数)):i= 2
cout << endl;
//todo 第二种调用方法
comPare.operator()(); //todo 调用 仿函数调用 空谓词(没有参数)
comPare.operator()(3); //todo 调用 仿函数调用 一元谓词(1个参数), i=0 i=1 i=2
int i2 = comPare.operator()(2,3); //todo 仿函数调用 二元谓词(2个参数)
cout << "调用 仿函数的调用(二元谓词(2个参数)):i2 = " << i2<<endl; //调用 仿函数的调用(二元谓词(2个参数)):i2 = 4
cout << endl;
//todo 第三种调用方法
//定义一个结构体别名 com1
struct ComPareObject com1;
com1(); // 调用 仿函数调用 空谓词(没有参数
com1(4); //todo 调用 仿函数调用 一元谓词(1个参数), i=0 i=1 i=2 i=3
int i3 = com1(3,3);
cout << "调用 仿函数的调用(二元谓词(2个参数)):i3 = " << i3 <<endl; // 调用 仿函数的调用(二元谓词(2个参数)):i3 = 6
cout << endl;
//todo 第四种调用方法
com2(); // 调用 仿函数调用 空谓词(没有参数)
com2(5); //todo 调用 仿函数调用 一元谓词(1个参数), i=0 i=1 i=2 i=3 i=4
int i4 = com2(4,4);
cout << "调用 仿函数的调用(二元谓词(2个参数)):i4 = " << i4 <<endl; //调用 仿函数的调用(二元谓词(2个参数)):i4 = 8
cout << endl;
//todo 第五种调用方法
// ComPareObject;
comPare.innerMethod();// ComPareObject类内 普通函数的调用
// ComPareObject类外 普通函数调用
fun2(); // ComPareObject类外 普通函数的调用
return 0;
};
在源码里使用我们自定义仿函数(重载了()运算符) 15T3
// TODO for_each 算法包里面的遍历
// 算法包
#include <iostream>
#include <vector> // stl包
#include <algorithm> // 算法包
using namespace std;
// 自定义仿函数
class __F{
public:
// 仿函数 一元谓词(1个参数)
void operator()(int __first){
cout << "自定义一元谓词 __first=" << __first << endl;
}
};
int main() { //15T3
vector<int> vectorVar;
vectorVar.insert(vectorVar.begin(), 10000);
vectorVar.insert(vectorVar.begin(), 20000);
vectorVar.insert(vectorVar.begin(), 30000);
vectorVar.insert(vectorVar.begin(), 40000);
vectorVar.insert(vectorVar.begin(), 50000);
vectorVar.insert(vectorVar.begin(), 60000);
/**
* for_each(_InputIterator __first, _InputIterator __last, _Function __f)
* todo 这里比较特殊 第三个参数 我们传递的实参是__F对象 f = __f
追踪进源码发现 调用了 __f(*__first) 调用的是对象f的仿函数 一元谓词
倒推: __f(*__first)
= f(*(vectorVar.begin()))
=f(10000)
= 调用的是对象f的仿函数 一元谓词
*/
__F f;
for_each(vectorVar.begin(),vectorVar.end(),f);
/*自定义一元谓词 __first=60000
自定义一元谓词 __first=50000
自定义一元谓词 __first=40000
自定义一元谓词 __first=30000
自定义一元谓词 __first=20000
自定义一元谓词 __first=10000
*/
return 0;
}
7. 下标运算符 [] 重载 11T3
// []括号运算符重载。
// 数组 系统源码把此括号[i]给重载, 系统重载后的样子 *(arr+i)
#include <iostream>
using namespace std;
// 写一个小容器,模拟容器
class ArrayClass{
private:
// C++ 默认都是系统值 size 系统值 -13275
int size = 0; // 大小 开发过程中,给size赋值默认值,不然会出现问题,后患无穷的问题
int * arrayValue; // 数组存放 int 类型的很多值
public:
void set(int index,int value){
arrayValue[index]=value; // []目前不是我的 系统的
size+=1;
}
int getSize(){ // size成员的目标:是为了数组循环可以遍历
return this->size;
}
// 运算符重载 [index]
int operator [](int index){
return this->arrayValue[index]; // 系统的[]
}
};
// 输出容器的内容
void printfArrayClass(ArrayClass arrayClass){
cout << "数组的长度 arrayClass.getSize()="<<arrayClass.getSize() << endl;
for (int i=0;i<arrayClass.getSize();++i){
cout << arrayClass[i] << endl; // []是我们自己的 重载符号[]
}
}
int main11T3() {
// 能在栈区的,尽量在栈区
// 1.代码量少
// 2.避免麻烦
// 3.怕有问题
// 4.栈区的回收,不是你负责的,责任推卸
ArrayClass arrayClass; // 栈区 实例出来的对象,是在堆区了
arrayClass.set(0,1000);
arrayClass.set(1,2000);
arrayClass.set(2,3000);
arrayClass.set(3, 4000);
arrayClass.set(4, 5000);
printfArrayClass(arrayClass);
return 0;
}
8. 类成员访问运算符 -> 重载 ???????
C++ 多态
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。
虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数(此虚函数有实现体)。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
在基类中,给某个函数添加virtual关键字,虚拟化这个函数。让子类继承父类时 重写这个虚函数,
这样在子类中才会在重写的虚函数中输出自己的逻辑内容,才会避免出现二义性。
如果子类不重写这个虚函数,在子类中调用该虚函数时,就会输出基类的业务逻辑内容。
class BaseClass{
//虚函数
virtual int area(){
// 输出基类的业务逻辑内容
};
}
class ZiClass:public BaseClass{
public:
int area(){
// 输出子类的业务逻辑内容
};
}
C++默认关闭多态,怎么开启多态? 使用虚函数 在父类上给函数增加 virtual 虚拟化 关键字
/**
抛开 C++ 抛开Java 等等,请问什么是多态?
父类的引用指向子类的对象,同一个方法有不同的实现,重写(动态多态)和 重载(静态多态)重写(动态多态): 子类重写了父类的函数. 程序在运行期间才能确定调用哪个类的函数
重载(静态多态): 编译期已经决定,调用哪个函数了,这个就属于静态多态的范畴
*/
基类有虚函数 子类重写(动态多态)基类的虚函数案例:12T3
// 3.多态(虚函数)。 动态多态(程序的角度上:程序在运行期间才能确定调用哪个类的函数 == 动态多态的范畴)
// Java语言默认支持多态
// C++默认关闭多态,怎么开启多态? 使用虚函数 在父类上给函数增加 virtual 虚拟化 关键字
/**
抛开 C++ 抛开Java 等等,请问什么是多态?
父类的引用指向子类的对象,同一个方法有不同的实现,重写(动态多态)和 重载(静态多态)
重写(动态多态): 子类重写了父类的函数. 程序在运行期间才能确定调用哪个类的函数
重载(静态多态): 编译期已经决定,调用哪个函数了,这个就属于静态多态的范畴
*/
#include <iostream>
using namespace std;
// Android标准
class BaseActivity {
public:
// todo 在基类中,给某个函数添加virtual关键字,虚拟化这个函数。让子类继承父类时 重写这个虚函数, 避免出现二义性
// 虚函数
virtual void onStart() {
cout << "BaseActivity onStart" << endl;
}
};
class HomeActivity : public BaseActivity {
public:
void onStart() { // todo 重写父类的虚函数onStart() (动态多态)
cout << "HomeActivity onStart" << endl;
}
};
class LoginActivity : public BaseActivity {
public:
void onStart() { //todo 重写父类的虚函数 onStart() (动态多态)
cout << "LoginActivity onStart" << endl;
}
};
//void startToActivity1(BaseActivity baseActivity){
// baseActivity.onStart();
//}
//void startToActivity2(BaseActivity& baseActivity){
// baseActivity.onStart();
//}
// 在此函数 体系多态,例如:你传入HomeActivity,我就帮你运行HomeActivity
void startToActivity3(BaseActivity* baseActivity){
baseActivity->onStart();
}
int main12T3() {
// TODO 第一版本
HomeActivity* homeActivity = new HomeActivity();
LoginActivity* loginActivity = new LoginActivity();
startToActivity3(homeActivity);//HomeActivity onStart
startToActivity3(loginActivity); //LoginActivity onStart
if (homeActivity && loginActivity) delete homeActivity; delete loginActivity; //需要回收
cout << endl;
// TODO 第二个版本
BaseActivity * baseActivity1 = new HomeActivity();
BaseActivity * baseActivity2 = new LoginActivity();
startToActivity3(baseActivity1); //HomeActivity onStart
startToActivity3(baseActivity2); //LoginActivity onStart
//TODO 如果去掉了 父类BaseActivity去掉了 virtual关键字 版本一二就会输出 BaseActivity onStart BaseActivity onStart 出现二义性
return 0;
}
重载(静态多态)案例:12T4
// 静态多态 (编译期已经决定,调用哪个函数了,这个就属于静态多态的范畴) 重载(静态多态)
#include <iostream>
using namespace std;
void add(int number1, int number2) {
cout <<"int+int="<< number1 + number2 << endl;
}
void add(float number1, float number2) {
cout <<"float+float="<< number1 + number2 << endl;
}
void add(double number1, double number2) {
cout <<"double+double="<< number1 + number2 << endl;
}
int main12T4() {
add(10000, 10000);//静态多态
add(1.9f, 2.8f); //静态多态
add(545.4, 654.54); //静态多态
}
纯虚函数
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函
纯虚函数(抽象函数 没有实现体{})(相当于Java的抽象类)。
纯虚函数 就是在基类中让某个虚函数=0告诉编译器,该虚函数没有主体 即为纯虚函数 。
子类继承父类,必须要实现父类的纯虚函数,添加自己的实现体内容。
如果子类没有实现父类的纯虚函数,子类就是抽象类。
class BaseClass{
//纯虚函数
virtual int area() = 0;
}
class ZiClass:public BaseClass{
public:
int area(){
// 输出子类的业务逻辑内容
}
}
虚函数 纯虚函数 的应用案例: 12T5
#include <iostream>
using namespace std;
// 5.纯虚函数(抽象函数 没有实现体{})(相当于Java的抽象类)
// 抽象类 的函数 分为:1.普通函数, 2.纯虚函数(抽象函数 没有实现体{})
class BaseActivity{
private:
void setContentView(string layoutResID) {
cout << "XmlResourceParser解析布局文件信息... 反射" << endl;
}
public:
// 1.普通函数
void onCreate() {
setContentView(getLayoutID());
initView();
initData();
initListener();
}
// 2.纯虚函数(抽象函数 没有实现体{})
// virtual string getLayoutID(); // 虚函数
virtual string getLayoutID() = 0; // 纯虚函数
virtual void initView() = 0;// 纯虚函数
virtual void initData() = 0;// 纯虚函数
virtual void initListener() = 0;// 纯虚函数
// 虚函数
virtual void onStart() {
cout << "BaseActivity onStart" << endl;
}
};
// 子类继承父类,必须要实现父类的纯虚函数(如果子类没有实现父类的纯虚函数,子类就是抽象类)
// 子类 MainActivity
// MainActivity如果没有实现父类的纯虚函数,自己就相当于 抽象类了
class MainActivity : public BaseActivity {
public:
void onStart() {
cout << "子类MainActivity重写了父类的虚函数onStart()" << endl;
}
string getLayoutID() {
cout << "子类MainActivity实现了父类的纯虚函数getLayoutID()" << endl;
return "R.layout.activity_main";
}
void initView() {
// Button btLogin = findViewById(R.id.bt_login);
// Button btRegister = findViewById(R.id.bt_register);
// TextView tvInfo = findViewById(R.id.tv_info);
// ... 省略
cout << "子类MainActivity实现了父类的纯虚函数initView()" << endl;
}
void initData() {
// tvInfo.setText("info...");
// ... 省略
cout << "子类MainActivity实现了父类的纯虚函数initView()" << endl;
}
void initListener() {
/*btLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 点击做事情
}
});*/
// ... 省略
cout << "子类MainActivity实现了父类的纯虚函数initListener()" << endl;
}
};
// 子类 HomeActivity
// HomeActivity如果没有重写父类的纯虚函数,自己就相当于 抽象类了
class HomeActivity : public BaseActivity {
public:
string getLayoutID() {
cout << "子类HomeActivity实现了父类的纯虚函数getLayoutID()" << endl;
return "R.layout.activity_home";
}
void initView() {
// Button btLogin = findViewById(R.id.bt_login);
// Button btRegister = findViewById(R.id.bt_register);
// TextView tvInfo = findViewById(R.id.tv_info);
// ... 省略
cout << "子类HomeActivity实现了父类的纯虚函数initView()" << endl;
}
void initData() {
// tvInfo.setText("info...");
// ... 省略
cout << "子类HomeActivity实现了父类的纯虚函数initView()" << endl;
}
void initListener() {
/*btLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 点击做事情
}
});*/
// ... 省略
cout << "子类HomeActivity实现了父类的纯虚函数initListener()" << endl;
}
};
// 子类 LoginActivity
// LoginActivity没有重新父类的所有纯虚函数,自己就是 抽象类了
class LoginActivity : public BaseActivity {
public:
string getLayoutID() {
cout << "子类LoginActivity实现了父类的纯虚函数getLayoutID()" << endl;
return "R.layout.activity_login";
}
void initView() {
// Button btLogin = findViewById(R.id.bt_login);
// Button btRegister = findViewById(R.id.bt_register);
// TextView tvInfo = findViewById(R.id.tv_info);
// ... 省略
cout << "子类LoginActivity实现了父类的纯虚函数initView()" << endl;
}
};
int main() {
// MainActivity重新了父类所有的纯虚函数,可以实例化初始化
MainActivity mainActivity;
mainActivity.onStart();
mainActivity.getLayoutID();
mainActivity.initView();
mainActivity.initData();
mainActivity.initListener();
/*
子类MainActivity重写了父类的虚函数onStart()
子类MainActivity实现了父类的纯虚函数getLayoutID()
子类MainActivity实现了父类的纯虚函数initView()
子类MainActivity实现了父类的纯虚函数initView()
子类MainActivity实现了父类的纯虚函数initListener()
*/
// HomeActivity重新了父类所有的纯虚函数,可以实例化初始化
/* HomeActivity* homeActivity = new HomeActivity();
homeActivity->getLayoutID();
homeActivity->initView();
homeActivity->initData();
homeActivity->initListener();
if(homeActivity ) delete homeActivity; //需要回收
*/
HomeActivity homeActivity ;
homeActivity.getLayoutID();
homeActivity.initView();
homeActivity.initData();
homeActivity.initListener();
/*
子类HomeActivity实现了父类的纯虚函数getLayoutID()
子类HomeActivity实现了父类的纯虚函数initView()
子类HomeActivity实现了父类的纯虚函数initView()
子类HomeActivity实现了父类的纯虚函数initListener()
*/
//如果子类没有实现父类的所有的纯虚函数,自己就相当于 抽象类了 不能实例化
//LoginActivity loginActivity;
return 0;
}
全纯虚函数(相当于Java版接口)
此类所有的函数,都是纯虚函数 都加关键字virtual 都=0
class ISudent_DB {
virtual void insertStudent(Student4 student) = 0;
virtual void deleteStudent(int _id) = 0;
virtual void updateStudent(int _id, Student4 student) = 0;
virtual Student4 queryByStudent(Student4 student) = 0;
};
全纯虚函数(相当于Java版接口)案例: 12T6
// 全纯虚函数(相当于Java版接口)
#include <iostream>
using namespace std;
class Student4 {
private:
int _id=0;
string name;
int age=0;
public:
void setId(int id) {this->_id = id;}
int getId() {return this->_id;}
string getName() {return this->name;}
int getAge() {return this->age;}
public:
Student4() {}
Student4(int id, string name, int age) : _id(id), name(name), age(age) {
}
};
// 此类所有的函数 ,都是纯虚函数,就相当于 Java的接口了
class ISudent_DB {
virtual void insertStudent(Student4 student) = 0;
virtual void deleteStudent(int _id) = 0;
virtual void updateStudent(int _id, Student4 student) = 0;
virtual Student4 queryByStudent(Student4 student) = 0;
};
// Java的实现类
class Student_DBImpl1 : public ISudent_DB {
public:
void insertStudent(Student4 student) override {
// 插入操作,省略代码...
cout << "子类Student_DBImpl1实现了父类的纯虚函数insertStudent(Student student)" << endl;
}
void deleteStudent(int _id) override {
// 删除操作,省略代码...
}
void updateStudent(int _id, Student4 student) override {
// 更新操作,省略代码...
}
Student4 queryByStudent(Student4 student) override {
// 查询操作,省略代码...
return Student4();
}
};
// Java的实现类
class Student_DBImpl2 : public ISudent_DB {
public:
void insertStudent(Student4 student) {
// 插入操作,省略代码...
}
void deleteStudent(int _id) {
// 删除操作,省略代码...
}
void updateStudent(int _id, Student4 student) {
// 更新操作,省略代码...
}
Student4 queryByStudent(Student4 student) {
// 查询操作,省略代码...
cout << "子类Student_DBImpl2实现了父类的纯虚函数queryByStudent(Student student)" <<student.getId()<<" "<<student.getName()<<" "<<student.getAge()<< endl;
}
};
// Java的实现类
class Student_DBImpl3 : public ISudent_DB {
public:
void insertStudent(Student4 student) {
// 插入操作,省略代码...
}
void deleteStudent(int _id) {
// 删除操作,省略代码...
}
void updateStudent(int _id, Student4 student) {
student.setId(_id);
// 更新操作,省略代码...
cout << "子类Student_DBImpl3实现了父类的纯虚函数updateStudent(Student student)" <<student.getId()<<" "<<student.getName()<<" "<<student.getAge()<< endl;
}
Student4 queryByStudent(Student4 student) {
// 查询操作,省略代码...
}
};
int main12T6() {
Student_DBImpl1 studentDbImpl1;
Student_DBImpl2 studentDbImpl2;
Student_DBImpl3 studentDbImpl3;
Student4 student(9527,"derry",37);
studentDbImpl1.insertStudent(student); // 子类Student_DBImpl1实现了父类的纯虚函数insertStudent(Student student)
studentDbImpl2.queryByStudent(student); //子类Student_DBImpl2实现了父类的纯虚函数queryByStudent(Student student)9527 derry 37
studentDbImpl3.updateStudent(1111,student); //子类Student_DBImpl3实现了父类的纯虚函数updateStudent(Student student)1111 derry 37
cout << "Success" << endl;
return 0;
}
使用全纯虚函数(相当于Java版接口) 实现C++的回调的案例: 12T7
// 回调。 Java的登录 简单的 接口
#include <iostream>
using namespace std;
// 登录成功的Bean
class SuccessBean {
public:
string username;
string userpwd;
SuccessBean( string username, string userpwd) : username(username),userpwd(userpwd) {}
};
// 登录响应的接口 成功,错误
class ILoginResponse{
public:
// 登录成功
virtual void loginSuccess(int code, string message, SuccessBean successBean) = 0;
// 登录失败
virtual void loginError(int code, string message) = 0;
};
// 写一个实现类,继承接口
// 接口实现类 重写纯虚函数
class ILoginResponseImpl : public ILoginResponse {
public:
// 登录成功
void loginSuccess(int code, string message, SuccessBean successBean) override {
cout << "恭喜登录成功 " << "code:" << code << " message:" << message
<< "successBean:" << successBean.username << "," << successBean.userpwd << endl;
}
// 登录失败
void loginError(int code, string message) override {
cout << " 登录失败 " << "code:" << code << " message:" << message << endl;
}
};
// 登录的API动作
// 使用&引用 提高性能
void loginAction(string name,string pwd,ILoginResponse& loginResponse){
if (name.empty() || pwd.empty()) {
cout << "用户名或密码为空!" << endl;
return;
}
if ("Derry" == name && "123" == pwd) {
loginResponse.loginSuccess(200,"登录成功", SuccessBean(name, pwd));
}else{
loginResponse.loginError(404, "登录错误,用户名或密码错误...");
}
}
int main12T7() {
string name="Derry";
string pwd="123";
ILoginResponseImpl iLoginResponse;
loginAction(name,pwd, iLoginResponse );
return 0;
}
C++ 数据抽象
数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。
数据抽象是一种依赖于接口和实现分离的编程(设计)技术。
C++ 接口(抽象类)
接口描述了类的行为和功能,而不需要完成类的特定实现。
C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的,
class Box
{
public:
// 纯虚函数
virtual double getVolume() = 0;
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
以下这段是转载的,非常经典:
定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
// 虚函数 是在基类中使用关键字 virtual 声明的函数
virtual int area()
{
cout << "Parent class area() " <<endl;
return 0;
}
//= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
virtual int getArea() = 0;
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Rectangle class area () " <<endl;
return (width * height);
}
int getArea() override {
cout << "Rectangle class getArea() " <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Triangle class area () " <<endl;
return (width * height / 2);
}
int getArea() override {
cout << "Triangle class getArea() " <<endl;
return (width * height)/2;
}
};
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
/*
* 由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。
*/
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area(); // Rectangle class area ()
shape->getArea(); //Rectangle class getArea()
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area(); //Triangle class area ()
shape->getArea(); //Triangle class getArea()
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
// 输出对象的面积
cout << "Total Rectangle area: " << Rect.getArea() << endl; // Rectangle class getArea() Total Rectangle area: 35
Triangle Tri;
Tri.setWidth(5);
Tri.setHeight(7);
// 输出对象的面积
cout << "Total Triangle area: " << Tri.getArea() << endl; //Triangle class getArea() Total Triangle area: 17
return 0;
}
C++ 异常处理
异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw。
- throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
- catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
- try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:
try
{
// 保护代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}
抛出异常
您可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
捕获异常
catch 块跟在 try 块后面,用于捕获异常。您可以指定想要捕捉的异常类型,这是由 catch 关键字后的括号内的异常声明决定的。
try
{
// 保护代码
}catch( ExceptionName e )
{
// 处理 ExceptionName 异常的代码
}
#include <iostream>
using namespace std;
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
int main ()
{
int x = 50;
int y = 0;
double z = 0;
try {
z = division(x, y);
cout << z << endl;
}catch (const char* msg) {
cerr << msg << endl;
}
return 0;
}
C++ 标准的异常
C++ 提供了一系列标准的异常,定义在 <exception> 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:
异常 | 描述 |
---|---|
std::exception | 该异常是所有标准 C++ 异常的父类。 |
std::bad_alloc | 该异常可以通过 new 抛出。 |
std::bad_cast | 该异常可以通过 dynamic_cast 抛出。 |
std::bad_typeid | 该异常可以通过 typeid 抛出。 |
std::bad_exception | 这在处理 C++ 程序中无法预期的异常时非常有用。 |
std::logic_error | 理论上可以通过读取代码来检测到的异常。 |
std::domain_error | 当使用了一个无效的数学域时,会抛出该异常。 |
std::invalid_argument | 当使用了无效的参数时,会抛出该异常。 |
std::length_error | 当创建了太长的 std::string 时,会抛出该异常。 |
std::out_of_range | 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator[]()。 |
std::runtime_error | 理论上不可以通过读取代码来检测到的异常。 |
std::overflow_error | 当发生数学上溢时,会抛出该异常。 |
std::range_error | 当尝试存储超出范围的值时,会抛出该异常。 |
std::underflow_error | 当发生数学下溢时,会抛出该异常。 |
定义新的异常
您可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:
what() 是异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因。
#include <iostream>
#include <exception>
using namespace std;
struct MyException : public exception
{
const char * what () const throw ()
{
return "C++ Exception";
}
};
int main()
{
try
{
throw MyException();
}
catch(MyException& e)
{
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
}
catch(std::exception& e)
{
//其他的错误
}
}
MyException caught
C++ Exception
C++ 动态内存
C++ 程序中的内存分为两个部分:
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
很多时候,您无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。
在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。
如果您不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。
new 和 delete 运算符
使用 new 运算符来为任意的数据类型动态分配内存的通用语法:
data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。
new 不只是分配了内存,它还创建了对象。
(malloc() 函数在 C 语言中就出现了,在 C++ 中仍然存在,但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。)
new data-type;
double* pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存
如果自由存储区已被用完,可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针,并采取以下适当的操作:
double* pvalue = NULL;
if( !(pvalue = new double ))
{
cout << "Error: out of memory." <<endl;
exit(1);
}
在任何时候,当您觉得某个已经动态分配内存的变量不再需要使用时,您可以使用 delete 操作符释放它所占用的内存,如下所示:
delete pvalue; // 释放 pvalue 所指向的内存
#include <iostream>
using namespace std;
int main ()
{
double* pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量pvalue请求对内存 同时创建对象
*pvalue = 29494.99; // 在分配的地址存储值
cout << "pvalue=" << pvalue << endl; //pvalue=0x10a13a8
cout << "*pvalue=" << *pvalue << endl; //*pvalue=29495
delete pvalue; // 释放内存
return 0;
}
数组的动态内存分配
假设我们要为一个字符数组(一个有 20 个字符的字符串)分配内存,我们可以使用上面实例中的语法来为数组动态地分配内存,如下所示:
char* pvalue = NULL; // 初始化为 null 的指针
pvalue = new char[20]; // 为变量pvalue 请求内存 同时还创建了数组对象delete [] pvalue; // 删除 pvalue 所指向的数组对象
#include <iostream>
using namespace std;
int main ()
{
// 动态分配,数组长度为 m的数组 array
int *array=new int [20];
//释放数组内存
delete [] array;
return 0;
}
对象的动态内存分配
如果要为一个包含四个 Box 对象的数组分配内存,构造函数将被调用 4 次,同样地,当删除这些对象时,析构函数也将被调用相同的次数(4次)。
#include <iostream>
using namespace std;
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // 删除数组
return 0;
}
调用构造函数!
调用构造函数!
调用构造函数!
调用构造函数!
C++ 命名空间
假设这样一种情况,当一个班上有两个名叫 Zara 的学生时,为了明确区分它们,我们在使用名字之外,不得不使用一些额外的信息,比如他们的家庭住址,或者他们父母的名字等等。
同样的情况也出现在 C++ 应用程序中。例如,您可能会写一个名为 xyz() 的函数,在另一个可用的库中也存在一个相同的函数 xyz()。这样,编译器就无法判断您所使用的是哪一个 xyz() 函数。
因此,引入了命名空间这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。
不同文件夹中可以有相同名字的文件。
定义命名空间
命名空间的定义使用关键字 namespace,后跟命名空间的名称
namespace namespace_name {
// 代码声明
}
为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称。
namespace_name ::code; // code 可以是变量或函数
让我们来看看命名空间如何为变量或函数等实体定义范围:
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
int main ()
{
// 调用第一个命名空间中的函数
first_space::func();
// 调用第二个命名空间中的函数
second_space::func();
return 0;
}
Inside first_space
Inside second_space
using 指令
您可以使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func1(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func2(){
cout << "Inside second_space" << endl;
}
}
using namespace first_space;
using namespace second_space;
int main ()
{
// 调用第一个命名空间中的函数
func1();
// 调用第二个命名空间中的函数
func2();
return 0;
}
Inside first_space
Inside second_space
嵌套的命名空间
命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间,
namespace namespace_name1 {
// 代码声明
namespace namespace_name2 {
// 代码声明
}
}
您可以通过使用 :: 运算符来访问嵌套的命名空间中的成员:
// 访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2;
// 访问 namespace_name1 中的成员
using namespace namespace_name1;
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void funcFirst(){
cout << "Inside first_space funcFirst()" << endl;
}
// 第二个命名空间
namespace second_space{
void funcSecond(){
cout << "Inside second_space funcSecond()" << endl;
}
}
}
// 访问 first_space 中的成员
using namespace first_space;
// 访问 second_space 中的成员
using namespace first_space::second_space;
int main(){
funcFirst(); // 调用第一个命名空间中的函数funcFirst()
funcSecond(); // 调用第二个命名空间中的函数funcSecond()
return 0;
}
Inside first_space funcFirst()
Inside second_space funcSecond()
关于命名空间内变量和函数及全局变量的使用和作用域:
全局变量 a 表达为 ::a,用于当有同名的局部变量时来区别两者。
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
int a = 100;
void funcFirst(){
cout << "Inside first_space funcFirst()" << endl;
}
// 第二个命名空间
namespace second_space{
int a =20;
void funcSecond(){
cout << "Inside second_space funcSecond()" << endl;
}
}
}
int a = 200;//定义一个全局变量a
int main(){
cout <<"first_space::a ="<< first_space::a<< endl; //first_space::a =100
cout <<"first_space::second_space::a="<< first_space::second_space::a << endl; //first_space::second_space::a=20
cout <<"调用 全局变量a = "<< a << endl; //调用全局变量a = 200
cout <<"调用 全局变量 ::a = "<<::a << endl; //调用全局变量 ::a = 200
int a = 30;
cout <<"调用 局部变量a = "<< a << endl; //调用局部变量a = 30
// 全局变量 a 表达为 ::a,用于当有同名的局部变量时来区别两者。
cout <<"调用 全局变量 ::a = "<<::a << endl; //调用全局变量 ::a = 200
// 访问 first_space 中的成员
using namespace first_space;
// 访问 second_space 中的成员
using namespace first_space::second_space;
funcFirst(); // 调用第一个命名空间中的函数funcFirst()
funcSecond(); // 调用第二个命名空间中的函数funcSecond()
return 0;
}
自定义命名空间 嵌套命名空间案例
/*
* 自定义命名空间
* */
#include <iostream>
using namespace std; // C++自己的命名空间 (C# .net 命名空间)
// 自定义命名空间
namespace derry1 {
int age = 33;
char * name = "Derry猛男1";
void show() {
cout << "name:" << name << ", age:" << age << endl;
}
void action() {
cout << "derry1 action" << endl;
}
}
// 自定义命名空间
namespace derry2 {
void action() {
cout << "derry2 action" << endl;
}
}
// 自定义命名空间
namespace derry3 {
namespace derry3Inner {
namespace derry3Inner1 {
namespace derry3Inner2 {
namespace derry3Inner3 {
void out() {
cout << "爱恨情仇人消瘦,悲欢起落人寂寞" << endl;
}
}
}
}
}
}
// 声明自定义的 命名空间
// using namespace derry1; // 全局的
int mainT1() {
int ageValue =derry1::age;
derry1::show();
// 声明自定义的 命名空间
using namespace derry1; //局部的
ageValue = age; // 直接去引出来 ::
show(); // 直接去引出来 ::
// TODO ------ 命名空间derry1 derry2 里面重复的函数action()
using namespace derry2;
// action();//报错 二义性
derry1::action();
derry2::action();
// TODO ------ 命名空间的嵌套
// 第一种方式 先声明命名空间 再使用
using namespace derry3::derry3Inner::derry3Inner1::derry3Inner2::derry3Inner3;
// 再使用
out();
// 第二种方式 直接使用
derry3::derry3Inner::derry3Inner1::derry3Inner2::derry3Inner3::out();
return 0;
}
C++ 模板
模板是创建泛型类或函数的蓝图或公式。
您可以使用模板来定义函数和类.
函数模板
C++的模板函数 非常类似于 Java的泛型
模板函数定义的一般形式如下所示:
template <typename type> ret-type func-name(parameter list)
{
// 函数的主体
}
类模板
template <class type> class class-name {}
C++的模板函数案例:12T8
// 8.模版函数(类似于Java版泛型)。 C++没有泛型 C++的模板函数 非常类似于 Java的泛型
#include <iostream>
using namespace std;
// C++模板函数 == Java的泛型解决此问题
template <typename TT>
void addAction(TT n1,TT n2){
cout << "模板函数:" << n1 + n2 << endl;
}
int main12T8() {
addAction(1, 2);
addAction(10.2f, 20.3f);
addAction(545.34, 324.3);
addAction<string>("AAA", "BBB");
addAction<char>('a','b' );
// addAction(2, 324.3);
// addAction(54, 324.3f);
}
C++ 预处理器(跳过)
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。
我们已经看到,之前所有的实例中都有 #include 指令。这个宏用于把头文件包含到源文件中。
C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令。
#define 预处理
#define 预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:
#define macro-name replacement-text
#include <iostream>
using namespace std;
#define PI 3.14159
int main ()
{
cout << "Value of PI :" << PI << endl; //Value of PI :3.14159
return 0;
}
参数宏
您可以使用 #define 来定义一个带有参数的宏
#include <iostream>
using namespace std;
#define PI 3.14159
#define MIN(a,b) (a<b ? a : b)
int main ()
{
cout << "Value of PI :" << PI << endl;
int i, j;
i = 100;
j = 30;
cout <<"较小的值为:" << MIN(i, j) << endl; //较小的值为:30
return 0;
}
条件编译
有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。
条件预处理器的结构与 if 选择结构很像。
#ifdef NULL
#define NULL 0
#endif
您可以只在调试时进行编译,调试开关可以使用一个宏来实现
#ifdef DEBUG
cerr <<"Variable x = " << x << endl;
#endif
如果在指令 #ifdef DEBUG 之前已经定义了符号常量 DEBUG,则会对程序中的 cerr 语句进行编译。您可以使用 #if 0 语句注释掉程序的一部分
#if 0
不进行编译的代码
#endif
#include <iostream>
using namespace std;
#define DEBUG
#define MIN(a,b) (((a)<(b)) ? a : b)
int main ()
{
int i, j;
i = 100;
j = 30;
cerr <<"Trace: Inside main function 1" << endl; //Trace: Inside main function 1
/*
只在调试时进行编译,调试开关可以使用一个宏来实现
#ifdef DEBUG
cerr <<"Variable x = " << x << endl;
#endif
*/
#ifdef DEBUG
cerr <<"Trace: Inside main function 2" << endl; // //Trace: Inside main function 2
#endif
// 这是注释部分 不执行
/*
#if 0
不进行编译的代码
#endif
*/
#if 0
cout << MKSTR(HELLO C++) << endl; //不执行
#endif
cout <<"The minimum is " << MIN(i, j) << endl; // The minimum is 30
/*
只在调试时进行编译,调试开关可以使用一个宏来实现
#ifdef DEBUG
cerr <<"Variable x = " << x << endl;
#endif
*/
#ifdef DEBUG
cerr <<"Trace: Coming out of main function" << endl; // Trace: Coming out of main function
#endif
}
# 和 ## 运算符
# 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。
# 字符串化的意思,出现在宏定义中的#是把跟在后面的参数转换成一个字符串。
当用作字符串化操作时,# 的主要作用是将宏参数不经扩展地转换成字符串常量。
让我们来看看它是如何工作的。不难理解,C++ 预处理器把下面这行
#define MKSTR( x ) #x
cout << MKSTR(HELLO C++) << endl;
转换成了
cout << "HELLO C++" << endl; //输出 HELLO C++
#include <iostream>
using namespace std;
#define MKSTR( x ) #x
int main ()
{
cout << MKSTR(HELLO C++) << endl; // HELLO C++
return 0;
}
## 运算符用于连接两个令牌。
## 连接符号,把参数连在一起。
当 CONCAT 出现在程序中时,它的参数会被连接起来,并用来取代宏。
例如,程序中 CONCAT(HELLO, C++) 会被替换为 "HELLO C++"
#define CONCAT( x, y ) x ## y
#define concat(a, b) a ## b
int xy = 100;
cout << concat(x, y);
转换成了
cout << xy;//输出 xy = 100
#include <iostream>
using namespace std;
#define MKSTR( x ) #x
#define concat(a, b) a ## b
int main ()
{
cout << MKSTR(HELLO C++) << endl; // HELLO C++
int xy = 100;
cout << concat(x, y)<< endl; // xy=100
return 0;
}
C++ 中的预定义宏
C++ 提供了下表所示的一些预定义宏:
__LINE__ | 这会在程序编译时包含当前行号。 |
__FILE__ | 这会在程序编译时包含当前文件名。 |
__DATE__ | 这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。 |
__TIME__ | 这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。 |
#include <iostream>
using namespace std;
#define MKSTR( x ) #x
#define concat(a, b) a ## b
int main ()
{
cout << MKSTR(HELLO C++) << endl; // HELLO C++
int xy = 100;
cout << concat(x, y)<< endl; // xy=100
cout << "Value of __LINE__ : " << __LINE__ << endl; //Value of __LINE__ : 14
cout << "Value of __FILE__ : " << __FILE__ << endl; //Value of __FILE__ : E:\Android\FMFmyndkStudyPro\xx4_ndk_cpp\runoobcppbase\define3.cpp
cout << "Value of __DATE__ : " << __DATE__ << endl; //Value of __DATE__ : Aug 26 2023
cout << "Value of __TIME__ : " << __TIME__ << endl; //Value of __TIME__ : 15:47:45
return 0;
}
C++ 信号处理
信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。
有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件 <csignal> 中。
信号 | 描述 |
---|---|
SIGABRT | 程序的异常终止,如调用 abort。 |
SIGFPE | 错误的算术运算,比如除以零或导致溢出的操作。 |
SIGILL | 检测非法指令。 |
SIGINT | 程序终止(interrupt)信号。 |
SIGSEGV | 非法访问内存。 |
SIGTERM | 发送到程序的终止请求。 |
signal() 函数
C++ 信号处理库提供了 signal 函数,用来捕获突发事件。以下是 signal() 函数的语法:
void (*signal (int sig, void (*func)(int)))(int);
这个看起来有点费劲,以下语法格式更容易理解:
第一个参数是要设置的信号的标识符,第二个参数是指向信号处理函数的指针。
函数返回值是一个指向先前信号处理函数的指针。如果先前没有设置信号处理函数,则返回值为 SIG_DFL。如果先前设置的信号处理函数为 SIG_IGN,则返回值为 SIG_IGN。
signal(registered signal, signal handler)
使用 signal() 函数捕获 SIGINT 信号。不管您想在程序中捕获什么信号,您都必须使用 signal 函数来注册信号,并将其与信号处理程序相关联。看看下面的实例:
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void signalHandler( int signum )
{
cout << "Interrupt signal (" << signum << ") received.\n";
// 清理并关闭
// 终止程序
exit(signum);
}
int main ()
{
// 注册信号 SIGINT 和信号处理程序
signal(SIGINT, signalHandler);
while(1){
cout << "Going to sleep...." << endl;
sleep(1);
}
return 0;
}
Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.
raise() 函数
使用函数 raise() 生成信号,该函数带有一个整数信号编号作为参数,语法如下:
sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。
int raise (signal sig);
下是我们使用 raise() 函数内部生成信号的实例:
Sleep 函数
功能:执行挂起一段时间,也就是等待一段时间在继续执行
用法:Sleep(时间)
注意:
- (1)Sleep 是区分大小写的,有的编译器是大写,有的是小写。
- Linux 用 #include <unistd.h> 和 sleep(),Windos 用 #include <windows.h> 和 Sleep()。
- (2)Sleep 括号里的时间,在 Windows 下是以毫秒为单位,而 Linux 是以秒为单位。
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void signalHandler( int signum )
{
cout << "Interrupt signal (" << signum << ") received.\n";
// 清理并关闭
// 终止程序
exit(signum);
}
int main(){
int i = 0;
// 注册信号 SIGINT 和信号处理程序
signal(SIGINT, signalHandler);
while(++i) {
cout << "Going to sleep...." << endl;
if( i == 3 ){
raise( SIGINT);
}
sleep(1);
};
return 0;
}
Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.
C++ 多线程
创建线程
创建一个 POSIX 线程:
创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败。
#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)
参数 描述 thread 指向线程标识符指针。 attr 一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。 start_routine 线程运行函数起始地址,一旦线程被创建就会执行。 arg 运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。
终止线程
终止一个 POSIX 线程:
#include <pthread.h>
pthread_exit (status)
向线程传递参数
这个实例演示了如何通过结构体向线程传递多个参数。
#include <iostream> // windows平台 支持Windows
// 必须的头文件
#include <pthread.h> // minGW 默认是没有pthread的 TODO 第四步 导入 pthread 的头文件
using namespace std;
#define NUM_THREADS 5
struct thread_data{
private:
int thread_id;
char* message;
public:
int getThreadId() const {
return this-> thread_id;
}
void setThreadId(int threadId) {
this->thread_id = threadId;
}
char *getMessage() const {
return this->message;
}
void setMessage(char *message) {
this->message = message;
}
};
void* PrintHelloS(void* pVoid){
struct thread_data* threadDataP = static_cast<thread_data *>(pVoid);
thread_data threadData=*threadDataP;
cout << "结构体thread_data threadData.getThreadId()=" << threadData.getThreadId()<< endl ;
cout << "结构体thread_data threadData.getMessage()="<< threadData.getMessage() << endl;
return nullptr;
};
void* PrintHelloS2(void* pVoid){
struct thread_data* threadDataP = static_cast<thread_data *>(pVoid);
cout << "结构体thread_data threadDataP->getThreadId()=" << threadDataP->getThreadId()<< endl ;
cout << "结构体thread_data threadDataP->getMessage()="<< threadDataP->getMessage()<< endl;
return nullptr;
};
int main(){
// 定义线程的 id 变量,多个变量使用数组容器存储
pthread_t threadIdArr[NUM_THREADS];
// 定义一个int类型数组用来保存i的值
int indexes[NUM_THREADS];
//定义一个结构体为 thread_data 类型的数组 threadDataArr 长度 =NUM_THREADS
struct thread_data threadDataArr[NUM_THREADS];
for (int i=0;i<NUM_THREADS;i++){
indexes[i]=i;
threadDataArr[i].setThreadId(indexes[i]);
threadDataArr[i].setMessage("一个字符串:derry");
thread_data threadData = threadDataArr[i];
thread_data* threadDataP = &threadData; // &threadDataArr[i]
pthread_t* ptdP = &threadIdArr[i];
// int ret = pthread_create(ptdP,NULL,PrintHelloS,threadDataP);
int ret = pthread_create(ptdP,NULL,PrintHelloS2,threadDataP);
pthread_t ptd = *ptdP;
pthread_join(ptd, NULL);
if(ret==0){
cout << "pthread_create 创建成功: code=" << ret << endl;
}else{
cout << "pthread_create 创建失败: code=" << ret << endl;
}
};
pthread_exit(NULL);
}
/*结构体thread_data threadData.getThreadId()=0
结构体thread_data threadData.getMessage()=一个字符串:derry
pthread_create 创建成功: code=0
结构体thread_data threadData.getThreadId()=1
结构体thread_data threadData.getMessage()=一个字符串:derry
pthread_create 创建成功: code=0
结构体thread_data threadData.getThreadId()=2
结构体thread_data threadData.getMessage()=一个字符串:derry
pthread_create 创建成功: code=0
结构体thread_data threadData.getThreadId()=3
结构体thread_data threadData.getMessage()=一个字符串:derry
pthread_create 创建成功: code=0
结构体thread_data threadData.getThreadId()=4
结构体thread_data threadData.getMessage()=一个字符串:derry
pthread_create 创建成功: code=0
*/
/*结构体thread_data threadDataP->getThreadId()=0
结构体thread_data threadDataP->getMessage()=一个字符串:derry
pthread_create 创建成功: code=0
结构体thread_data threadDataP->getThreadId()=1
结构体thread_data threadDataP->getMessage()=一个字符串:derry
pthread_create 创建成功: code=0
结构体thread_data threadDataP->getThreadId()=2
结构体thread_data threadDataP->getMessage()=一个字符串:derry
pthread_create 创建成功: code=0
结构体thread_data threadDataP->getThreadId()=3
结构体thread_data threadDataP->getMessage()=一个字符串:derry
pthread_create 创建成功: code=0
结构体thread_data threadDataP->getThreadId()=4
结构体thread_data threadDataP->getMessage()=一个字符串:derry
pthread_create 创建成功: code=0
*/
连接和分离线程
我们可以使用以下两个函数来连接或分离线程:
pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止。当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接。
pthread_join (threadid, status)
pthread_detach (threadid)
这个实例演示了如何使用 pthread_join() 函数来等待线程的完成。
静态函数,友元函数,普通函数,构造函数,析构函数,拷贝构造函数的综合使用案例 10t8
Pig.h
#include <iostream>
using namespace std;
#ifndef PIG_H // 你有没有这个宏PIG_H(Java 宏==常量)
#define PIG_H // 定义这个宏PIG_H
class Pig {
private:
int age;
char *name;
public:
// 静态成员声明
static int id;
// 构造函数的声明系列 无实现体
Pig();
Pig(char *name);
Pig(char *name,int age) ;
// 析构函数 无实现体
virtual ~Pig();
// 拷贝构造函数
Pig(const Pig& obj);
// 普通函数 set get
void setName(char *name);
char *getName();
void setAge(int age);
int getAge() ;
// 常量指针常量 只读
void showPigInfo() const;
// 静态函数的声明 未实现
static void changeTag(int age);
// 不要这样干
// void changeTag(int age);
// 友元函数的声明
friend void changeAges(Pig* pig, int age);
};
#endif // 关闭/结尾宏PIG_H
Pig.cpp
#include "Pig.h"
// 实现构造函数
Pig::Pig() {
cout << "实现默认构造函数Pig()" << endl;
}
Pig::Pig(char *name) {
cout << "实现1个参数构造函数 Pig(char *name)" << endl;
}
Pig::Pig(char *name, int age) {
cout << "实现2个参数构造函数 Pig(char *name, int age)" << endl;
}
// 实现析构函数
Pig::~Pig() {
cout << "实现析构函数~Pig()" << endl;
}
// 实现 拷贝构造函数
Pig::Pig(const Pig &obj) {
cout << "实现拷贝构造函数Pig(const Pig &obj) " << endl;
}
int Pig::getAge() {
return this->age;
}
char * Pig::getName() {
return this->name;
}
void Pig::setAge(int age) {
return; this->age= age;
}
void Pig::setName(char *name) {
this->name = name;
}
void Pig::showPigInfo() const {
}// 常量指针常量 只读
// 实现 静态属性【不需要增加 static关键字】
int Pig::id=878;
// 实现静态函数,【不需要增加 static关键字】
void Pig::changeTag(int age) {
}
// 友元的实现
// 友元特殊:不需要关键字,也不需要 对象::
void changeAges(Pig * pig, int age) {
}