数组与指针

🗨️字数统计=2.6k字 ⏳阅读时长≈11分钟

一、数组

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
class Point{
public:
Point(float xx = 0,float yy = 0):x(xx),y(yy){ }
float getX() const {return x;}
float getY() const {return y;}
private:
float x,y;
};
//直线线性拟合,number为点数
float lineFit(const Point points[], int number){
float avgx = 0, avgy = 0;
float lxx = 0,lyy = 0, lxy = 0;
for(int i =0;i<number;i++){
avgx += points[i].getX()/number;
avgy += points[i].getY()/number;
}
for(int i = 0;i<number;i++){
lxy += (points[i].getX() - avgx) * (points[i].getY() - avgy);
lyy += (points[i].getY() - avgy) * (points[i].getY() - avgy);
lxx += (points[i].getX() - avgx) * (points[i].getX() - avgx);
}
cout<<"This line can be fitted by y=ax+b."<<endl;
//输出回归系数a,b
cout<<"a = "<<lxy/lxx<<" "<<"b = "<<avgy-lxy*avgx/lxx<<endl;
return (float)(lxy/sqrt(lxx*lyy)); //线性相关的密切程度
}
int main(){
Point points[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(points,10);
cout<<"Line coefficient r = "<<r<<endl;
}



二、指针

1. 指针运算符 * 和 &

  • * 称为指针运算符,也称解析;
  • & 称为取地址运算符;



2. 指针变量的赋值运算


3. 指针的类型

指向常量的指针 const int * p


* 在const的右边

指针类型的常量 int * const p

* 在const的左边

4. 指针的算法运算、关系运算


5. 用指针访问数组元素



6. 指针数组 Point *p[2]


7. 指针与函数

指针作为函数参数

为什么需要用指针做参数?

  • 需要数据双向传递时(引用也可以达到此效果);

    用指针作为函数的参数,可以使被调函数通过形参指针存取主调函数中实参指针指向的数据,实现数据的双向传递
  • 需要传递一组数据,只传首地址运行效率比较高 ;

    实参是数组名时,形参可以是指针


引用和指针有何区别?何时只能用指针而不能使用引用

  • 引用是一个别名,不能为空,不能被重新分配;指针是一个存放地址的变量。
  • 当需要对变量重新赋以另外的地址或赋值为NULL时只能使用指针

指针类型的函数

若函数的返回值是指针,该函数就是指针类型的函数

1
2
int * function(){
}

函数类型的指针

即指向函数的指针

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

int (*func)(int, int)

含义:函数指针指向的是程序代码存储区。

)

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
#include <iostream>
using namespace std;
int compute(int a, int b, int (*func)(int, int))
{
return func(a, b);
}
int max(int a, int b) // 求最大值
{11
return ((a > b) ? a : b);
}
int min(int a, int b) // 求最小值
{
return ((a < b) ? a : b);
}
int sum(int a, int b) // 求和
{
return a + b;
}
int main()
{
int a, b, res;
cout << "请输入整数a:";
cin >> a;
cout << "请输入整数b:";
cin >> b;
res = compute(a, b, &max);
cout << "Max of " << a << " and " << b << " is " << res << endl;
res = compute(a, b, &min);
cout << "Min of " << a << " and " << b << " is " << res << endl;
res = compute(a, b, &sum);
cout << "Sum of " << a << " and " << b << " is " << res << endl;
}

8. 对象指针

定义形式:类名 *对象指针名

1
2
3
Point *ptr;
Point a(5,10);
ptr = &a;

对象指针通过 -> 访问对象成员

ptr->getx() 相当于 (*ptr).getx();

9. this 指针

10. 动态内存分配

首先我们需要分清几个概念
int *point = new int(5); //用5初始化
int *point = new int(); //用0初始化
int *point = new int; //不分配初值
int *point = new int[5] //表示为该指针分配包含十个元素的地址空间


同一个程序有可能会处理很大的数据,有时处理很小,若总是以最大的空间内存分配,难免会造成浪费。

  • 在C语言中,用 malloc 进行动态内存分配;
  • 在C++中,用 new 语句

new 动态分配

动态分配一个变量

1
2
3
4
5
6
p = new T; //T代表int,double,float等等
//p类型为T*的指针
//该语句动态分配了一片大小为 sizeof(T) 字节的内存空间,并将起始地址赋给了p
int *pn;
pn = new int;
*pn = 5; //往刚开辟的空间中写入了数据5


动态分配一个数组

1
2
3
4
5
6
7
p = new T[N];
//动态分配了一片大小为 sizeof(T)*N 字节的内存空间,并将起始地址赋给了p
int *pn;
int i = 5;
pn = new int[i*20]; //100个元素,最大合法下标为99
pn[0] = 20;
pn[100] = 30; // 编译没问题,运行时数组越界

注:new运算符返回值类型都是 T*


delete 释放动态分配的内存

delete总是和new成对出现 :即动态分配的内存一定要释放掉,否则占用的内存就会越来越多。

delete指针

该指针必须指向new出来的空间

1
2
3
4
int *p = new int;
*p = 5;
delete p;
delete p; //wrong; 同一片空间不能被delete两次


delete [ ]指针

1
2
3
int *p = new int[20];
p[0] = 1;
delete []p; //若delete p 则只删除了一部分,没有删干净



三、自定义动态数组类 ArrofPoints

将数组的建立和删除过程封装起来,数组下标越界检查

四、Vector类模板(C++标准库中的)

为什么需要vector

  • 封装任何类型的动态数组,自动创建和删除
  • 数组下标越界检查


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

  • vector<int> arr(5) 建立大小为5的int数组
  • vector<int> arr(5,2) 大小为5的int类型数组,所有元素用2初始化


vector对象的使用

  • 对数组元素的引用 :

    与普通数组具有相同形式: vector对象名 [ 下标表达式 ]

vector数组对象名不表示数组首地址 ,因为数组对象不是数组,而是封装了数组的对象

  • 获得数组长度 :用size函数 数组对象名.size()


vector应用举例:

五、深复制与浅复制

以上面的自定义动态数组类 ArrayOfPoints 为例

浅复制

ArrayOfPoints pointsArray2(pointsArray1); //创建副本

  • pointsArray1和pointsArray2有相同的值,表面上好像完成了复制,但是指向的是同一个内存空间,并没有形成真正的副本,当程序中改变pointsArray1时也会改变pointsArray2。
  • 而且,在程序结束之前,pointsArray1和pointsArray2的析构函数会自动调用,动态分配的内存空间被释放,由于两个对象给公用了同一块内存空间,因此该空间被释放两次,导致运行错误。

深复制(重新编写复制构造函数)

核心思想:重新new一个存储空间进行值的复制操作

六、字符串

详见第一章 【C++复习总结回顾】—— 【一】基础知识+字符串/string类

七、手撸String类

设计一个字符串类MyString,具有构造函数、析构函数、复制构造函数、并重载运算符 +、=、+=、[ ],尽可能的完善它,使之能满足各种需要。

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
#include<iostream>
#include<cstring>
using namespace std;
class Mystring{
private:
char* mystring; //字符指针
int len; //字符串长度
Mystring(int clen){ //私有构造,防止其他类进行调用创建实例,只使用一次(在+重载)
mystring = new char[clen + 1];
for (int i = 0; i < clen; i++)
mystring[i] = '\0';
len = clen;
}
public:
Mystring(); //无参构造
Mystring(const char* const cmystring); //带参构造
Mystring(const Mystring& rhs); //复制构造
~Mystring(); //析构
int getLen() const{ //获取长度
return this->len;
}
const char *GetMyString() const{ //获取字符串
return mystring;
}
char & operator[](int offset); //重载下标运算符,作为左值可被修改,需返回引用
char operator [](int offset) const; //重载下标运算符,作为右值仅能访问
Mystring operator +(const Mystring& rhs); //a+b a的值并不会被修改,不需要返回引用
void operator +=(const Mystring& rhs); //a+=b 无返回值
Mystring& operator =(const Mystring& rhs); //a=b a的值会被修改,需要返回引用
};
//无参构造
Mystring::Mystring(){
mystring = new char[1];
mystring[0] = '\0';
len = 0;
}
//带参构造
Mystring::Mystring(const char* const cmystring){
len = strlen(cmystring);
mystring = new char[len+1];
for(int i = 0; i<len; i++)
mystring[i] = cmystring[i];
mystring[len] = '\0';
}
//复制构造
Mystring::Mystring(const Mystring& rhs){
len = rhs.getLen();
mystring = new char[len+1];
for(int i = 0; i<len;i++)
mystring[i] = rhs[i];
mystring[len] = '\0';
}
//析构
Mystring::~Mystring(){
delete[] mystring;
len = 0;
}
char& Mystring::operator[](int offset){
if(offset>len) //若超出最大长度,返回最后一位字符
return mystring[len-1];
else
return mystring[offset];
}
char Mystring::operator[](int offset) const
{
if (offset > len) //若超出最大长度,返回最后一位字符
return mystring[len - 1];
else
return mystring[offset];
}
//字符串连接+重载
Mystring Mystring :: operator +(const Mystring& rhs){
int totallen = len + rhs.getLen();
Mystring str(totallen);
int i = 0;
for(;i<len;i++)
str[i] = mystring[i];
for(int j = 0;j<rhs.getLen();i++,j++)
str[i] = rhs[j];
str[totallen] = '\0';
return str;
}
void Mystring::operator+=(const Mystring& rhs){
int totallen = len + rhs.getLen();
Mystring str(totallen);
int i = 0;
for(;i<len;i++)
str[i] = mystring[i];
for (int j = 0; j < rhs.getLen(); i++, j++)
str[i] = rhs[j];
str[totallen] = '\0';
*this = str;
}
Mystring &Mystring ::operator=(const Mystring &rhs)
{
delete[] mystring;
len = rhs.getLen();
mystring = new char[len + 1];
for (int i = 0; i < len; i++)
mystring[i] = rhs[i];
mystring[len] = '\0';
return *this;
}


测试:

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
int main()
{
Mystring s1("initial test");
cout << "S1:\t" << s1.GetMyString() << endl;
char *temp = "Hello World";
s1 = temp;
cout << "Now,S1 is replaced by Hello World" << endl;
cout << "S1:\t" << s1.GetMyString() << endl;
char s2[20];
strcpy(s2, "; I like you!");
cout << "S2:\t" << s2 << endl;
s1 += s2;
cout << "S1+S2:\t" << s1.GetMyString() << endl;
cout << "S1[4]:\t" << s1[4] << endl;
s1[4] = 'x';
cout<<"now s1[4] is repalced by x"<<endl;
cout << "S1:\t" << s1.GetMyString() << endl;
cout << "S1[999]:\t" << s1[999] << endl;
Mystring s3(" Another myString");
cout << "S3:\t" << s3.GetMyString() << endl;
Mystring s4;
s4 = s1 + s3;
cout << "S1+S3:\t" << s4.GetMyString() << endl;
return 0;
}


分享到