🍉C++学习笔记(一) 🍊C++学习笔记(二) 🍅C++学习笔记(三)

基础语法

if语句

查看if语句基本格式
1
2
3
4
5
6
7
if (condition1) {
// 如果条件1为真,执行这里的代码
} else if (condition2) {
// 如果条件1为假且条件2为真,执行这里的代码
} else {
// 如果条件1和条件2都为假,执行这里的代码
}

for语句

查看for语句基本格式

在 C++ 中,for 循环是一种常用的控制结构,它允许你重复执行一段代码特定次数或者遍历一个序列(如数组、容器或字符串)。以下是 for 循环的基本使用格式:

  • 标准的 for 循环
1
2
3
for (初始化表达式; 循环条件; 步进表达式) {
// 执行的代码块
}
  • 初始化:在循环开始前执行一次,用于初始化循环变量。
  • 循环条件:在每次迭代前检查,如果为真,则执行循环体内的代码。
  • 步进表达式:在每次循环结束时执行,用于更新循环变量。
  • 示例:遍历数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

int main() {
int numbers[] = {1, 2, 3, 4, 5};
int length = sizeof(numbers) / sizeof(numbers[0]);

for (int i = 0; i < length; ++i) {
cout << numbers[i] << " ";
}
cout << endl;

return 0;
}
  • 范围 for 循环(C++11 及更高版本)
1
2
3
for (声明语句 : 序列) {
// 执行的代码块
}
  • 声明语句:声明用于遍历序列的变量。
  • 序列:可以是数组、容器或任何提供了迭代器的对象。
  • 示例:遍历字符串
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <string>
using namespace std;

int main() {
string str = "Hello, World!";
for (char c : str) {
cout << c << endl;
}

return 0;
}
  • 示例:遍历容器
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>
using namespace std;

int main() {
vector<int> vec = {1, 2, 3, 4, 5};
for (int val : vec) {
cout << val << " ";
}
cout << endl;

return 0;
}
  • 注意事项
  • 循环变量的作用域:在标准的 for 循环中,循环变量的作用域仅限于循环内部。在范围 for 循环中,循环变量的作用域可以是循环内部或整个函数。
  • 避免在循环条件中修改循环变量:这可能会导致循环行为异常。
  • 使用范围 for 循环:当你需要遍历容器或数组时,范围 for 循环可以提供更简洁和更安全的语法。

现在,让我们使用 for 循环来输出一个 LaTeX 格式的数学公式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <string>
using namespace std;

int main() {
// LaTeX 格式的数学公式
string latexFormula = "$\\sin(A \\pm B) = \\sin(A)\\cos(B) \\pm \\cos(A)\\sin(B)$";

// 输出 LaTeX 格式的数学公式
cout << latexFormula << endl;

// 使用 for 循环遍历并输出公式中的每个字符
for (char c : latexFormula) {
cout << c;
}
cout << endl;

return 0;
}

在这个例子中,我们首先输出了整个 LaTeX 格式的数学公式,然后使用范围 for 循环遍历字符串中的每个字符并输出它们。这样做是为了演示 for 循环的用法,实际上在控制台输出 LaTeX 代码不会渲染数学公式,你需要在 LaTeX 文档中编译它才能看到格式化后的公式。

递归

一步骤,让我们更清楚地理解递归的过程对于计算斐波那契数列的第n项

递归计算斐波那契数列的第 n 项的过程可以通过以下步骤详细描述:

  1. 基线条件:递归算法需要有基线条件来终止递归。对于斐波那契数列,基线条件是当 n 为 0 或 1 时,函数返回 1,因为斐波那契数列的前两项都是 1。

    1
    2
    if (n == 0) return 1;
    if (n == 1) return 1;
  2. 递归调用:如果 n 大于 1,递归算法将问题分解成两个更小的子问题,即计算斐波那契数列的第 n-1 项和第 n-2 项。

    1
    return Funct(n-1) + Funct(n-2);
  3. 递归展开:以下是递归调用的展开示例,它展示了递归是如何进行分解直到达到基线条件的。

    假设我们要计算 Funct(5)

    • Funct(5) 调用 Funct(4) + Funct(3)
    • Funct(4) 调用 Funct(3) + Funct(2)
    • Funct(3) 调用 Funct(2) + Funct(1)
    • Funct(2) 调用 Funct(1) + Funct(0)
    • Funct(1) 返回 1 (基线条件)
    • Funct(0) 返回 1 (基线条件)
  4. 计算和返回:一旦到达基线条件,递归开始返回计算的值。每个函数调用都等待其子调用的结果来计算最终结果。

    • Funct(2) 返回 1 + 1 = 2
    • Funct(3) 返回 2 + 1 = 3
    • Funct(4) 返回 3 + 2 = 5
    • Funct(5) 返回 5 + 3 = 8
  5. 最终结果:最终,最外层的函数调用 Funct(5) 返回 8,这就是斐波那契数列的第 5 项。

递归过程的关键在于问题分解和子问题的解决,以及将子问题的解合并起来以解决原始问题。然而,递归方法计算斐波那契数列的一个缺点是它进行了大量的重复计算,这会导致性能问题,特别是对于较大的 n 值。为了提高效率,可以采用记忆化递归(使用缓存来存储已计算的值以避免重复计算)或者直接使用迭代方法。

orange,查看递归示例代码方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<iostream>
using namespace std;

/*
6. 斐波拉契数列递归实现的方法如下:
int Funct(int n)
{
if (n == 0) return 1;
if (n == 1) return 1;
retrurn Funct(n - 1) + Funct(n - 2);
}
5,4 5,3
4,3 4,2
3,2 3,1
2,1 2,0
1,1 0,1 2
0,1

请问,如何不使用递归,来实现上述函数?
*/
class Test06 {
public:
int Funct(int n);
};

int Test06::Funct(int n) {
/*

if (n == 0) return 1;
if (n == 1) return 1;
return Funct(n - 1) + Funct(n - 2);
*/

if (n == 0 || n == 1) {
return 1;
}

int a = 1, b = 1, c;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return c;

}

int main() {
Test06 t06;
int n;
cout << "请输入斐波那契数列的项数n:";
cin >> n;
cout << "斐波那契数列的第 " << n << " 项是:" << t06.Funct(n) << endl;
return 0;
}

编译工具

Dev-C++ 5.11 常用快捷键

Dev-C++ 5.11 提供了一系列代码编辑快捷键,以帮助开发者更快速、更高效地进行代码编写。以下是一些常用的代码编辑快捷键:

查看快捷键
  1. 文件操作

    • Ctrl + N:新建源代码文件
    • Ctrl + O:打开文件或工程
    • Ctrl + S:保存当前文件
    • Ctrl + W:关闭当前编辑的文件
  2. 编辑操作

    • Ctrl + X:剪切选中的文本
    • Ctrl + C:复制选中的文本
    • Ctrl + V:粘贴文本
    • Ctrl + Z:撤销操作
    • Ctrl + Y:重做撤销的操作
    • Ctrl + A:全选文本
    • Ctrl + D:删除当前行
    • Ctrl + E:复制当前行并将其粘贴到下一行
  3. 代码注释

    • Ctrl + /:注释或取消注释当前行
    • Ctrl + M:取消注释选中区域
  4. 代码缩进与格式化

    • Ctrl + Shift + A:整体代码缩进对齐
    • Tab:增加制表符缩进
    • Shift + Tab:减少制表符缩进
  5. 编译与运行

    • F9:编译程序
    • Ctrl + F9:编译当前文件
    • F10:运行程序
    • F11:编译并运行程序
  6. 调试操作

    • F8:单步执行
    • F7:下一步(Step Over)
    • F6:停止调试
    • F5:开始调试
  7. 其他

    • Ctrl + Home:跳转到文件开头
    • Ctrl + End:跳转到文件末尾
    • Ctrl + G:跳转到指定行号
    • Ctrl + F:查找文本
    • Ctrl + R:替换文本

这些快捷键可以帮助用户更高效地在Dev-C++环境中进行编程工作。需要注意的是,快捷键的具体组合可能会根据用户的个性化设置而有所不同。

Visual Studio常用快捷键

在同一个项目文件下,多个c++文件中只能有一个main函数,否则系统会提示无法找到文件

查看快捷键

Visual Studio 2017 和 2019 为 C++ 开发提供了许多有用的快捷键,以下是一些常用的快捷键:

  1. 编码和编辑
    • 格式化代码Ctrl + K, Ctrl + F
    • 注释/取消注释Ctrl + K, Ctrl + C (注释) 或 Ctrl + K, Ctrl + U (取消注释)
    • 切换大小写Ctrl + Shift + U
    • 查找Ctrl + F
    • 查找和替换Ctrl + H
    • 在文件中替换Ctrl + Shift + H
    • 转到定义F12Ctrl + 点击
    • 查看引用Alt + F12
    • 导航到Ctrl + G (转到行号)
    • 撤销Ctrl + Z
    • 重做Ctrl + Y
    • 剪切Ctrl + X
    • 复制Ctrl + C
    • 粘贴Ctrl + V
    • 删除行Ctrl + L
    • 删除到行末Ctrl + Delete
    • 插入行Ctrl + Enter
    • 智能提示Ctrl + Space
    • 参数信息Ctrl + Shift + Space
    • 代码片段Tab (在代码片段提示时)
  2. 构建和调试
    • 构建解决方案Ctrl + Shift + B
    • 清理解决方案Ctrl + Shift + E
    • 启动调试F5
    • 逐行执行F10
    • 逐语句执行F11
    • 步出函数Shift + F11
    • 运行到光标处Alt + F9
    • 设置/清除断点F9
    • 条件断点右键点击断点,选择“条件”
    • 断点日志右键点击断点,选择“日志”
    • 监视变量:在监视窗口中添加变量或表达式
  3. 查看和窗口管理
    • 解决方案资源管理器Ctrl + Alt + L
    • 属性窗口Shift + F4
    • 输出窗口Ctrl + Alt + O
    • 错误列表Ctrl + E, E
    • 任务列表Ctrl + Shift + K
    • 全屏F11
    • 拆分窗口Ctrl + Shift + F3
    • 切换拆分窗口Ctrl + F6Ctrl + Tab
    • 预览代码Ctrl + Z (当在解决方案资源管理器中选择文件时)

这些快捷键可以帮助你更高效地在 Visual Studio 中编写和调试 C++ 代码。Visual Studio 还提供了许多其他快捷键和功能,你可以通过查看 Visual Studio 的帮助文档或在网上搜索来发现更多。

第一章

c++是一种编译式的、通用的、大小写敏感的编程语言,完全支持面向对象开发模式

C++对c语言的增强表现在两个方

(1)在原来面向过程的机制上法,增加了c语言对类型的处理
(2)增加了面向对象机制

基本输入/输出流

说明:
1)标准输入: cin>>变量1>>变量2>>……>>变量n
2)标准输出:cout<<表达式1<<表达式2<<……<<表达式n
3) 使用标准出入cin与标准输出cout前,要在程序的最前面包含

1
2
3
4
5
6
#include<iostream>  //包含头文件:输入输出流
using namespace std; //使用命名空间
// 如果不是该行的最后一个元素,输出一个空格作为分隔符,在一行内出入所有数组元素
if (j < j - 1) {
cout << " ";
}

4)换行操作:用语句cout<< endl;或者cout<<”\n”,不输入endl时不会自动换行
5)当连续从键盘上读取数据时,以空格、制表符tab键或enter键作为分隔符。
6)用户自己定义的数据类型,不能直接使用输入“>>”或输出“<<”,必须对输入输出符重载
基本输入输出示例
1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
using namespace std;

int main(){
int a[10];
for(int i = 0;i<10;i++)
cin>>a[i];
for(int i = 0;i<10;i++)
cout<<a[i]<<" ";
cout<< "\n" ;
}

输出输入10个数字

头文件和命名空间

1)头文件

常见头文件 表示
标准输入输出流 iostream
标准文件流 fstream
标准字符串处理函数 string
标准数学函数 cmath

程序员也可以编译自己的文件段,作为头文件反复调用
其他头文件:< iomanip >是C++标准库中的头文件,提供了一些格式化输出和输入的函数和操作符。

主要包括以下几个函数和操作符:

  1. setprecision(n):设置输出浮点数的精度为n位。
  2. setw(n):设置输出域宽为n个字符。
  3. setfill(c):设置输出时,如果域宽大于输出字符数,用字符c填充空余的位置。 eft和right操作符:分别设置输出左对齐和右对齐。
  4. boolalphanoboolalpha操作符:分别设置输出bool值为true/false1/0
  5. hex、oct和dec操作符:分别设置输出16进制、8进制和10进制的整数。
  6. fixed和scientific操作符:分别设置输出为定点数和科学计数法表示的浮点数
  7. istream& ignore (streamsize n = 1, int delim = EOF); 其中,n表示要跳过的字符数,delim是可选参数,表示要跳过的分隔符(默认为 EOF)。ignore() 会返回输入流对象,因此可以进行链式调用。
  8. cin.ignore(numeric_limits< streamsize >::max(), ‘\n’); 其中,numeric_limits<streamsize>::max()表示跳过输入流中的所有字符,直到遇到换行符为止

这些函数和操作符可以使得输出的格式更加规范和美观,提高代码的可读性和可维

2)命名空间
c++中为了避免名字定义冲突,特别引入“命名空间”的作用为了消除同名引起的歧义。在程序中使用标准程序库中的标识符时,要写语句“using namespace std;”.

函数返回值类型

在C++中,函数的返回类型可以有多种,以下是一些常见的返回类型:

  1. 基本数据类型

    • int:整型
    • char:字符型
    • float:单精度浮点型
    • double:双精度浮点型
    • bool:布尔型
  2. 枚举类型

    • 枚举类型是用户定义的类型,它由一组命名的整数常量组成。
  3. 指针类型

    • 例如 int*,指向整型的指针。
  4. 引用类型

    • 例如 int&,对整型的引用。
  5. 数组类型

    • 例如 int[],表示一个整型数组。
  6. 类和结构体

    • 例如 std::string 或自定义的类和结构体类型。
  7. void

    • void 类型表示函数不返回任何值。

强制类型转换符

当不同类型的量进行混合运算时,系统自动进行合理的类型转换,也可以强制类型转换
1)将一种数据类型转换成另一种数据类型

1
2
3
4
5
6
7
static_cast<类型名>(表达式)
说明: static_cast 可以省略不写
oneint2 = static_cast<int>(oneDouble);
oneint2 = int(oneDouble);
oneint2 = (int)oneDouble; //圆括号
oneint2 = oneDouble; //自动类型转换

函数参数默认值

1)c++中,可以在声明函数时为形参制定默认值。调用函数时,从最左侧的参数开始调用

带默认值函数示例
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
void func(int a = 11,int b = 22, int c = 33){
cout<<"a="<<a<< "b="<< b << "c="<< c <<"\n";
}
int main(){
func();
func(55);
func(77,99);
func(8,88,888);
return 0;

}

a=11b=22c=33
a=55b=22c=33
a=77b=99c=33
a=8b=88c=888


2)c++语言规定,定义函数时,为参数赋值,只能按照从后往前的顺序赋值,且在调用是也只省缺后面的连续若干个实参。
例子:
1
2
3
4
void dfi(int a = 2,double b = 3.0); //正确
void dfi(int a ,double b = 3.0); //正确
void dfi(int a = 2,double b); //错误


3)c++语言规定,在函数的调用处只能省缺后面的连续若干个实参,而且所有缺省的实参都必须有默认值。
函数调用示例:
1
2
3
4
5
6
函数声明: void func(int a ,int b = 2,int c = 3)
函数调用:func(1,2233); //正确
函数调用:func(); //错误
函数调用:func(10,20); //正确
函数调用:func(5,,9); //错误


4)c++语言规定,在指定默认值是不仅可以使用常数,还可以用任何有定义的表达式作为参数默认值
函数调用示例:
1
2
3
int Max(int m, int n)
int a,b
void func2(int x,int y = Max(a,b),int t = a-b){……}

5)函数声明没有函数体,表示有这个函数存在;函数定义有函数体,表示函数的具体实现;函数的默认值可以给到其中的一个,但是不能同时都给

引用和函数参数的传递

注意:
1、对象在引用前必须先初始化,声明中符号“&”的位置无关紧要。
2、不能通过常引用(const)去修改其引用变量的值
3、const位于符号*的左侧,表示指针,所指数据为常量,数据不能通过本指针改变,但可以指向其他内存单元
4、const位于符号*的右侧,表示指,针本身为常量,数据可以通过本指针改变,也不可以指向其他内存单元
4、const位于符号*的左右两侧,表示指,针本身以及指针所指数据皆为常量,数据不可以通过本指针改变,也不可以指向其他内存单元

查看教程

普通引用与常引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
列:
//普通引用:
int x = 10;
int &a = x ; // a就是一个普通引用
int & a = x ; //正确
int a& = x ;//正确
//常引用:
const int &b = x ; // b就是一个常引用
a = 20; //则x = 20,b = 20
x = 30; //则a = 30,b = 30
b = 40; //错误
const int *pa2 = &a2; //指针pa2所指的数据是常量
int * const pa2 = &a2; //指针pa2是常量 ,所指数据为变量
const int * const pa2 = &a2; //指针pa2与所指的数据都是常量

引用在函数中的使用

两种方式:传值与传引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 值传递
void SVal(int a,int b){
int tmp;
tmp = a; a = b; b = tmp;
cout <<"SVal()函数中:\t\ta = "<<a<<",b="<<b<<endl;
return;
}
// 引用传递
void SRef(int & a,int &b){
int tmp;
tmp = a; a = b; b = tmp;
cout <<"SRef()函数中:\t\ta = "<<a<<",b="<<b<<endl;
return;

}


内联函数

对需要频繁调用,且代码量少的函数,可以将其定义为内联函数。编译时,编译程序将整个函数体的代码复制到调用该函数的位置。
,如果函数体中有循环语句和switch语句通常不定义为内联函数

可以使用关键字inline将成员函数定义为内联函数。(凡是出现调动该函数的地方,编译程序自动将其装换为该函数的函数体,不再在程序执行时调动该函数,大大提高了效率)。

1
2
3
4
5
定义内联函数的格式如下:
inline返回值类型函数名(形参表){
函数体
}

函数的重载

为同一个函数定义几个版本(参数不同),从而使一个函数名具有多种功能,称为函数重载。

注意事项

满足下面一个条件便可以重载

1.参数表中对用参数类型不一样
2.参数表中参数个数不同;
3.参数表中不同类型参数的次序不同

不能重载的情况

1.两个函数的名字和参数表都是一样的,仅仅是返回值类型不同,则这两个函数不是重载

1
2
3
float add(int ,float);
int add(int ,float); //错误!


2.函数采用引用的不能区分函数,则这两个函数不能重载。
1
2
3
void print(double);
void print(double &); //不可以重载


3.函数调用可能会引发二义性,不能采用函数重载。
1
2
3
int Sum(int a,int b,int c=0);
int Sum(int a,int b);
且函数调用语句为:Sum(1,2) //调用会产生二义性不可重载

4.其他存在赋值兼容的情况

指针和动态内存分配

1.指针:即指针变量,该变量存储的是一个地址,是该指针所指对象的首地址。

1
2
int a = 100,*pa = &a;
int s[10],ps =s; //指针ps指向数组s的首地址

2.动态分配
1
2
3
4
5
6
7
8
9
动态分配内存的一般格式为:
指针名 = new 类型名; //分配
delete 指针名; // 释放

当不再使用这个空间时,必须使用delete释放空间。
若使用new运算符动态分配一个数组,那么释放该数组时,
语句如下:
delete [] 指针


示例
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
int main()
{
double *p; //声明double类型指针
p = new double[3]; //分配3个double类型存储空间
for(int i = 0;i<3; i++)
cin>>*(p+i); //将输入数据存储在指定地址中
for(int j = 0;j<3; j++)
cout<<*(p+j)<<" "; //将地址里的内容输出
delete []p; // 释放空间

}

用string对象处理字符串

查看教程

1.说明:

1)使用string对象,必须#include< string.h >头文件

2)string对象存储的是字符串的首地址,并非字符串本身;sizeof(string)在32位的Dev C++中是4,在64位的Dev C++中是8.
2.对象的操作:
1)string对象,可以使用cin和cout进行输入、输出
2)string对象之间可以相互赋值,也可以用字符串常量和字符串数组的名字对string对象进行赋值。
3)string对象之间可以比较大小按照字典顺序进行大小判定,而且是大小写相关的

1
2
3
bool b;  //声明布尔型变量b
string s1 = "China", s2 = "Ok";
b =s1>s2; //比较两个字符串首字母大小 变量b的值为0

4)string对象,可以使用“+”运算对字符串进行连接

3.string类中常用的成员函数:

函数功能
const char *c_str()const;返回一个指向字符串的指针,用于将string转换为const char*
int size() const;返回当前字符串的大小
int length() const;返回当前字符串的长度
bool empty() const ;判定当前字符串是否为空
size_type find(const char *str,size_type index);返回str在字符串中第一个出现的位置(从index开始查找)如果没有找到返回 -1
size_type find( char ch,size_type index);返回ch在字符串中第一个出现的位置(从index开始查找)如果没有找到返回 -1
string & insert(int p,const string & s);在p位置插入字符串s
string &append(const string * s);将字符串s 连接到当前字符串的结尾处
string substr(int pos = 0,int n = npos)const;返回从pos开始n个字符组成的字符串
示例

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
#include<string.h>
using namespace std;
int main(){
string str;
if(str.empty())
cout<<"str is NULL."<<",length="<<str.length()<<endl;
else
cout<< "str is not NULL."<<endl;
str = str.append("abcdefg"); //将字符串连接到当前字符串的结尾
cout<<"str is"<<str<<",size = "<<str.size()<<str.length()<<endl;
cout<<"length"<<str.length()<<endl;
const char*p = str.c_str(); //p指向字符串str
cout<<"p = "<<p<<endl; //输出abcdefg
cout<<"find:"<<str.find("de",0)<<endl; //从str的第0查找字符串“de”,成功返回3
cout<<"find:"<<str.find("de",4)<<endl; //从str的第4查找字符串“de”,查找失败返回-1对应的无符号数
string str1 = str.insert(4,"123"); //重str的第4个位置插入“123”
cout<<str1<<endl;
return 0;

}

结果:
str is NULL.,length=0
str isabcdefg,size = 77
length7
p = abcdefg
find:3
find:18446744073709551615
abcd123efg

第二章

结构化程序设计

结构化程序设计的基本方法:采用自顶向下、逐步求精模块化的思想,将复杂的大问题层层分解为许多简单的小问题。
结构化程序设计的三种基本结构:顺序结构、选择结构、循环结构。
结构化程序设计的基本思想数据结构 + 算法 = 程序
结构化程序设计的缺点:难以理解,难以扩充、难以纠错

面向对象的设计的概念和特点

1、概念
面向对象技术将问题看成对象的集合。对象具有两个特性:1)对象本身的信息,也称为属性;2)对象的操作,也称为属行为
对象 = 数据(属性) + 函数(行为)
是对象的一个抽象、对象是类的实例化
2、特点(抽象、封装、继承和多态
1)抽象:将同一类事物的共同特征概括出来,这个过程叫做“抽象”。
:是对现实世界中客观事物的抽象。对于一个具体的类它有许多具体的个体,这些个体叫做对象
对象:是系统中用来描述客观事物的一个实体,用对象名属性操作三要素来描述对象。
2)封装:就是把对象的属性和操作封装结合成一个独立的单元。
封装的作用:数据和操作数据的函数紧密联系起来;将对象的一部分属性和函数隐藏起来,对外不可见,起保护作用;另一部分函数对外可见,作为对象进行操作的接口。
3)继承:在编写一个新类的时候以现有的类作为基础,使得到的新类从现有的类“派生”而来,从而达到代码扩充和代码复用作用

考点

1.两种继承:单一继承多重继承


4)多态:不同的对象可以调用相同名称的函数,但可导致完全不同的行为的现象称为。多态性。利用多态性,程序中只需要进行一般形式的函数调用,函数的实现细节留给接收函数调用的对象,这大大提高了人们解决复杂问题的能力。
考点

1.C++支持编译时的多态与运行时多态

类的初步认识

  1. 类的基本概念
    :是具有相同属性和操作的一组对象的集合,它为属于该类的 全部对象提供了统一的抽象描述,其内部包括数据变量成员函数两个主要部分。
    是一种用户自己构造的数据类型,
    要先声明后使用、是具有唯一标识的实体。
  2. C++语言中常用的数据类型有整型、实型、字符型(这3种类型也被称之为简单数
    据类型)、数组类型、布尔类型、枚举类型、结构体类型、公用体类型、指针类型、引用类参考链接

型等。

访问级别 访问控制修饰符 同类 派生类 外部类
公开 public
受保护 protected ×
默认 private × ×
私有 private × ×
访问修饰符示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<iostream>
#include<string>
using namespace std;

class Box{
public:
double length;
void setWidth(double wid) ;
double getWidth();
private:
double width;
};
//类体外定义成员函数
double Box::getWidth(){
return width;
}
void Box::setWidth(double wid){
width = wid;
}
int main(){
Box box;
box.length = 10.0; //访问public成员变量
cout<<"Length of box:"<<box.length<<endl; //输出box.length
// box.width;// `错误` 此处不能调用private成员变量
box.setWidth(10.3);//必须使用成员函数设定的宽度
cout<<"Width of box:"<<box.getWidth()<<endl; //输出Width of box : 10
return 0;


}

Length of box:10
Width of box:10.3
使用private的好处:1)有利于程序修改2)可以避免对象的不正确操作

注意: 1)不能在类的声明中对数据变量进行初始化2)类中声明任何成员变量不能使用extern、auto、register关键字修饰;3)可以是空类;

类的示例剖析

注意:

1、在c++中string关键字全小写,在java中String首字母大写
2、类名myClass,方法名myFunctiond都使用小驼峰表示,变流量使用全拼小写,常量使用大驼峰表示
3、在类中提前声明方法,在类外书写方法体,更清晰易渎,在类体外定义成员函数时使用::

程序结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include<iostream>
#include<string>
#include<string.h>
using namespace std;

class myDate{
public:
myDate(); //构造函数
myDate(int,int,int); //带参数的构造函数
void setDate(int,int,int); //设置
void setDate(myDate); //设置
myDate getDate(); //获取 日期
void setYrar(); //设置
int getMonth(); //获取
void printDate() const; //打印日期
private:
int year,month,day;
};
myDate::myDate(){ // 构造函数
year =1970,month = 1,day = 1;

}
myDate::myDate(int y,int m,int d){ // 构造函数
year =y,month =m,day = d;

}
void myDate::setDate(int y,int m ,int d){ // 构造函数
year =y,month =m,day = d;
return;

}
void myDate::setDate(myDate oneD){ // 构造函数
year =oneD.year =oneD.month,day = oneD.day;
return;

}
myDate myDate::getDate(){ // 获取日期

return *this;

}
int myDate::getMonth(){ // 构造函数
return month;

}
void myDate::printDate() const{ // 构造函数
cout<<year<<"/"<<month<<"/"<<day;
return;
}


class Student{
public:
void setStudent(string,myDate); // 设置学生信息
void setName(string); // 设置学生姓名
string getName(); // 获取学生姓名
void setBirthday(myDate); // 设置学生生日
myDate getBirthday(); // 获取学生生日
void printStudent() const; // 设置学生生日
private:
string name;
myDate birthday;

} ;//string 系统定义好的类,myDate前面定义好的类
void Student::setStudent(string s,myDate d) { // 设置学生信息
name = s;
birthday.setDate(d);
return;
}
void Student::setName(string n ){ // 设置学生姓名
name = n;
return;

}
string Student::getName(){
return name;
}
void Student::setBirthday(myDate d){
birthday.setDate(d);
return;

}; // 设置学生生日
myDate Student::getBirthday(){
return birthday;
}; // 获取学生生日
void Student::printStudent() const{
cout<<"姓名"<<name<<"生日";
birthday.printDate();//调用myDate的成员函数
cout<<endl;
}; // 设置学生生日
int main(){
Student ss;
int y,m,d;
string name_;
cout<<"请输入学生的姓名生日,生日以年月日的次序输入;";
cin>>name_>>y>>m>>d;
ss.setStudent(name_,myDate(y,m,d));//调用成员函数
ss.printStudent();
return 0;
}


请输入学生的姓名生日,生日以年月日的次序输入;小明 2000 3 14
姓名小明生日3/1/14


使用引用方式驱动程序
1
2
3
4
5
6
7
8
9
10
11
int main(){
Student ss;
int y,m,d;
string name_;
Student &sy = ss; //对象的引用
cout<<"请输入学生的姓名生日,生日以年月日的次序输入;";
cin>>name_>>y>>m>>d;
sy.setStudent(name_,myDate(y,m,d));//调用成员函数
sy.printStudent();
return 0;
}

标识符的作用域

1.函数原型作用域:在声明函数原型时形参的作用范围就是函数原型的作用域,也是c++中最小的作用域。
例:double area(double r)
r 的作用范围就在函数area形参列表的左右括号之间。
2.局部作用域:只在程序块内有效。
3.类作用域:在类X成员函数中可以直接使用成员m;在类外通过x.m、X::m或者ptr->m ,ptr为指向该类的指针。
4.命名空间的作用域:用于消除各大模块之间同名引起的歧义。在命名空间内部可以直接引用当前命名空间中声明的标识符,如需要引用其他空间命名的标识符使用以下格式:
命名空间::标识符

第三章

构造函数

1.使用构造函数创建指针:myDate *pd = new myDate();使用new创建对象,下面两种都是合法的:
myDate *pd = new myDate()
myDate *pd = new myDate
1)用户定义了构造函数,都会调用构造函数进行初始化。
2)用户未定义构造函数,对带括号的情况,系统在成员变量分配内存的同时,将其初始化为0; 不加括时,系统只为成员变量分配内存空间,成员变量的分配空间是随机值
2.复制构造函数
1)使用一个已存在的对象去初始化另一个正在创建的对象
2)复制构造函数的原型为:
类名::类名(类名&) // 对象的引用作为形参
或类名::类名(`const`类名` &`) //为了不改变原型对象,使用`const限制`

复制构造函数示例
1
2
3
4
5
6
7
8
9
10
11
12
Student();  //在类中声明构造函数的
Student(const Student &s); //在类中声明复制构造函数的原型

Student:: Student(const Student &s){//复制构造函数的函数体
name = "copy"+s.name;
birthday = s.birthday;
}

void main(){
Student stud;//定义对象stud,并调用构造函数
Student stud2(stud); //定义对象stud2并调用复制构造函数初始化
Student ss[3]{Student(),stud,Student(stu)}}; //ss[0]调用构造函数,ss[1]与ss[2]调用复制构造函数初始化

析构函数

作用:在对象消失时,释放由构造函数分配的内存

在类体中的声明形式:~类名();
定义形式:类名::~类名(){ }
注意:

  1. 中只能定义一个析构函数且不能指定参数,并对象生存周期结束时,系统自定调用。
  2. 系统会自动生成空的析构函数,使用new运算符动态分配了内存空间,则在析构函数中应该使用delete释放掉这部分空间。
  3. 系统会按照后创建先析构的顺序,可以使用delete调用析构函数改变顺序。
析构造函数示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134

#include<iostream>
#include<string>
#include<string.h>
using namespace std;

class myDate{
public:
myDate(); //构造函数
~myDate(); //析构函数
myDate(int,int,int); //带参数的构造函数
void setDate(int,int,int); //设置
void setDate(myDate); //设置
myDate getDate(); //获取 日期
void setYrar(); //设置
int getMonth(); //获取
void printDate() const; //打印日期
private:
int year,month,day;
};
myDate::myDate(){ // 构造函数
year =1970,month = 1,day = 1;
cout<<"myDate构造函数1 "<<endl;

}
myDate::~myDate(){ // 析构函数

cout<<"myDate析构函数1 "<<endl;

}
myDate::myDate(int y,int m,int d){ // 构造函数
year =y,month =m,day = d;

}
void myDate::setDate(int y,int m ,int d){ // 构造函数
year =y,month =m,day = d;
return;

}
void myDate::setDate(myDate oneD){ // 构造函数
year =oneD.year =oneD.month,day = oneD.day;
return;

}
myDate myDate::getDate(){ // 获取日期

return *this;

}
int myDate::getMonth(){ // 构造函数
return month;

}
void myDate::printDate() const{ // 构造函数
cout<<year<<"/"<<month<<"/"<<day;
return;
}


class Student{
public:
Student();
~Student();
Student(const Student &s);
void setStudent(string,myDate); // 设置学生信息
void setName(string); // 设置学生姓名
string getName(); // 获取学生姓名
void setBirthday(myDate); // 设置学生生日
myDate getBirthday(); // 获取学生生日
void printStudent() const; // 设置学生生日
private:
string name;
myDate birthday;

} ;//string 系统定义好的类,myDate前面定义好的类
Student::Student():name("Noname"),birthday(myDate()){

cout<<"Student构造函数2 "<<endl;

}
Student::~Student(){

cout<<"Student析构函数2 "<<endl;

}
Student::Student(const Student &s){
name = "copy" + s.name;
birthday = s.birthday;
cout<<"Student复制构造函数2 "<<endl ;
}


void Student::setStudent(string s,myDate d) { // 设置学生信息
name = s;
birthday.setDate(d);
return;
}
void Student::setName(string n ){ // 设置学生姓名
name = n;
return;

}
string Student::getName(){
return name;
}
void Student::setBirthday(myDate d){
birthday.setDate(d);
return;

}; // 设置学生生日

myDate Student::getBirthday(){
return birthday;
}; // 获取学生生日
void Student::printStudent() const{
cout<<"姓名 "<<name<<" 生日";
birthday.printDate();//调用myDate的成员函数
cout<<endl;
}; // 设置学生生日


int main(){
Student stud;
stud.printStudent(); //输出默认值
Student sy[2] = {Student(),stud};
for(int i=0;i<2;i++)
sy[i].printStudent();

return 0;
}




myDate构造函数1
Student构造函数2
姓名 Noname 生日1970/1/1
myDate构造函数1
Student构造函数2
myDate构造函数1
Student复制构造函数2
姓名 Noname 生日1970/1/1
姓名 copyNoname 生日1970/1/1
Student析构函数2
myDate析构函数1
Student析构函数2
myDate析构函数1
Student析构函数2
myDate析构函数1

注意:
在构造函数中初始化对象的变量:

1
2
3
4
Student::Student():name("Noname"),birthday(myDate()){


}

类的静态成员

  1. 静态全局变量:static修饰的、在所有花括号之外声明的变量,其作用范围全局可见,整个项目内有效
  2. 静态局部变量:static修饰的、在块内定义的,其作用范围从定义之处开始到本块结束为止
  3. 静态变量均存储在全局数据区,只初始化一次,如果未初始化系默认初始为0;
  4. 类的静态成员有两种:静态成员变量与静态成员函数,
  5. 静态成员变量不能在类体内赋值,应该在类外进行初始化,给静态成员变量赋初始值格式如下:
    类型 类名:: 静态成员变量 = 初值;//不能有static
  6. 在类体外定义成员函数时,前面不能加static
  7. 类的静态成员,被类的所有对象共享

    注意:

    静态成员函数不可以调用非静态成员

访问静态成员的一般格式:

类名:: 静态成员名
对象名 · 静态成员名
对象指针->静态成员名

静态成员函数与一般成员函数的不同

1)可以不指向某个具体对象,只与类名连用
2)在没有建立对向前,静态成员就已经存在;
3)静态成员是类的成员,不是对象的成员;
4)静态成员为该类的所有对象共享,他们被存储在一个公共区域
5) 没有this指着,只能通过对象名或指向对象的指针访问类的数据成员
6)静态成员函数不能被说明为虚函数
7)静态成员函数不能直接访问非静态函数

静态成员变量示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
#include<string>
#include<string.h>
using namespace std;
static int glos = 100;//静态全局变量
void f(){
int a = 1; // 局部自动变量
static int fs = 1;// 静态局部变量 fs, 完成初始化
cout<<"在f中:a(自动) = "<<a<<" fs(静态) = "<<fs<<" glos(静态) = "<<glos<<endl;
a +=2;fs += 2;glos +=10;
cout<<"在f中:a(自动) = "<<a<<" fs(静态) = "<<fs<<" glos(静态) = "<<glos<<endl;
//cout <<"ms = "<< ms<<endl;该航错误,变量ms不可见

}
int main(){
static int ms =10;
for(int i = 1; i<=3; i++)
f();

cout <<"ms = "<< ms<<endl; //输出10
cout <<"glos = "<<glos<<endl;//输出130
printf("nihao");
return 0;

}

静态成员函数示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

#include<iostream>
#include<string>
#include<string.h>
using namespace std;
class classA{
public:
double x,y;
static int num;//类的共有静态成员变量,供所有对象共享;
//此处用于记录 通过构造函数已生成的对象个数
classA(){ //构造函数
x = 0; y = 0;
num++;//每生成一个对象,num加 1
}
classA(double x0,double y0){ //构造函数
x = x0; y = y0;
num++;//每生成一个对象,num加 1
}
static void staticFun(){
cout<<"调用静态函数current_num = "<<num<<endl;
//cout<<"x = "<<x<<endl;
//错误,在静态函数中不能访问非静态变量。
}
};
int classA ::num = 0; //必须在类体外初始化静态成员变量
int main(){
classA obj(1.2,3.4),*p; //调用以构造函数
//第一次调用
cout<<"classA::num = "<<classA::num <<endl; //使用类名做限定符
classA::staticFun() ;//调用函数
//第二次调用
cout<<"obj.num "<<obj.num<<endl; //使用对象名做限定符
obj.staticFun();

cout<<endl;

classA A[3]; //调用三次构造函数
//调用后结果
cout<<"classA::num = "<<classA::num<<endl;
classA::staticFun() ;//调用函数
cout<<endl;

p = new classA(5.6,7.8) ;//调用一次构造函数
cout<<"classA::num "<<classA::num <<endl;
classA::staticFun() ;//调用函数
cout<<"p->num "<<p->num <<endl;
p->staticFun();
return 0;


}

classA::num = 1
调用静态函数current_num = 1
obj.num 1
调用静态函数current_num = 1

classA::num = 4
调用静态函数current_num = 4

classA::num 5
调用静态函数current_num = 5
p->num 5
调用静态函数current_num = 5

变量及对象的生存期和作用域

生存期:是指所占据内存空间由分配到释放的时期。
作用域:仅仅在定义的代码块内有效。

常量成员和常量引用成员

1、 类的常量成员变量

定义类的常量成员变量的一般格式:

const 数据不类型 类常量成员变量 = 表达式

类常量成员变量必须进行初始化,而且只能通过构造函数的成员初始化列表方式进行。

2、 类的常量对象

类的常量对象必须在声明的同时进行初始化,而且不能被更新。定义常量对象的格式:

const 类名 对象名(参数表);或
类名 const 对象名(参数表);

例:

1
myDate const al(1997,12,20); //定义myDate类常量对象al 并初始化

常量对象只能调用常量成员函数不能调用非常量成员函数,普通对象可以调用所有成员函数

常量成员函数与普通成员函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<iostream>
using namespace std;
class Sample{
public:
Sample();
void getVlue() const;//常量成员函数
void getVlue() ;//非常量成员函数 (同名)
void priVlue() ;//非常量成员函数
void priVcon() const;//常量成员函数
};
Sample::Sample() {};//构造函数

void Sample::getVlue() const //常量成员函数
{
cout<<"常量成员函数" <<endl;
}

void Sample::getVlue() //非常量成员函数
{
cout<<"非常量成员函数" <<endl;
}

void Sample::priVlue() //非常量成员函数
{
cout<<"非常量成员函数priVlue()" <<endl;
}


void Sample::priVcon() const //常量成员函数
{
cout<<"常量成员函数priVcon()" <<endl;
}

int main(){
Sample o;
const Sample cono;
cout<<"cono\t";
cono.getVlue();//通过常量对象只能,调动常量函数
// cono.priVlue() ; //错误!不能调用非常量成员函数

cout<<"o\t";
o.getVlue(); //“同名”情况下,系统自动调用区别用

cout<<"o\t";
o.priVlue(); //调用普通函数

cout<<"o\t";
o.priVcon(); //调用常量成员函数

return 0;

}

cono 常量成员函数
o 非常量成员函数
o 非常量成员函数priVlue()
o 常量成员函数priVcon()

3、常量函数

在类体内定义常量函数格式:

类型标识符 函数名 (参数列表) const{….//函数体}

在类体内声明,体外定义格式:

声明形式: 类型标识符 函数名 (参数列表)const;
定义形式: 类型标识符 类名::函数名(参数列表) const;
{….//函数体}

4、常引用作为函数参数
  使用引用作为函数参数,传送的是地址,所以形参改变,则实参也跟着改变,但如果不希望函数改变对象的值,就要使用常引用作为参数,
例如:

1
void Display(const double& r){cout<<r<<endl} //Display是能使用而不能改变r所引用的对象。

常量成员变量及常量成员函数的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include<iostream>
using namespace std;
class constClass{
const int conMbr;//类中常量成员变量
int Mbr;//普通成员变量
public :
constClass():conMbr(0),Mbr(100){
}//类中定义的const成员变量必须在构造函数的初始化列表中进行初始化
constClass(int i):conMbr(i){
Mbr = 200 ; //初始化列表处给出初值
}

void printConst(){
cout<<"conMbr = "<<conMbr<<",Mbr = "<<Mbr<<endl;
}
int getConst(){
cout<<"调用非常量函数"<<endl;
return conMbr;
}

int getConst() const{
cout<<"调用常量函数"<<endl;
return conMbr;
}

int getValue(){
;
return Mbr;
}
void processConst(){
cout<<"在processConst函数中非常量-- "<<endl;
int x = 2*conMbr +1;//可以读取conMBR
cout<<"x = 2*conMbr +1 = "<<x<<endl;
//conMbr++;//错误!不能更改常量成员变量conMbr的值
Mbr++;
cout<<"Mbr = "<<Mbr<<endl;
}


void processConst() const{
cout<<"在processConst函数中非常量-- "<<endl;
int x = conMbr +1;//可以读取conMBR
cout<<"x = conMbr +1 = "<<x<<endl;
//conMbr++;//错误!不能更改常量成员变量conMbr的值
//Mbr++; //错误!也不能更改非常量成员变量 Mbr的值
cout<<"Mbr = "<<Mbr<<endl;
}
};

int main(){
constClass ob1(123),ob2;
//定义普通对象 ob1={123,200},ob2={0,100}
ob1.printConst();
cout<<"ob2.getConst() = "<<ob2.getConst()<<endl;

//普通对象系统自动调用非常量函数

ob2.processConst() ;
const constClass ob3(20);//定义常量对象ob ={20,200 }
cout<<"ob3.getConst() = "<<ob3.getConst() <<endl;
ob3.processConst();
//常量对象系统自动调用常量函数
return 0;
}




conMbr = 123,Mbr200
调用非常量函数
ob2.getConst() = 0
在processConst函数中非常量—
x = 2*conMbr +1 = 1
Mbr = 101
调用常量函数
ob3.getConst() = 20
在processConst函数中非常量—
x = conMbr +1 = 21
Mbr = 200

常引用型成员变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
using namespace std;
int fvalue = 10;
class CDemo{
public:
const int num; //常量型成员变量
const int&ref;//常引用型成员变量
int value;
public:
CDemo(int n):num(n),ref(value),value(4){}

//常引用型成员变量也必须在 构造函数的初始化列表中进行初始化

};
int main(){
cout<<sizeof(CDemo) <<endl;
//输出 CDemo对象的长度24(整形站8个字节)
CDemo f(100);
//定义对象f ,初始化f{100,4,4}
// f.ref = f.value; //错误!在程序中不能修改这个引用
cout<<"f.num= "<<f.num <<"\t f.ref ="
<<f.ref<<"\t f.value ="<<f.value<<endl;
return 0;

}

24
f.num= 100 f.ref =4 f.value =4

成员对象和封闭类

1、在定义封闭类的构造函数时,需要添加初始化列表指明要调用成员对象的那个构造函数。

在封闭类构造函数中添加初始化列表格式:

封闭类名::构造函数名 (参数表):成员变量1(参数表), 成员变量2 (参数表)····{····}

例:

1
Student::Student(string n):name(n) ,birthday(myDate()){}

注意:

1)  执行封闭类的构造函数时,先执行成员对象的构造函数,然后再执行封闭类自己的构造函数。
2)  成员对象的构造函数的执行次序与成员对象在类定义中的说明次序一致,与在构造函数初始化列表中出现的次序无关
3)  ;当封闭类消亡时先执行封闭类的析构函数,然后再执行成员对象的析构函数,析构函数先构造后析构

封闭类构造函数的初始化列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include<iostream>
using namespace std;

class CTyre{ //轮胎类
private:
int radius; //半径
int width;//宽度

public:
CTyre():radius(16) ,width(185){
}//定以构造函数 1
CTyre(int r,int w):radius(r) ,width(w){
}//定以构造函数 2

int getRadius()//获取半径
{
return radius;

};
int getWidth()//获取宽度
{
return width;

}

};

class CCar {//汽车类 封闭类

private:
int price; //价格
CTyre tyre;//成员对象

public:
CCar();//声明构造函数 3
CCar(int p ,int tr, int tw);//声明构造函数3

int getPrice(){ //获取价格
return price;
}
CTyre getCTyre(){
//获取轮胎信息
return tyre;
}
} ;
CCar::CCar()//定义构造函数3
{
price = 50010;
CTyre();
} ;
CCar::CCar(int p ,int tr, int tw):price(p),tyre(tr,tw){
};//定义构造函数,使用初始化列表
int main(){
CCar car(48900,17,255) ;//定义对象car并调用构造函数4初始化,
// 需先执行构造函数2初始化轮胎信息,在继续执行构造函数3初始化价格


cout<<"\tprice= "<<car.getPrice()<<endl;
cout<<"\tgetRadius = "<<car.getCTyre().getRadius()<<
"\tgetWidth = "<<car.getCTyre().getWidth()<<endl;


}

      price= 48900    getRadius =  17 getWidth =  255

2、封闭类的复制构造函数

如果封闭类的对象是用默认复制构造函数初始化,那么他包含的成员对象也会用复制构造函数初始化

封闭类的复制构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
using namespace std;
class A{
public:
A(){
cout<<"default"<<endl;//构造函数

};
A(A&a){
cout<<"copy"<<endl;//复制构造函数

};
};
class B {//封闭类
A a;

};
int main(){
B b1,b2(b1);//b1是类A构造函数初始化,b2是类A 复制构造函数初始化


return 0;
}

default
copy

友元

1、 友元机制是对类外一些函数打开的一个特殊通道,授权他们能够访问本类的私有成员变量
2、 友元机制破坏了类的封装性和信息隐藏,但有助于数据共享,能够提高程序的效率
3、 友元机制包括友元函数和友元类

友元函数:

在定义一个类的时候,可以把一些函数(包括全局函数其他类成员函数)声明为“友元”,这样这些函数就成为了友元函数

类中声明友元函数的格式:

friend 函数类型 函数名(参数表); //针对全局函数
friend 函数类型 函数所在类名::函数名(参数列表);

注意:

友元函数可以在类中私有或公有部分通过关键字friend声明或定义,但如在类中声明,而在类外定义,就不能再在类外使用friend关键字。

友元函数的优点

  1. 友元函数应被看做类接口的一部分,使用它的主要目的是提高效率,因为它可以直接访问对象的私有成员,从而省去调用类的相应成员函数开销。
  2. 类的设计者不必考虑好该类的各种使用情况之后再设计这个类,而是可以根据需要,通过用友元来增加类的接口。
友元函数声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<iostream>
#include<cmath>//数学函数
using namespace std;
class Pixel;//前向引用声明
class Test{
public:
void printX(Pixel p);

};
class Pixel{
private:
int x,y;
public:
Pixel(int x0,int y0)//定以构造函数
{
x = x0;y = y0;
}
void printxy(){
cout<<"Pixel:("<<x<<","<<y<<")"<<endl;
}
friend double getDist(Pixel p1,Pixel p2);//声明全局友元函数
friend void Test::printX(Pixel p);//声明Text类成员函数为友元函数

};
void Test::printX(Pixel p){
cout<<"p.x ="<<p.x<<"\tp.y ="<<p.y<<endl;
return;

}
double getDist(Pixel p1,Pixel p2)//友元函数在类外定义
{
double xd = double(p1.x -p2.x);//友元函数使用pixel 类私有成员
double yd = double(p1.y -p2.y);//强制类型转换
return sqrt(xd*xd + yd*yd) ;//计算两点之间距离
}


int main(){
Pixel p1(0,0) ,p2(10,10);
p1.printxy();
p2.printxy();
cout<<"(p1,p2)之间的距离 ="<<getDist(p1,p2)<<endl;//直接调用全局函数
Test t;
cout <<"从友元函数中输出----" <<endl;
t.printX(p1);//通过对象调用类的成员函数
t.printX(p2);//通过对象调用类的成员函数
return 0;
}

Pixel:(0,0)
Pixel:(10,10)
(p1,p2)之间的距离 =14.1421
从友元函数中输出——
p.x =0 p.y =0
p.x =10 p.y =10

友元类

如果将一个类B说明为另一个类A的友元类,则类B中的所有函数都是类A的友元函数
友元类的关系是单向的,且不可出传递
除非必要一般不使用友元类

在类定义中声明友元类的格式

friend clsaa 类名

友元类示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<iostream> 
using namespace std;
class myComplex{//复数类
private:
double real,imag;//复数的实部,虚部
public:
myComplex();//声明构造函数
myComplex(double r,double i);
friend class oper; //定义友元类 .oper是myComplex的友元类且方向不可逆



};
myComplex::myComplex(){
real = 0; imag = 0;
}
myComplex::myComplex(double r,double i){
real = r,imag = i;
}
class oper{
//声明成员函数,连个参数对象c1与c2相加
public:
myComplex addCom(myComplex c1,myComplex c2);
//声明成员函数,输出参数对象c有关的数据
void outCom(myComplex c) ;
};
//类外定义函数
myComplex oper::addCom(myComplex c1,myComplex c2) {
return myComplex(c1.real+c2.real,c1.imag+c2.imag);
}
void oper:: outCom(myComplex c){
cout<<"("<<c.real<<","<<c.imag<<")";
}
int main(){
myComplex c1(1,2),c2(3,4),res;
oper o;
res = o.addCom(c1,c2);//通过oper操作myComplex成员函数
o.outCom(c1);
cout<<"+";
o.outCom(c2);
cout<<"=";
o.outCom(res);
cout<<endl;
return 0;
}





(1,2)+(3,4)=(4,6)

this指针

1、当一个承运函数被调用时,系统将自动向它传递一个隐含参数,该参数是一个指向调用该函数的对象指针,名为this指针,从而使成员函数知道该对那个对象进行操作。
2、使用this指针,保证了每个对象可以拥有自己的数据成员,但处理这些数据成员的代码却可以被所有的对象共享,从而提高了程序的安全性和效率。
3、this指针式实现封装的一种机制,它将对象和该对象调用的成员函数链接在一起,从而在外部看来,每个对象都拥有自己的成员函数。

注意:

除非形参的名字与成员变量的名相同。一般情况下都省略掉符号this->,而让系统进行默认