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