天天看点

C++数组、指针与字符串(三)C++数组、指针与字符串

C++数组、指针与字符串

1.指针访问控制数组元素

定义指向数组元素的指针

定义与赋值

例:int a[10],*pa;
    pa=&a[0];或pa=a;
           

等效的形式

*pa就是a[0], (pa+1)就是a[1],…, *(pa+i)就是a[i]

a[i],*(pa+i), *(a+i) pa[i]都是等效的。

2.指针数组

指针数组:数组的元素是指针类型

例:Point *pa[2];
由pa[0]、pa[1]两个指针组成
           
例:利用指针数组存放矩阵
#include"iostream"
using namespace std;
int main() {
	int line1[] = { 1,0,0 };//矩阵的第一行
	int line2[] = { 0,1,0 };//矩阵的第二行
	int line3[] = { 0,0,1 };//矩阵的第三行

	//定义整型指针数组并初始化
	int* pLine[3] = { line1,line2,line3 };
	cout << "Matrix test:" << endl;
	//输出矩阵
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++)
			cout << pLine[i][j] << " ";
		cout << endl;
	}
	return 0;
}
运行结果:
Matrix test:
1 0 0
0 1 0
0 0 1
           

3.以指针作为函数参数

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

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

需要传递一组数据,只传首地址运行效率比较高

例:读入三个浮点数,将整数部分和效数部分分别输出。
#include"iostream"
using namespace std;

//将整数x分成整数部分和小数部分,形参intpart、fracpart是指针
void splitFloat(float x, int* intPart, float* fracPart) {
	*intPart = static_cast<int>(x);//取x的整数部分
	*fracPart = x - *intPart;//取x的小数部分
}

int main() {
	cout << "Enter 3 float point number:" << endl;
	for (int i = 0; i < 3; i++) {
		float  x, f;
		int n;
		cin >> x;
		splitFloat(x, &n, &f);//变量地址作为实参
		cout << "Integer Part =" << n << "Fraction Part=" << f << endl;
	}
	return 0;
}
运行结果:
Enter 3 float point number:
112.5 16.92 13.65
Integer Part =112Fraction Part=0.5
Integer Part =16Fraction Part=0.92
Integer Part =13Fraction Part=0.65

           
例:指向常量的指针做形参
#include"iostream"
using namespace std;
const int N = 6;
void print(const int* p, int n);
int main() {
	int array[N];
	for (int i = 0; i < N; i++)
		cin >> array[i];
	print(array, N);
	return 0;
}
void print(const int* p, int n) {
	cout << "{" << *p;
	for (int i = 1; i < n; i++)
		cout << "," << *(p + i);
	cout << "}" << endl;
}
           

4.指针类型的函数

指针函数的定义形式:

存储类型 数据类型 *函数名(){

//函数体语句

}

注意:不要将非静态局部地址用作函数的返回值。

例:在子函数中定义局部变量后将其地址返回给主函数,就是非法地址。

int main(){
  int* function();
  int* ptr=function();
  *prt=5;//危险的访问
  return 0;
}
int* function(){
  int local=0;//非静态局部变量作用域和寿命都仅限于本函数体内
  return &local;
}//函数运行结束时,变量local被释放
           

注意:返回的指针要确保在主调函数中是有效、合法的地址。

例:主函数中定义的数组,在子函数中对照该数组元素进行某种操作,返回其中一个元素的地址,着就是合法有效的地址。
#include"iostream"
using namespace std;
int main() {
	int array[10];//主函数中定义的数组
	int* search(int* a, int num);
	for (int i = 0; i < 10; i++)
		cin >> array[i];
	int* zeroptr = search(array, 10);//将主函数中数组的首地址传给子函数
	return 0;
}
int* search(int* a, int num) {//指针a指向主函数中定义的数组
	for (int i = 0; i < num; i++)
		if (a[i] == 0)
			return &a[i];//返回的地址指向的元素是主函数中定义的
}//函数运行结束时,a[i]的地址仍有效

           
正确例子:在子函数中通过动态内存分配new操作取得的内存地址返回给主函数是合法有效的,但是内存分配和释放不再同一级别,要注意不要忘记释放,避免内存泄漏。
#include"iostream"
using namespace std;
int main() {
	int* newintvar();
	int* intptr = newintvar();
	*intptr = 5;//访问合法有效地址
	delete intptr;//如果忘记释放,会造成内存泄漏
	return 0;
}
int* newintvar() {
	int* p = new int();
	return p;//返回的地址指向的是动态分配空间
}//函数运行结束时,p中的地址仍然有效
           

5.指向函数的指针

函数指针的定义

定义形式

存储类型 数据类型 (*函数指针名)();

含义

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

函数指针的典型用途——实现函数回调

通过函数指针调用的函数

例如将函数的指针作为参数传递给一个函数,使得在处理相似事件的时候可以灵活的使用不同的方法。

调用者不关心谁是被调用者

​ 需要知道一个具有特定原型和限制条件的被调用函数。

函数指针举例:
编写一个计算函数compute,对两个整数进行各种计算。有一个形参为指向具体算法函数的指针,根据不同的实参函数,用不同的算法进行计算。
编写三个函数:求两个 整数的最大值、最小值、和。分别用这三个函数作为实参,测试compute函数
#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)//求最大值
{
	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;
}
运行结果:
请输入整数a:30
请输入整数b: 25
Max of30and25is30
Min of30and25is25
Sum of30and25is55
           

6.对象指针

对象指针定义形式

类名 *对象指针名;

例:Point a(5,10);
       Point *ptr;
       ptr=&a;
           

通过指针访问对象成员

对象指针名->成员名

例子:

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

例:使用指针来访问Point类的成员
#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 a(4, 5);
	Point* p1 = &a;//定义对象指针,用a的地址初始化
	cout << p1->getX()<< endl;//用指针访问对象成员
	cout << a.getX()<< endl;//用对象名访问对象成员
	return 0;
}
           

this指针

隐含与类的每一个非静态成员函数中。

指出成员函数所操作的对象。

当通过一个对象调用成员函数时,系统先将该对象的地址赋给this指针,然后调用成员函数,成员函数对对象的数据成员进行操作时,就隐含使用this指针。

例如:Point类的getX函数中的语句:
return x;
相当于:
return this->x;
           

7.动态分配与释放内存

动态申请内存操作符new

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

功能:在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值。

结果值:

成功:T类型指针,指向新分配的内存; 失败:抛出异常。

释放内存操作符delete

delete指针p

功能:释放指针p所指向的内存。p必须是new操作的返回值。

例:动态创建对象举例
#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 << "construct ralled." << 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 = new Point;//调用构造函数
	delete ptr1;//s删除对象,自动调用析构函数

	cout << "Step two:" << endl;
	ptr1 = new Point(1, 2);
	delete ptr1;
	return 0;
}
运行结果:
Step one :
Default Constructor called.
Destructor called.
Step two :
construct ralled.
Destructor called.
           

8.申请和释放动态数组(一)

分配和释放动态数组

分配:new 类型名T[数组长度]

数组长度可以是任何整形类型表达式,在运行时计算

释放:delete[] 数组名p

释放指针p所指向的数组

p必须是用new分配得到的数组首地址。

例:动态创建对象数组
#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 << "construct ralled." << 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() {
	Point* ptr = new Point[2];//创建对象数组
	ptr[0].move(5, 10);//通过指针访问数组元素的成员
	ptr[1].move(15, 20);//通过指针访问数组元素成员
	cout << "Deleting..." << endl;
	delete[] ptr;//删除整个对象数组
	return 0;
}
运行结果:
Default Constructor called.
Default Constructor called.
Deleting...
Destructor called.
Destructor called.
           

动态创建多维数组

new  类型名T[第一维长度][第二维长度]....;
           

如果内存申请成功,new运算返回一个指向新分配内存首地址的指针。

例如:
char (*fp)[3];
fp = new char[2][3];
           
例:动态创建多维数组
#include"iostream"
using namespace std;
int main() {
	int(*cp)[9][8] = new int[7][9][8];
	for (int i = 0; i < 7; i++) 
		for (int j = 0; j < 9; j++) 
			for (int k = 0; k < 8; k++)
				*(*(*(cp + i) + j) + k) = (i * 100 + j * 10 + k);
			for (int i = 0; i < 7; i++) {
				for(int j = 0;j < 9; j++) {
				    for (int k = 0; k < 8; k++)
					    cout << cp[i][j][k] << " ";
				     cout << endl;
			}
			cout << endl;
		}
		delete[] cp;
		return 0;
	}
           

9.智能指针

c++11的智能指针

unique_ptr:

不允许多个指针共享资源,可以用标准库中的move函数转移指针

share_ptr:

多个指针共享资源

weak_ptr:

可复制shared_ptr,但其构造或者释放对资源不产生影响

10.VECTOR对象

vector标准库的类模版

VECTOR对象的定义

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

例:
vector<int> arr(5)
建立大小为5的int数组
           

vector对象的使用

对数组元素的引用

与普通数组具有相同形式:

vector对象名 [下标表达式]

vector数组对象名不表示数组首地址

获得数组长度

用size函数

vector对象名.size()

例:vector举例
#include"iostream"
#include"vector"
using namespace std;
//计算数组arr中元素的平均值
double average(const vector<double>& arr) {
	double sum = 0;
	for (unsigned i = 0; i < arr.size(); i++)
		sum += arr[i];
	return sum / arr.size();
}
int main() {
	unsigned n;
	cout << "n=";
	cin >> n;

	vector<double>arr(n);//创建数组对象
	cout << "Please input" << n << "real numbers:" << endl;
	for (unsigned i = 0; i < n; i++)
		cin >> arr[i];

	cout << "Average=" << average(arr) << endl;
	return 0;
}
           
例:基于范围for循环配合auto举例
#include<vector>
#include<iostream>
int main()
{
	std::vector<int>v = { 1,2,3 };
	for (auto i = v.begin(); i != v.end(); ++i)
		std::cout << *i << std::endl;
	for (auto e : v)
		std::cout << e << std::endl;
}
           

11.深层复制与浅层复制

浅层复制

实现对象间数据元素的一一对应复制

深层复制

当被复制的对象数据成员是指针类型时,不是复制该指针成员本身,而是将指针所指对象进行复制

12.移动构造

移动构造

C++11标准中提供了一种新的构造方法——移动构造

C++11之前,如果要将源对象的状态转移到目标对象只能通过复制。在某些情况下,没有必要复制对象——只需要移动他们

C++11引入移动语义:

源对象资源的控制权全部交给目标对象

移动构造函数

class_name(class_name &&)

例:函数返回含有指针成员的对象(使用复制构造)
#include"iostream"
using namespace std;
class IntNum {
public:
	IntNum(int x = 0) : xptr(new int(x)) {//构造函数
		cout << "Calling constructor..." << endl;
	}
	IntNum(const IntNum& n) :xptr(new int(*n.xptr)) {//复制构造函数
		cout << "Calling copy constructor..." << endl;
	};
	~IntNum() {//析构函数
		delete xptr;
		cout << "Destructing..." << endl;
	}
	int getInt() { return *xptr; }
private:
	int* xptr;
};
//返回值为IntNum类对象
IntNum getNum() {
	IntNum a;
	return a;
}
int main() {
	cout << getNum().getInt() << endl;
	return 0;
}
运行结果:
Calling constructor...
Calling copy constructor...
Destructing...
0
Destructing...

//使用移动构造
#include "iostream"
using namespace std;
class IntNum {
public:
	IntNum(int x = 0) : xptr(new int(x)) {//构造函数
		cout << "Calling constructor..." << endl;
	}
	IntNum(const IntNum& n):xptr(new int(*n.xptr)) {//复制构造函数
		cout << "Calling copy constructor..." << endl;
	}
	IntNum(IntNum&& n) :xptr(n.xptr) {//移动构造函数
		n.xptr = nullptr;
		cout << "Calling move constructor..." << endl;
	}
	~IntNum() {//析构函数
		delete xptr;
		cout << "Destructing..." << endl;
	}
	int getInt() { return *xptr; }
private:
	int* xptr;
};
//返回值为IntNum类对象
IntNum getNum() {
	IntNum a;
	return a;
}
int main() {
	cout << getNum().getInt() << endl;
	return 0;
}
运行结果:
Calling constructor...
Calling move constructor...
Destructing...
0
Destructing...

           

13.C风格字符串

字符串常量

例:“program”

各字符连续、顺序存放,每个字符占一个字节,以‘\0’结尾,相当于一个隐含创建的字符常量数组

“program”出现在表达式中,表示这一char数组的手地址

首地址可以赋给char常量指针:

const char *STRING1=“program”

用字符数组存储字符串(C风格字符串)

例如:
char str[8]={'p','r','o','g','r','a','m','\0'};
char str[8]="program";
char str[]="program";
           

用字符数组表示字符串的缺点

执行连接、拷贝、比较等操作,都需要显式调用库函数,当字符串长度不确定时,需要用new动态创建字符数组,最后要用delete释放,字符串事迹长度大于它分配的空间时,会产生数组下标越界的错误。

14.string类

string类常用的构造函数

string();//默认构造函数,建立一个长度为0的串

string(const char *s);//用指针s所指向的字符串常量初始化string对象

string(const string& rhs);//复制构造函数

例: string类应用举例
#include"string"
#include"iostream"
using namespace std;

//根据value的值输出true或false
//title为提示文字
inline void test(const char* title, bool value)
{
	cout << title << "returns" << (value ? "ture" : "false") << endl;
}
int main() {
	string s1 = "DEF";
	cout << "s1 is" << s1 << endl;
	string s2;
	cout << "Please enter s2:";
	cin >> s2;
	cout << "length of s2:" << s2.length() << endl;

	//比较运算符的测试
	test("s1<=\"ABC\"", s1 <= "ABC");
	test("\"DEF\"<=s1", "DEF" <= s1);

	//连接运算符的测试
	s2 += s1;
	cout << "s2=s2+s1:" << s2 << endl;
	cout << "length of s2:" << s2.length() << endl;
	return 0;
}
           

输入整行字符串

getline可以输入整行字符串(要包含string头文件)。

例如:
getline(cin,s2);
           

输入字符串时,可以使用其它分隔符作为字符串结束的标志(例如逗号、分号),将分隔符作为getline的第3个参数即可。

例如:
getline(cin,s2,',');
           
例:用getline输入字符串
#include"iostream"
#include"string"
using namespace std;
int main() {
	for (int i = 0; i < 2; i++){
		string city, state;
	    getline(cin, city, ',');
		getline(cin, state);
		cout << "City:" << city << "State:" << state << endl;
	}
	return 0;
}
运行结果:
Beijing, China
City : BeijingState:China

San Francisco, the Ynited States
City :
San FranciscoState : the Ynited States
           

继续阅读