C++ 进阶笔记

本文最后更新于:2023年4月7日 下午

C++进阶笔记

数组

​ 数组是具有一定顺序关系的若干对象的集合。

array[N] 数组的下标从0开始。

声明

数据类型 标识符 [常量表达式1] [常量表达式2]

​ 数据类型: 整型、浮点型、结构体

使用

数组名 [常量表达式1] [常量表达式2]

范围for循环

​ 对给定序列中每一个元素按序列中元素的顺序逐一访问,配合auto自动判断元素类型。用来实现数组元素的快速遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for(const auto&e : a){
cout << e << endl;
}

//二维数组
int arry[][3] = { {1,2,3},{4,5,6} };
for (int (&row)[3] : arry) {
for (int &e : row) {
cout << e << endl;
}
}
for (auto &row : arry) {
for (auto &e : row) {
cout << e << endl;
}
}

下标迭代for循环

1
2
3
for(int i = 0; i < n; i++){
cout << b[i] << endl;
}

关于二维数组

​ C++中二维数组被当作一维的数组,int m[2][3]可以看作大小是2,每个元素都是一个大小为3,类型为int类型的数组。

数组定义

1
2
3
4
5
6
7
8
9
10
int a[3]= {1,1,1};
int a[] = {1,1,1};
int a[2][3] = {1,2,3,4,5,6};
int a[2][3] = {{1,2,3},{4,5,6}};
int a[][3] = {1,2,3,4,5,6};
int a[][3] = {{1,2,3},{4,5,6}};

//对象数组 注意有缺省值的时候,需要适当改动默认构造函数
Location loc[2] ={Location(3,4),Location(1,2)}

二维数组与地址

数组作为函数参数

​ 传递的是地址。数组作为参数时,函数里一般不指定第一维的大小。

数组的名称代表数组首元素的地址

数组的名称取地址(&数组名称)代表整个数组开始的地址,虽然和数组首元素的地址

实例

注意.h文件的 def

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Point.h 类的定义
#ifndef _POINT_H
#define _POINT_H

class Point {
private:
int x, y;
public:
Point();
~Point();
Point(int x, int y);
int getX() { return x; }
int getY() { return y; }
void movePoints(int newX, int newY);
};

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Point.cpp
//Point.h 类的具体实现
#include<iostream>
#include "Point.h"

using namespace std;

Point::Point():x(0),y(0) {
cout << "Deafult constructor called." << endl;
}

Point::~Point() {
cout << "Destructor called" << endl;
}

Point::Point(int x,int y):x(x),y(y){
cout << "Constructor called" << endl;
}

void Point::movePoints(int newX, int newY) {
x = newX;
y = newY;
cout << "Move to new Points(" << newX << "," << newY << ")" << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//主函数 .cpp
#include<iostream>
#include"Point.h"

using namespace std;

int main() {
Point a[2];
for (int i = 0; i < 2; i++){
a[i].movePoints(i + 10, i + 11);
}
return 0;
}

实例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Point.h 类的定义 头文件
//注意使用了友元函数 linefit函数中,可以直接访问 x,y
#ifndef _Point_H
#define _Point_H

class Point {
public:
Point(float x = 0, float y = 0) :x(x), y(y) {}
float getX() const { return x; }
float getY() const { return y; }
friend float lineFit(const Point points[], int nPoint);
private:
float x, y;
};

#endif
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
//main.cpp
#include<iostream>
#include"Point.h"

using namespace std;

//友元函数 直接访问Point的私有属性 x,y
float lineFit(const Point points[], int nPoint) {
float avgX = 0, avgY = 0;
float lxx = 0, lyy = 0, lxy = 0;
for (int i = 0; i < nPoint; i++){
avgX += points[i].x/nPoint;
avgY += points[i].y/nPoint;
}
for (int i = 0; i < nPoint; i++){
lxx += (points[i].x - avgX) * (points[i].x - avgX);
lyy += (points[i].y - avgY) * (points[i].y - avgY);
lxy += (points[i].x - avgX) * (points[i].y - avgY);
}
cout << "This lien can be fitted by y=ax+b." << endl;
cout << "a=" << lxy / lxx << " " << endl;
cout << "b=" << avgY - lxy * avgX / lxx << endl;
return static_cast<float>(lxy / sqrt(lxx * lyy));
}

int main() {
Point p[10] = { Point(6,10),Point(14,20), Point(26,30), Point(33,40),Point(46,50),Point(54,60),Point(67,70),Point(75,80),Point(84,90), Point(100,100) };
float r = lineFit(p, 10);
cout << "Line coefficient r=" << r << endl;
return 0;
}

指针

指针也是一种数据类型,具有指针类型的变量称为指针变量,指针变量是用来存放内存单元地址的。

声明

数据类型 *标识符

数据类型可以是任意类型,代表指针所指向的对象的类型。

​ 举例:

int *ptr

​ 定义了一个指向int类型数据的指针变量,这个指针的名字是ptr,存放int型数据的地址。

与地址相关的运算 * 和 &

* 表示获取指针变量所指向的值,一元操作符。

& 表示获得一个对象的地址,一元操作符。

注意:

* 出现在声明语句中,在被声明的变量名之前时,表面声明的是指针。

int *p 声明指针变量p

* 出现在执行语句中或者声明语句的初值表达式中作为一元运算符时,表示指针所访问的对象的内容。

cout<< *p 打印p所指向的内存单元的值

& 出现在变量声明语句中位于被声明变量的左边时,表示声明的是引用

int &rf 表明rf是个引用

&给变量赋初值的时候出现在等号右边或者在执行语句中作为一元运算符出现时,表示取对象的地址。

int a,b;

int *pa,*pb = &b; 声明指针变量pa、pbb的地址赋给指针pb.

pa = &a; a的地址赋给指针pa.

赋值

存储类型 数据类型 *指针名 = 初始地址

指针名 = 地址

数组名其实是一个不能被赋值的指针,即指针常量

int a[10];

int *ptr = a;

空指针

1
2
3
4
//用0和NULL表示/
int *p;
p=0;
int *p = NULL;

void类型指针

void类型的指针经过显示转换之后可以访问任何类型的数据。一般只在指向的数据类型不确定时使用。

1
2
3
4
5
void *pv;
int i =5 ;
pv = &i;
int *pint = static_cast<int *>(pv); // void类型指针转换后赋给int型指针
cout << "*pint=" << *pint << endl;

指向常量的指针与指针类型的常量

1
2
3
4
5
6
7
//指向常量的指针
//指针可以乱指,但是必须不能用指针去改指向的值
int a;
const int *p1 = &a;
int b;
p1 = &b; //正确,p1本身的值可以改变
*p1 = 1;//错误,不能改变p1所指向的对象的值
1
2
3
4
//指针类型的常量
int a,b;
int* const p2 = &a;
p2 = &b; //错误,p2是指针常量,值不能改变。

运算

​ 指针可以和整数进行加减运算。

*(p1+n1)p1[n1]表示p1 当前所指位置后方的第 n1 个数的内容。

C++指针

begin 与 end 函数

​ C++ 11 引入了beginend函数,将数组作为参数。begin返回指向数组首元素的指针,end返回指向数组尾元素下一位置的指针。

1
2
3
4
//查找arr数组中第一个负数
int *pbeg = begin(arr),*pend = end(arr);
while(pbeg != pend && *pbeg >= 0)
++pbeg;

一维实例

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

int main() {
int a[3] = {1,2,3};
for (int i = 0; i < 3; i++){
cout << a[i] << " ";
}
cout << endl;
int* p = a;
for (int i = 0; i < 3; i++) {
cout << *(p+i) << " ";
}
cout << endl;
for (int* q = a; q < (a+3); q++) {
cout << *q << " ";
}
cout << endl;
}

指针数组与数组指针

​ 数组中的每个元素都是指针变量。指针数组中的每个元素都必须是同一类型的指针。

数据类型 *数组名[下标表达式]

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
#include<iostream>

using namespace std;

int main() {
int line1[] = { 1, 2, 3 };
int line2[] = { 4, 5, 6 };
int line3[] = { 7, 8, 9 };
//指针数组 存放三个元素都是指针元素
//line123 数组名 数组元素的首地址 指向数组的指针
int* pLine[3] = { line1,line2,line3 };
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
cout << *(*(pLine + i) + j) << " ";
}
cout << endl;
}

int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
//把二维数组直接当作指针数组来访问
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
cout << *(*(arr + i) + j) << " ";
}
cout << endl;
}
//数组指针 指向数组的首地址
int (*arrPtr)[3] = arr;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
cout << *(*(arrPtr + i) + j) << " ";
}
cout << endl;
}
//指针数组 存放的每个元素都是指向各个数组首元素的指针(数组名)
//相当于前面的line1 line2 line3
int* ptrArr[3] = { arr[0],arr[1],arr[2]};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
cout << *(*(ptrArr + i) + j) << " ";
}
cout << endl;
}
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
//数组元素
array[0][0]: 1
//一个int型数组元素所占空间 4个字节
sizeof array[0][0]: 4
//指向一个数组元素的指针(数组元素[0][0]的地址)
&array[0][0]: 0x7fffa23b2af0
//指向一个数组元素的指针所占空间 8个字节
sizeof &array[0][0]: 8

//代表一维数组的首地址(在里面指向第一个元素)
array[0]: 0x7fffa23b2af0
//定义一个整型指针,代表指向一维数组里的第一个元素(首地址)
int (*p) = array[0];
//例子中一个一维数组所占空间 4*4=16字节
sizeof array[0]: 16

//代表整个一维数组的首地址(在外面指向一个整个一维数组)
&array[0]: 0x7fffa23b2af0
//定义一个整型数组指针,代表指向整个一维数组
int (*p)[4] = &array[0];
//一个指向以一维数组的指针所占空间 8个字节
sizeof &array[0]: 8


//指向二维数组的指针地址
array: 0x7fffa23b2af0
//整个二维数组所占空间 2*4*4=32
sizeof array: 32
//指向一整个二维数组的指针
&array : 0x7fffa23b2af0
//指向一整个二维数组的指针所占空间 8个字节
sizeof &array : 8

array[0][0]:输出第一行第一列的元素值,即 1。 sizeof array[0][0]:输出一个 int 类型变量所占据的字节数,即 4。 &array[0][0]:输出第一行第一列的元素的内存地址,即 0x7fffa23b2af0。 sizeof &array[0][0]:输出一个指向 int 类型的指针所占据的字节数,即 8。 array[0]:输出第一行的数组的起始地址,即 0x7fffa23b2af0。 sizeof array[0]:输出一个包含 4 个 int 类型元素的一维数组所占据的字节数,即 16。 &array[0]:输出第一行的数组的起始地址,即 0x7fffa23b2af0。 sizeof &array[0]:输出一个指向包含 4 个 int 类型元素的一维数组的指针所占据的字节数,即 8。 array:输出整个二维数组的起始地址,即 0x7fffa23b2af0。 sizeof array:输出一个包含 4 个包含 4 个 int 类型元素的一维数组的二维数组所占据的字节数,即 32。 &array:输出整个二维数组的起始地址,即 0x7fffa23b2af0。 sizeof &array:输出一个指向包含 4 个包含 4 个 int 类型元素的一维数组的二维数组的指针所占据的字节数,即 8。

解释:本题中的二维数组 array 由 4 个一维数组组成,每个一维数组包含 4 个 int 类型的元素。 因此,array[0][0] 输出第一行第一列的元素值 1, sizeof array[0][0] 输出一个int 类型变量所占据的字节数 4。 &array[0][0] 输出第一行第一列的元素的内存地址, sizeof(array) = 32:因为数组array共有2行4列,每个元素是int类型,所以占用总共的空间是2 * 4 * sizeof(int) = 32个字节。 &array = 0x7ffcbf5a5d50:这是数组array的地址,它是一个指向数组的指针,占用8个字节。 sizeof(&array) = 8:&array是一个指向数组的指针,它的大小也是8个字节。

总之,数组名在大多数情况下会被解释为指向数组第一个元素的指针,但是当它作为sizeof、&、和赋值操作符的操作数时,它会被解释为指向整个数组的指针。

数组名 array 在这里表示整个数组,因此在 sizeof 操作符中使用 array 时,返回的是整个数组占用的空间大小,即 2 * 4 * sizeof(int) = 32 个字节。

在输出 array 的地址时,使用的是 &array,即数组名 array 的地址。这个地址占用的空间大小是一个指针的大小,即 8 个字节。

需要注意的是,虽然数组名在很多情况下会被自动转换为指向数组首元素的指针,但是在 sizeof 操作符和 & 操作符中,它们都代表整个数组。因此,对于数组名的使用要根据具体的语境来理解。

1
2
3
4
5
6
7
8
9
10
11
int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
//arr代表第一个元素的地址, 即第一个大小为3的数组的地址。
//arrPtr代表指向数组的指针,因此可以直接赋值。
int (*arrPtr)[3] = arr;


int arr[3] = {1,2,3};
//arr代表第一个元素的地址, arrPtr代表指向数组的指针
//&arr代表数组arr的地址,即指向arr数组的指针,虽然可能&arr和arr值一样
int (*arrPtr)[3] = &arr;
为什么第四句话有取地址符而第二句话没有?

​ 在第二句代码中,数组名 arr 表示的是二维数组 int arr[3][3] 中第一个一维数组 int arr[0] 的地址,即 arr 等价于 &arr[0]。因此,将 arr 直接赋值给指针 arrPtr,是可以正确的。

​ 而在第四句代码中,数组名 arr 表示的是一维数组 int arr[3] 中第一个元素 int arr[0] 的地址。由于指针 arrPtr 的类型为 int (*)[3],即指向一个包含 3 个 int 元素的数组的指针,因此需要使用取地址符 & 将一维数组 arr 的地址取出,并强制转换为 int (*)[3] 类型的指针,才能正确地将其赋值给 arrPtr

简而言之:数组中数组名代表第一个元素的地址。一维数组的数组名代表第一个元素的地址(&a[0]),想赋值给(*p)[] (指向数组的指针),必须传数组开始的地址(虽然值上可能一样),所以需要取地址符&。二 吗数组中数组名代表第一个元素也就是第一个一维数组的地址,想赋值给(*p)[] (指向数组的指针),可以直接赋值。

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
int b[][4] = { 1,2,3,4,5,6,7,8 };
int (*b)[4]={1,2,3,4,5,6,7,8};
cout << b << endl;
//输出b数组的地址 第一行的地址

cout << sizeof(b) << endl;
//输出b数组所占用的字节数 4*2*4 = 32

cout << &b << endl;
//输出b数组的地址,整个二维数组的地址

cout << sizeof(&b) << endl;
//输出指向整个二维数组的指针所占用的字节数 8

cout << b[0] << endl;
//输出数组第一行的地址

cout << sizeof(b[0]) << endl;
//输出第一行所占用的字节数 4*4 = 16

cout << &b[0] << endl;
//输出数组第一行的地址

cout << sizeof(&b[0]) << endl;
//输出指向第一行的指针所占用的字节数

cout << &b[0][0] << endl;
//输出第一个元素的地址

cout << sizeof(b[0][0]) << endl;
//输出每个元素所占用的字节数
元素大小
二维数组

指针作为函数参数

  1. 使得实参与形参指针指向共同的内存空间, 达到参数双向传递的目的。即通过被调函数中直接处理主函数中的数据而将函数的处理结果返回其调用者。
  2. 减少函数调用时候的开销。C++中可以通过引用和指针实现。
  3. C++中指向函数的指针传递函数代码的首地址。

如果函数体中不需要通过指针改变指针所指向变量的内容,应在参数表中将其声明为指向常量的指针,这样使得常对象被取地址后也可以作为该函数的参数。指向常量的指针与指针类型的常量

指针作为参数

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

void floatSplit(float x, int* intPart, float* floatPart) {
*intPart = static_cast<int>(x);
*floatPart = x - *intPart;
}

int main() {
float x=3.145, f;
int n;
floatSplit(x, &n, &f);
cout << "n:" << n << "f" << f << endl;
return 0;
}

指针型函数

​ 函数的返回值为指针类型,这个函数就是指针型函数。使用指针函数的主要目的就是要在函数结束时把大量的数据从被调函数返回到主调函数中

1
2
3
数据类型 *函数名(参数表){
函数体
}

函数返回数组指针

​ 因为数组不能被复制,因此不能直接返回数组,但是可以返回数组的指针

  1. 利用类型别名的方法简化操作。
1
2
3
typedef int arr[10]; // arr 类型别名 代表含有10个整数的数组
using arr = int[10]; // arr等价声明
arr *foo(int i);// foo返回一个指向含有10个整数的数组的指针
  1. 不使用类型别名的话,数组的维度必须跟在函数的名字之后,函数的形参列表也跟在函数名字后面并且先于数组的维度。

    类型说明符 (*函数名 ( 参数表) )[数组维度]

    int (*foo(int i))[10] 返回一个大小为10的整型数组指针。

    C++ 11中提供了简化上述声明的方法。尾置返回类型

    auto foo(int i) -> int(*)[10];

    接受一个int类型的参数,返回一个指向10个int类型的数组的指针。

  2. 如果知道函数返回的指针将指向哪个数组,就可以用decltype关键词声明返回类型。

    1
    2
    3
    4
    5
    6
    int a[] = {0,1,2,3,4};
    int b[] = {5,6,7,8,9};

    decltype(a) *func(int i){
    return (i%2) ? &a:&b;
    }
  3. 具体例子

1
2
3
int arr[10]; //arr是一个含有10个整数的数组
int *p1[10]; //p1是一个含有10个指针的数组
int (*p2)[10]; //p2是一个指针,指向含有10个整数的数组arr。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
using namespace std;
using arrT = int[5];//或者typedef int arrT[5];
int arry[5] = {1,2,3,4,5};
arrT* func()
{
return &arry;
}
int main()
{
int (*p)[5] =func();
for(int i=0;i<5;i++)
{
cout<<*(*p + i)<<" "<<endl;
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
int a[5]={1,2,3,4,5};
using namespace std;
int(*make_array())[5]//如果是引用的形式也可以
{
return &a;
}
int main(int argc,char *argv[])
{
int (*p)[5]=make_array();
cout<<*p<<endl;
for(int i=0;i!=5;++i)
{
cout<<(**p)++<<" ";//*p只能得到数组的地址
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
using namespace std;
int arry[10] = {1,2,3,4,5,6,7,8,9,10};
auto func() -> int(*)[10]
{
return &arry;
}
int main()
{
int (*p)[10] =func();
for(int i=0;i<10;i++)
{
cout<<(**p)++<<" ";
}
cout<<endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <cstddef>
using std::size_t;
#include <iostream>
using namespace std;
int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
decltype(odd) *arrPtr(int i)
{
return (i % 2) ? &odd : &even; // returns a pointer to the array
}
int main()
{
int (*arrP)[5] = arrPtr(5); // arrP points to an array of five ints
for (size_t i = 0; i < 5; ++i)
cout << (*arrP)[i] << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
decltype(odd) &arrPtr(int i)
{
return (i % 2) ? odd : even; // returns a pointer to the array
}
int main()
{
int (&arrP)[5] = arrPtr(5); // arrP points to an array of five ints
for (size_t i = 0; i < 5; ++i)
cout << (arrP)[i] << endl;
return 0;
}

指向函数的指针

​ 每个函数的函数名代表代码在内存中的起始地址。调用函数的通常形式“函数名 (参数表)”的实质就是“函数代码的首地址(参数表)”

指向函数的指针(函数指针)就是用来存放函数代码首地址的变量。

数据类型 (* 函数指针名) (形参表)

​ 函数指针在使用前也要进行赋值,使指针指向一个已经存在的函数代码的起始地址。

指针名 = 函数名

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

void print1(float) {
cout << "print1" << endl;
}

void print2(float data) {
cout << data << endl;
}



int main() {
void (*test)(float);
test = print1;
test(2.0);
test = print2;
test(3.0);
return 0;
}

​ 可以使用typedef简化

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;

typedef void(*typeFuncPtr)(float);

void print1(float) {
cout << "print1" << endl;
}

void print2(float data) {
cout << data << endl;
}



int main() {
typeFuncPtr funcPtr;
funcPtr = print1;
funcPtr(2.0);
funcPtr = print2;
funcPtr(3.0);
return 0;
}

对象指针

​ 对象指针是用来存放对象地址的变量。

类名 * 对象指针名

1
2
3
Point* pointPtr;
Point p1;
pointPtr = &p1;

​ 使用对象指针可以方便的访问对象的成员,但是使用之前一定要初始化,让它指向一个已经声明过的对象在使用。通过对象指针可以访问对象的公有成员。

对象指针名->成员名 等价于(*对象指针名).成员名

1
2
3
4
5
6
7
8
class Fred;
class Barney{
Fred cFred; //错误:类Fred的定义不完善,不能声明对象。
Fred *ptrFred; //正确,可以声明指向对象的指针。
};
class Fred{
Barney y;
};

指向类的静态成员的指针

​ 类的静态成员的访问是不依赖于对象的,因此可以用普通的指针指向和访问静态成员

访问静态数据成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;

class Point {
public:
Point(int x = 0, int y = 0) :x(x), y(y) {};
int getX() const { return x; }
int getY() const { return y; }
//声明静态数据成员
static int count;
private:
int x, y;

};

//静态数据成员定义和初始化
int Point::count = 0;

int main() {
Point p(4, 5);
//int型指针指向静态成员
int* ptr = &Point::count;
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
#include<iostream>
using namespace std;

class Point {
public:
Point(int x = 0, int y = 0) :x(x), y(y) {
count++;
}
Point(const Point& p) :x(p.x), y(p.y) {
count++;
}
~Point() { count--; }
int getX() const { return x; }
int getY() const { return y; }
//静态函数成员
static void showCount() {
cout << "Object count=" << count << endl;
}
private:
int x, y;
static int count;
};

int Point::count = 0;

int main() {
//指向静态成员函数时,右侧不需要使用&
void (* showCountPTR)() = Point::showCount;
Point pt(4, 5);
cout << "Point A: " << pt.getX() << "," << pt.getY() << endl;
showCountPTR();
Point pt2(pt);
cout << "Point B: " << pt2.getX() << "," << pt2.getY() << endl;
showCountPTR();
return 0;
}

this指针

this指针是一个隐含于某一个类的非静态成员函数中的特殊指针,用于指向正在被成员函数操作的对象。

原理

​ 每一次对成员函数的调用都存在一个目的对象,this指针就是指向这个目的对象的指针。this指针明确指出了成员函数当前所操作的数据所属的对象。

this指针实际上是类成员函数的一个隐含形式参数。当通过一个对象调用成员函数的时候,系统先将该对象的地址通过该参数传递给成员函数,成员函数对对象的数据成员进行操作时,就隐含使用了this指针。

this是一个指针常量,对于常成员函数,this同时又是一个指向常量的指针。在成员函数中,可以使用*this来标识正在调用该函数的对象。

重要的辨析

​ 成员函数通过this这个额外的隐式参数来访问调用它的对象。当我们调用成员函数时,会用对象的地址来初始化this指针(对象就是调用成员函数的对象)。这样我们在成员函数里就可以拿到对象的私有变量了。

this形参是隐式定义的,不会显式地出现在成员函数形参列表里,但实际是存在的,所以任何自定义为this的参数或变量都是非法的。 ​ 因为this指针总是指向这个对象,所以this是一个常量指针(指针的值是const的),不允许修改this指针的值。this指针的默认初始化过程等价TestClass* const this = &tc

C++规定只能使用指向常量的指针来存放常量对象的地址,如果tc是个常量对象,那么this的默认初始化就是非法的,参数列表后的const作用是用来修改隐式this指针的类型,把它变成常指针常量。可以理解为const对象只能调用const方法?

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
class TestClass
{
public:
TestClass() : val(100) {}
virtual ~TestClass() {}


int getVal_v1()
{
return val;
//等价于 return this->val;
}

int getVal_v2() const
{
return val;
}

private:
int val;
};

int main(){
//TestClass *const this = &tc
const TestClass tc;
//报错
int a = tc.getVal_v1();
//正常
int b = tc.getVal_v2();

cout << "getVal_v1: " << a << endl;
cout << "getVal_v2: " << b << endl;

return 0;
}

指向类的非静态成员的指针

​ f类的成员自身也是一些变量、函数或者对象等。因此也可以将它们的地址存放到一个指针变量中。这样,可以通过指针直接指向对象的成员,进而可以通过这些指针访问对象的成员。

​ 指向对象的指针使用前先声明、再赋值、然后引用。

声明

类型说明符 类名::* 指针名; 声明指向数据成员的指针

类型说明符 (类名::* 指针名)(参数表); 声明指向函数成员的指针

引用

​ 声明了指向成员的指针之后,需要对其进行赋值,也就是确定指向类的哪一个成员。

指针名 = &类名::数据成员名;

访问

注:由于类的定义值确定了各个成员的类型、所占内存大小以及它们的相对位置,并不为数据成员分配具体的地址。上述赋值只说明了被赋值的指针是专门用来指向哪个数据成员的,以及指针中存放的数据成员在类中的相对位置。

​ 由于类是通过对象实例化的,在声明类的对象时才为具体的对象分配内存空间。这时只要将对象在内存中的起始地址与成员指针中存放的相对偏移结合起来就可以访问到对象的数据成员

访问数据成员:

对象名 .* 类成员指针名

对象指针名-> *类成员指针名

​ 在一个类的作用域之外不能够对它的私有成员取地址。成员函数指针的声明、赋值、使用过程中,返回值类型、函数参数表一定要相互匹配。

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
#include<iostream>
using namespace std;

class Point {
public:
Point(int x = 0, int y = 0) :x(x), y(y) {};
int getX() const { return x; }
int getY() const { return y; }
private:
int x, y;
};

int main() {
Point p(4, 5);

//声明对象指针 ptPtr
Point* ptPtr = &p;

//声明 指向Point类中 int型的指针
int (Point::*funcPtr)() const;

//因为没有实例化,只有相对地址
//将Point类成员函数相对地址赋值给funcPtr
funcPtr = &Point::getX;

//等价于
int(Point:: * funcPtr2)() const = &Point::getX;

//实际调用过程
//使用对象名访问 用.
cout << (p.*funcPtr)() << endl;
//使用对象指针(绝对地址)访问 用->
cout <<(ptPtr->*funcPtr)() << endl;

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
// Point.h 类文件

#ifndef _POINT_H
#define _POINT_H

class Point {
public:
Point(int x, int y);
~Point();
//静态函数成员
static int showCount();

//函数成员
int getCount();

//数据成员
int publicInt=5;

//静态数据成员
static int publicStaticInt;
private:
int x, y;
static int count;
const int test = 3113212;
/*
TIPS:
如果有const限定的成员变量可以直接在类内初始化。
非const限定的的要在类外初始化。
*/
};


#endif // !_POINT_H
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
//points.cpp 类的实现文件
#include "point.h"
#include <iostream>


//静态数据成员在类外定义
int Point::count = 0;
int Point::publicStaticInt = 10;


Point::Point(int x, int y) :x(x), y(y) {
count++;
};

Point::~Point() {
count--;
}

int Point::getCount() {
return count;
}

int Point::showCount() {
return count;
}
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
//main.cpp 主函数

#include "point.h"
#include<iostream>

using namespace std;


int main() {
Point pt(4, 5);
//指向 对象pt的指针
Point* ptPtr = &pt;

//指向类的非静态数据成员的指针
int(Point:: * ptMemberIntPtr) = &Point::publicInt;

//指向类的静态数据成员的指针
int* ptMemberStaticIntPtr = &Point::publicStaticInt;


/*
指向成员函数的指针,
当指向普通成员函数时,需要使用&;
指向静态成员函数时,不需要使用&。
造成这一现象的原因是C++中关于左值的规定。
https://blog.csdn.net/Sy__TenMoons/article/details/89598613
*/
//指向类的静态函数成员的指针
int (*showCountPtr)() = Point::showCount;

//指向类的非静态函数成员的指针
int (Point:: * getCountPtr)() = &Point::getCount;



//对象名称索引对象数据成员
cout << "pt.publicInt " << pt.publicInt << endl;

//对象指针索引对象数据成员
cout << "ptPtr->publicInt " << ptPtr->publicInt << endl;


//用类名直接索引对象静态数据成员
cout << "publicStaticInt " << Point::publicStaticInt << endl;



//对象名称用指向类的数据成员访问类的数据成员
cout << "ptPtr->*ptMemberIntPtrt " << pt.*ptMemberIntPtr << endl;

//指向对象的指针用指向类的数据成员访问类的数据成员
cout << "ptPtr->*ptMemberIntPtrt " << ptPtr->*ptMemberIntPtr << endl;


//普通指针访问类的静态数据成员
cout << "*ptMemberStaticIntPtr " << *ptMemberStaticIntPtr << endl;


//对象名称用指向类的函数成员访问类的函数成员
cout << "(pt.*getCountPtr)() " << (pt.*getCountPtr)() << endl;

//指向对象的指针用指向类的函数成员访问类的函数成员
cout << "(ptPtr->*getCountPtr)() " << (ptPtr->*getCountPtr)() << endl;

//普通指针访问类的静态函数成员
cout << "(*showCountPtr)() " << (*showCountPtr)() << endl;

//对象名称访问函数成员
pt.getCount();

//指向对象的指针访问类的数据成员
ptPtr->getCount();


return 0;
}
1
2
3
4
5
6
7
8
9
10
//结果
pt.publicInt 5
ptPtr->publicInt 5
publicStaticInt 10
ptPtr->*ptMemberIntPtrt 5
ptPtr->*ptMemberIntPtrt 5
*ptMemberStaticIntPtr 10
(pt.*getCountPtr)() 1
(ptPtr->*getCountPtr)() 1
(*showCountPtr)() 1

与const结合 很重要的辨析

const与指针
1
2
3
4
5
const int func(const int& a) const

第一个是表示返回值是个int型的常值
第二个代表是个const型的引用,就是这个a是可以引用一个int型变量,但是不可以改变这个变量的值(可以读值)
第三个代表这个函数(应该是类中的成员函数),不可以改变调用对象中的数据成员的值。(可以读写数据成员的值,不可以改写其值)

对于这个函数const int func(const int& a) const声明中,三个const分别是什么意思?..._叛逆的鲁鲁修love CC的博客-CSDN博客

动态内存分配

​ C++中动态内存分配技术可以保证程序在运行过程中按实际需要申请适量的内存,使用结束后还可以释放。

​ 这种在程序运行过程中申请和释放的存储单元也称为堆对象

​ 在C++程序中,使用newdelete运算符,建立和删除堆对象,动态分配内存。

语法

new

new 数据类型 (初始化参数列表)

说明:该语句在运行过程中申请分配用于指定类型数据的内存空间,并根据初始化参数别表中给出的值进行初始化。如果内存申请成功,new运算便返回一个指向新分配内存首地址的类型的指针,可以通过这个指针堆堆对象进行访问。

普通对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//动态分配了int类型数据的内存空间
//将初值2放到该空间中
//该内存空间的首地址赋给point
int* point;
point = new int(2);

int* point = new int; //不赋初值
int* point = new int();//默认用0对对象进行初始化

//若用户定义了默认构造函数,则以下两种写法效果相同
//若未定义默认构造函数,系统会使用默认构造函数
//并且new Object()创建对象时还会为基本数据类型和指针类型的成员赋初值0
Object obj = new Object;
Object obj = new Object();

数组对象

new 类型名 [数组长度]

说明:new关键字创建数组之后,[]后仍可以添加(),但括号内不能带任何啊参数。加上()后,代表对数组的每个元素的初始化。

delete

普通对象

delete 指针名

说明:该语句用来删除由new建立的对象,释放指针所指向的内存空间。如果被删除的是对象,该对象的析构函数将被调用。对于new建立的对象,只能使用delete进行一次删除操作。如果对一块内存空间多次使用delete进行删除将会导致运行错误。

数组对象

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
#include<iostream>

using namespace std;

class Point {
public:
Point() :x(0), y(0) {
cout << "Default Constructor called." << endl;
}
Point(int x,int y) :x(x), y(y) {
cout << "Default Constructor2 called." << endl;
}
~Point() {
cout << "Destructor called."<<endl;
}
int getX() const { return x; }
int getY() const { return y; }
void move(int newX, int newY) {
x = newX;
y = newY;
}
private:
int x, y;
};

int main() {
cout << "Step one:" << endl;
//开辟一个Point类型的内存空间
//使用默认构造函数初始化
//将该内存空间的首地址赋给ptr1
Point* ptr1 = new Point;
//将ptr1指向的内存空间释放
delete ptr1;
cout << "Step two:" << endl;
//开辟一个Point类型的内存空间
//使用自定义的构造函数初始化
//将该内存空间的首地址赋给ptr1
ptr1 = new Point(1, 2);
//将ptr1指向的内存空间释放
delete ptr1;
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
#include<iostream>

using namespace std;

class Point {
public:
Point() :x(0), y(0) {
cout << "Default Constructor called." << endl;
}
Point(int x,int y) :x(x), y(y) {
cout << "Default Constructor2 called." << endl;
}
~Point() {
cout << "Destructor called."<<endl;
}
int getX() const { return x; }
int getY() const { return y; }
void move(int newX, int newY) {
x = newX;
y = newY;
cout << "Moving to:(" << x << "," << y << ")" << endl;
}
private:
int x, y;
};

int main() {
Point* ptr = new Point[2];
ptr[0].move(5,10);
ptr[1].move(15, 20);
//等价 对象名 . == 指向对象的指针 ->
ptr->move(5, 10);
(ptr+1)->move(15, 20);
cout << "Deleting ..." << endl;
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
53
54
55
56
57
58
59
#include<cassert>
#include<iostream>

using namespace std;

class Point {
public:
Point() :x(0), y(0) {
cout << "Default Constructor called." << endl;
}
Point(int x,int y) :x(x), y(y) {
cout << "Default Constructor2 called." << endl;
}
~Point() {
cout << "Destructor called."<<endl;
}
int getX() const { return x; }
int getY() const { return y; }
void move(int newX, int newY) {
x = newX;
y = newY;
cout << "Moving to:(" << x << "," << y << ")" << endl;
}
private:
int x, y;
};

class ArrayOfPoints {
public:
//构造函数,创建size大小的points数组
ArrayOfPoints(int size) :size(size) {
points = new Point[size];
}
//析构函数
~ArrayOfPoints() {
cout << "Deleting... " << endl;
delete[] points;
}
//根据下标index返回Point类的对象
Point& element(int index) {
assert(index >= 0 && index < size);
return points[index];
}
private:
//定义points指针
Point* points;
int size;
};

int main() {
int count = 2;

//points是个ArrayOfPoints对象
ArrayOfPoints points(count);

points.element(0).move(5, 0);
points.element(1).move(15, 20);
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
#include<iostream>

using namespace std;

int main() {
float (* cp)[4] = new float[3][4];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
*(*(cp + i) + j) = (i+1) * (j+1);
}
}

for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout<<*(*(cp + i) + j)<<" ";
}
}

cout << endl;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << cp[i][j] << " ";
}
}

delete[] cp;
return 0;
}

Vector创建数组

​ C++ 库提供了被封装的动态数组 vector,可以具有任何类型vector不是一个类,而是一个类模板。

语法

声明

vector<元素类型> 数组对象名(数组长度)

vector<元素类型> 数组对象名(数组长度,元素初值)

vector定义的数组对象的所有元素都会被初始化,如果数组元素的元素类型为基本数据类型,则所有元素都会被以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
#include<iostream>
#include<vector>

using namespace std;

double average(const vector<double>& arr) {
double sum = 0.0;
for (unsigned i = 0; i < arr.size(); i++){
sum += arr[i];
}
return sum / arr.size();
}

int main() {
unsigned n;
cout << "n= " << endl;
cin >> n;

vector<double> arr(n);
cout << "input" << n << "numbers:" << endl;
for (unsigned i = 0; i < n; i++){
cin >> arr[i];
}
cout << "Average=" << average(arr) << endl;
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include<cassert>
#include<iostream>

using namespace std;

class Point {
public:
Point() :x(0), y(0) {
cout << "Default Constructor called." << endl;
}
Point(int x, int y) :x(x), y(y) {
cout << "Default Constructor2 called." << endl;
}
~Point() {
cout << "Destructor called." << endl;
}
int getX() const { return x; }
int getY() const { return y; }
void move(int newX, int newY) {
x = newX;
y = newY;
cout << "Moving to:(" << x << "," << y << ")" << endl;
}
private:
int x, y;
};

class ArrayOfPoints {
public:
//构造函数,创建size大小的points数组
ArrayOfPoints(int size) :size(size) {
points = new Point[size];
}
//析构函数
~ArrayOfPoints() {
cout << "Deleting... " << endl;
delete[] points;
}
//根据下标index返回Point类的对象
Point& element(int index) {
assert(index >= 0 && index < size);
return points[index];
}
private:
//定义points指针
Point* points;
int size;
};

int main() {
int count = 2;

//points是个ArrayOfPoints对象
ArrayOfPoints pointsArray(count);

pointsArray.element(0).move(5, 0);
pointsArray.element(1).move(15, 20);

ArrayOfPoints pointsArray2 = pointsArray;
cout << "Copy of pointsArray" << endl;
cout << pointsArray2.element(0).getX() << ":" << pointsArray2.element(0).getY() << endl;
cout << pointsArray2.element(1).getX() << ":" << pointsArray2.element(1).getY() << endl;
cout << pointsArray.element(0).getX() << ":" << pointsArray.element(0).getY() << endl;
cout << pointsArray.element(1).getX() << ":" << pointsArray.element(1).getY() << endl;
pointsArray.element(0).move(25, 30);
pointsArray.element(1).move(35, 40);
cout << pointsArray2.element(0).getX() << ":" << pointsArray2.element(0).getY() << endl;
cout << pointsArray2.element(1).getX() << ":" << pointsArray2.element(1).getY() << endl;
cout << pointsArray.element(0).getX() << ":" << pointsArray.element(0).getY() << endl;
cout << pointsArray.element(1).getX() << ":" << pointsArray.element(1).getY() << endl;

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
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
#include<cassert>
#include<iostream>

using namespace std;

class Point {
public:
Point() :x(0), y(0) {
cout << "Default Constructor called." << endl;
}
Point(int x, int y) :x(x), y(y) {
cout << "Default Constructor2 called." << endl;
}
~Point() {
cout << "Destructor called." << endl;
}
int getX() const { return x; }
int getY() const { return y; }
void move(int newX, int newY) {
x = newX;
y = newY;
cout << "Moving to:(" << x << "," << y << ")" << endl;
}
private:
int x, y;
};

class ArrayOfPoints {
public:
//构造函数,创建size大小的points数组
ArrayOfPoints(int size) :size(size) {
points = new Point[size];
}
ArrayOfPoints(const ArrayOfPoints& v);
//析构函数
~ArrayOfPoints() {
cout << "Deleting... " << endl;
delete[] points;
}
//根据下标index返回Point类的对象
Point& element(int index) {
assert(index >= 0 && index < size);
return points[index];
}
private:
//定义points指针
Point* points;
int size;
};

//复制构造函数,为新的对象分配空间而不只是简单拷贝
ArrayOfPoints::ArrayOfPoints(const ArrayOfPoints& v) {
size = v.size;
points = new Point[size];
for (int i = 0; i < size; i++){
points[i] = v.points[i];
}
}
int main() {
int count = 2;

//points是个ArrayOfPoints对象
ArrayOfPoints pointsArray(count);

pointsArray.element(0).move(5, 0);
pointsArray.element(1).move(15, 20);

ArrayOfPoints pointsArray2 = pointsArray;
cout << "Copy of pointsArray" << endl;
cout << pointsArray2.element(0).getX() << ":" << pointsArray2.element(0).getY() << endl;
cout << pointsArray2.element(1).getX() << ":" << pointsArray2.element(1).getY() << endl;
cout << pointsArray.element(0).getX() << ":" << pointsArray.element(0).getY() << endl;
cout << pointsArray.element(1).getX() << ":" << pointsArray.element(1).getY() << endl;
pointsArray.element(0).move(25, 30);
pointsArray.element(1).move(35, 40);
cout << pointsArray2.element(0).getX() << ":" << pointsArray2.element(0).getY() << endl;
cout << pointsArray2.element(1).getX() << ":" << pointsArray2.element(1).getY() << endl;
cout << pointsArray.element(0).getX() << ":" << pointsArray.element(0).getY() << endl;
cout << pointsArray.element(1).getX() << ":" << pointsArray.element(1).getY() << endl;

return 0;
}

C++ 进阶笔记
https://anonymouslosty.ink/2023/03/31/C++ 进阶笔记/
作者
Ling yi
发布于
2023年3月31日
更新于
2023年4月7日
许可协议