c++(six_2)
Contents
数组 指针与字符串
动态内存分配
动态申请内存操作符 new
-
new 类型名T(初值列表)
-
功能:
在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值
-
结果值:
成功:T类型的指针,指向新分配的内存。失败:0(NULL)
int *point; point = new int (2); Point *ptr; ptr = new Point;
-
-
**注意:**堆对象的生命周期是整个程序生命期
-
如果建立的对象是某一个类的实例对象,就是要根据实际情况调用该类的构造函数
-
释放内存操作符delete
delete 指针名
**功能:**释放指针 P 所指向的内存。P必须是new操作的返回值
例:动态存储分配举例
#include<iostream>
using namespace std;
class Point
{
public:
Point ( ) { X =Y = 0;
cout <<“Default constructor call.”<<endl ;
}
Point (int xx, int yy) {
X = xx; Y = yy;
cout<<“constructor call.”<<endl ; }
~point ( ) {
cout <<“Destructor call.”<<endl ;
}
int GetX( ) { return X; }
int GetY( ) { return Y;}
void Move ( int x, int y) { X = x; Y = y; }
private:
int X, Y;
};
void main ( )
{
cout<<“Step One:”<<endl;
Point *p1 = new Point;
delete p1;
cout<<“Step One:”<<endl;
p1 = new Point (1,2);
delete p1;
};
//程序运行结果如下:
//Step One:
//Default constructor call.
//Destructor call.
//Step One:
//constructor call.
//Destructor call.
动态内存分配
-
用new创建一维数组
new 类型名T [ 下标表达式 ] ;
- 下标表达式表示数组元素的个数
- 动态为数组分配内存时不能指定数组元素的初值
-
结果值
- 成功:返回指向新分配内存首地址的 T 类型的指针
- 失败:返回空指针 0 (NULL)
例: 动态存储分配举例
#include<iostream>
using namespace std;
class Point
{ public:
Point ( ) { X =Y = 0;
cout <<“Default constructor call.”<<endl ;
}
Point (int xx, int yy) {
X = xx; Y = yy;
out <<“constructor call.”<<endl ;
}
~point ( ) {cout<<“Destructor call.”<<endl ; }
int GetX( ) { return X; }
int GetY( ) { return Y;}
void Move ( int x, int y){ X = x; Y = y; }
private:
int X, Y;
};
void main ( )
{ Point *ptr = new Point [2]; a //创建对象数组
ptr[ 0 ].Move ( 5, 10) ;
ptr[ 1 ].Move ( 15, 20);
//或者这种形式也可以
//ptr -> Move ( 5, 10) ;
//( ptr + 1) -> Move ( 15, 20);
delete [ ] ptr; //删除整个对象数组
};
//运行结果如下:
//Default constructor call.
//Default constructor call.
//Destructor call.
//Destructor call.
注意: 如果是用 new 建立的数组, 用 delete 删除时必须在指针名前面加 “ [ ]”
-
指向多维数组的指针和指针变量
使用vector创建数组对象
- vector定义动态数组的格式:
- vector <元素类型> 数组名(数组长度[,元素初值]);
- 注意
- 用vector 创建的数组对象都会被初始化,如果是基本数据类型,则要会被0初始化,如果是类类型,则要调用类的默认构造函数,这时就要保证类中一定要有默认构造函数,当然用户也可以自己为它赋值,但赋的初值都是一样的。
- 数组名不是首地址
- 访问方式与普通数组的方式一样
例:
#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;}
深复制与浅复制
无错误的浅拷贝
-
先看一个简单的例子,该例子是浅拷贝的典型代表,而且没有问题。
-
接下来看Example 1
Cperson.h
#ifndef _CPERSON_H #define _CPERSON_H class Cperson { public: Cperson(int age); void Print(void); private: int m_age; }; #endif
Cperson.cpp
#include"Cperson.h" #include<iostream.h> Cperson::Cperson(int age):m_age(age) { } void Cperson::Print(void) { cout<<"My age is "<<m_age<<endl; }
Main.cpp
#include"Cperson.h" #include<iostream.h> void main(void) { Cperson Tom(10); Tom.Print(); Cperson Jim(Tom); Jim.Print(); }
结果
-
结果分析
- 对于语句Cperson Jim(Tom),我们并没有定义相应的拷贝构造函数,编译器将会自动生成一个默认的拷贝构造函数
- 默认拷贝构造函数所做的工作是,将一个对象的全部数据成员赋值给另一个对象的数据成员
- C++只把对象数据成员简单赋值这种情况称为“浅拷贝”
- 听起来,编译器似乎很好,会提供一个我们没有定义的拷贝构造函数
有错误的浅拷贝
-
在接下来的Example 2中,我们将看到浅拷贝带来的错误
Cperson.h
#ifndef _CPERSON_H #define _CPERSON_H class Cperson { public: Cperson(int age,char *name); ~Cperson(); void Print(void); private: int m_age; char *m_name; }; #endif
Cperson.cpp
#include"Cperson.h" #include<iostream.h> #include<string.h> Cperson::Cperson(int age,char *name) { m_name = new char[strlen(name) + 1]; if(m_name != NULL) { strcpy(m_name,name); } m_age = age; cout<<m_name<<"的构造函数"<<endl; } Cperson::~Cperson() { cout<<"析构姓名:"<<m_name<<endl; if(m_name != NULL) { delete m_name; } } void Cperson::Print(void) { cout<<"My age is "<<m_age<<",My name is "<<m_name<<endl; }
Main.cpp
#include"Cperson.h" #include<iostream.h> void main(void) { Cperson Tom(10,"Tom"); Tom.Print(); Cperson Jim(Tom); Jim.Print(); }
结果
-
结果分析
- 可以看到,程序出现了错误
- 在执行语句Cperson Tom(10,“Tom”)时,用new动态开辟了一段内存,用来存放”Tom”
- 在执行Cperson Jim(Tom)时,只是将Tom的成员(Tom.m_age,Tom.m_name)赋值给Jim相应的成员
- 此时,Tom.m_name和Jim.m_name指向同一内存空间,然而,系统并没给Jim.m_name开辟相应的内存空间
- 执行完Jim.Print()后,开始执行析构函数,析构函数的执行顺序和对象构造函数的执行顺序相反,所以先执行Jim的析构函数,执行完Jim的析构函数后,Jim.m_name所指的内存空间已经释放
- 接着执行Tom的析构函数,此时就会出现问题,即在释放Tom.m_name所指的内存空间时会出现问题,因为这段内存空间在Jim的析构函数中已经释放过了
- 出现这种原因的根本在于“浅拷贝”,所以需要定义自己的构造函数。
深拷贝
-
接下来看Example 3,在该例子中执行深拷贝
Cperson.h
#ifndef _CPERSON_H #define _CPERSON_H class Cperson { public: Cperson(int age,char *name); Cperson(Cperson & per); ~Cperson(); void Print(void); private: int m_age; char *m_name; }; #endif
Cperson.cpp
#include"Cperson.h" #include<iostream.h> #include<string.h> Cperson::Cperson(int age,char *name) { m_name = new char[strlen(name) + 1]; if(m_name != NULL) { strcpy(m_name,name); } m_age = age; cout<<m_name<<"的构造函数"<<endl; } Cperson::Cperson(Cperson & per) { m_name = new char[strlen(per.m_name) + 1]; if(m_name != NULL) { strcpy(m_name,per.m_name); } m_age = per.m_age; cout<<m_name<<"的拷贝构造函数"<<endl; } Cperson::~Cperson() { cout<<"析构姓名:"<<m_name<<endl; if(m_name != NULL) { delete m_name; } } void Cperson::Print(void) { cout<<"My age is "<<m_age<<",My name is "<<m_name<<endl; }
Main.cpp
#include"Cperson.h" #include<iostream.h> void main(void) { Cperson Tom(10,"Tom"); Tom.Print(); Cperson Jim(Tom); Jim.Print(); }
结果:
-
结果分析
- 可以看到,自己定义了拷贝构造函数后,就没有问题了
浅拷贝和深拷贝
- 深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
- 在实际中应该避免浅拷贝
例: 对象的浅拷贝
#include <iostream>
#include <cassert>
using namespace std;
class Point {
//类的声明同例6-16
//……
};
class ArrayOfPoints {
//类的声明同例6-18
//……
};
int main() {
int count;
cout << "Please enter the count of points: ";
cin >> count;
ArrayOfPoints pointsArray1(count); //创建对象数组
pointsArray1.element(0).move(5,10);
pointsArray1.element(1).move(15,20);
ArrayOfPoints pointsArray2(pointsArray1); //创建副本
cout << "Copy of pointsArray1:" << endl;
cout << "Point_0 of array2: " << pointsArray2.element(0).getX() << ", "<< pointsArray2.element(0).getY() << endl;
cout << "Point_1 of array2: " << pointsArray2.element(1).getX() << ", "<< pointsArray2.element(1).getY() << endl;
pointsArray1.element(0).move(25, 30);
pointsArray1.element(1).move(35, 40);
cout << "After the moving of pointsArray1:" << endl;
cout << "Point_0 of array2: " << pointsArray2.element(0).getX() << ", "<< pointsArray2.element(0).getY() << endl;
cout << "Point_1 of array2: " << pointsArray2.element(1).getX() << ", "<< pointsArray2.element(1).getY() << endl;
return 0;
}
//运行结果如下:
//Please enter the number of points:2
//Default Constructor called.
//Default Constructor called.
//Copy of pointsArray1:
//Point_0 of array2: 5, 10
//Point_1 of array2: 15, 20
//After the moving of pointsArray1:
//Point_0 of array2: 25, 30
Point_1 of array2: 35, 40
Deleting...
Destructor called.
Destructor called.
Deleting...
//接下来程序出现异常,也就是运行错误
例: 对象的深拷贝
#include <iostream>
#include <cassert>
using namespace std;
class Point { //类的声明同例6-16 ……
};
class ArrayOfPoints {
public:
ArrayOfPoints(const ArrayOfPoints& pointsArray);
//其他成员同例6-18
};
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() {
//同例6-20
}
//程序的运行结果如下:
//Please enter the number of points:2
//Default Constructor called.
//Default Constructor called.
//Default Constructor called.
//Default Constructor called.
//Copy of pointsArray1:
//Point_0 of array2: 5, 10
//Point_1 of array2: 15, 20
//After the moving of pointsArray1:
//Point_0 of array2: 5, 10
//Point_1 of array2: 15, 20
//Deleting...
//Destructor called.
//Destructor called.
//Deleting...
字符串
用字符数组存储和处理字符串
- 字符串常量(例:“program”)
- 各字符连续、顺序存放,每个字符占一个字节,以‘\0’结尾,相当于一个隐含创建的字符常量数组
- “program”出现在表达式中,表示这一char数组的首地址
- 首地址可以赋给char常量指针:
- const char *STRING1 = “program”;
- 字符串变量
- 可以显式创建字符数组来表示字符串变量,例如,以下三条语句具有等价的作用
- char str[8] = { ‘p’, ‘r’, ‘o’, ‘g’, ‘r’, ‘a’, ’m’, ‘\0’ };
- char str[8] = “program”;
- char str[] = “program”;
用字符数组表示字符串的缺点
- 用字符数组表示字符串的缺点
- 执行连接、拷贝、比较等操作,都需要显式调用库函数,很麻烦
- 当字符串长度很不确定时,需要用new动态创建字符数组,最后要用delete释放,很繁琐
- 字符串实际长度大于为它分配的空间时,会产生数组下标越界的错误
- 解决方法
- 使用字符串类string表示字符串
- string实际上是对字符数组操作的封装
- 类string在string头文件中
string类
-
常用构造函数
string(); //缺省构造函数,建立一个长度为0的串 string(const char *s); //用指针s所指向的字符串常量初始化string类的对象 string(const string& rhs); //拷贝构造函数 string(const char *s,unsigned int n); //用指针s所指向的字符串前n个字符去初始化string类的对象 String(unsigned int n, char c);//将参数c中的 字符重复n次去初始化string类的对象。 string(const string& rhs, unsigned int pos, unsigned int n);//将rhs字符串从pos位置开始取n个字符去初始化string类的对象
-
例:
string s1; //建立一个空字符串 string s2 = “abc”; //用常量建立一个初值为”abc”的字符串 string s3 = s2;//执行拷贝构造函数,用s2的值作为s3的初值
-
常用操作符
s + t //将串s和t连接成一个新串 s = t //用t更新s s == t //判断s与t是否相等 s != t //判断s与t是否不等 s < t //判断s是否小于t(按字典顺序比较) s <= t //判断s是否小于或等于t (按字典顺序比较) s > t //判断s是否大于t (按字典顺序比较) s >= t //判断s是否大于或等于t (按字典顺序比较) s[i] //访问串中下标为i的字符
-
例:
string s1 = "abc", s2 = "def"; string s3 = s1 + s2; //结果是"abcdef" bool s4 = (s1 < s2); //结果是true char s5 = s2[1]; //结果是'e'
例: string类应用举例
#include <string>
#include <iostream>
using namespace std;
//根据value的值输出true或false,title为提示文字
void test(const char *title, bool value)
{
cout << title << " returns " << (value ? "true" : "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输入整行字符串
-
输入整行字符串
-
用cin的»操作符输入字符串,会以空格作为分隔符,空格后的内容会在下一回输入时被读取
-
用string头文件中的getline可以输入整行字符串,例如:
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: Beijing State: China
//San Francisco,the United States
//City: San Francisco State: the United