Contents

c++(six_2)

数组 指针与字符串

动态内存分配

https://cdn.jsdelivr.net/gh/adan-ning/images/202405071754763.png

动态申请内存操作符 new

  1. new 类型名T(初值列表)

    1. 功能:

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

    2. 结果值:

      成功:T类型的指针,指向新分配的内存。失败:0(NULL)

    int     *point;
    point  =  new  int (2);
    
    Point     *ptr;
    ptr  =  new  Point;
    
  2. **注意:**堆对象的生命周期是整个程序生命期

  3. 如果建立的对象是某一个类的实例对象,就是要根据实际情况调用该类的构造函数

  4. 释放内存操作符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.

动态内存分配

  1. 用new创建一维数组

    new  类型名T [ 下标表达式 ] ;
    
    1. 下标表达式表示数组元素的个数
    2. 动态为数组分配内存时不能指定数组元素的初值
  2. 结果值

    1. 成功:返回指向新分配内存首地址的 T 类型的指针
    2. 失败:返回空指针 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 删除时必须在指针名前面加 “ [ ]”

  1. 指向多维数组的指针和指针变量

    通过指针引用多维数组_多维数组指针

使用vector创建数组对象

  1. vector定义动态数组的格式:
  2. vector <元素类型> 数组名(数组长度[,元素初值]);
  3. 注意
    1. 用vector 创建的数组对象都会被初始化,如果是基本数据类型,则要会被0初始化,如果是类类型,则要调用类的默认构造函数,这时就要保证类中一定要有默认构造函数,当然用户也可以自己为它赋值,但赋的初值都是一样的。
    2. 数组名不是首地址
    3. 访问方式与普通数组的方式一样

例:

#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;} 

深复制与浅复制

无错误的浅拷贝

  1. 先看一个简单的例子,该例子是浅拷贝的典型代表,而且没有问题。

  2. 接下来看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();
    }
    

    结果

    https://cdn.jsdelivr.net/gh/adan-ning/images/202405071818965.png

  3. 结果分析

    1. 对于语句Cperson Jim(Tom),我们并没有定义相应的拷贝构造函数,编译器将会自动生成一个默认的拷贝构造函数
    2. 默认拷贝构造函数所做的工作是,将一个对象的全部数据成员赋值给另一个对象的数据成员
    3. C++只把对象数据成员简单赋值这种情况称为“浅拷贝”
    4. 听起来,编译器似乎很好,会提供一个我们没有定义的拷贝构造函数

有错误的浅拷贝

  1. 在接下来的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();
    }
    

    结果

    https://cdn.jsdelivr.net/gh/adan-ning/images/202405071822538.png

  2. 结果分析

    1. 可以看到,程序出现了错误
    2. 在执行语句Cperson Tom(10,“Tom”)时,用new动态开辟了一段内存,用来存放”Tom”
    3. 在执行Cperson Jim(Tom)时,只是将Tom的成员(Tom.m_age,Tom.m_name)赋值给Jim相应的成员
    4. 此时,Tom.m_name和Jim.m_name指向同一内存空间,然而,系统并没给Jim.m_name开辟相应的内存空间
    5. 执行完Jim.Print()后,开始执行析构函数,析构函数的执行顺序和对象构造函数的执行顺序相反,所以先执行Jim的析构函数,执行完Jim的析构函数后,Jim.m_name所指的内存空间已经释放
    6. 接着执行Tom的析构函数,此时就会出现问题,即在释放Tom.m_name所指的内存空间时会出现问题,因为这段内存空间在Jim的析构函数中已经释放过了
    7. 出现这种原因的根本在于“浅拷贝”,所以需要定义自己的构造函数。

深拷贝

  1. 接下来看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();
    }
    

    结果:

    https://cdn.jsdelivr.net/gh/adan-ning/images/202405071826794.png

  2. 结果分析

    1. 可以看到,自己定义了拷贝构造函数后,就没有问题了

浅拷贝和深拷贝

  1. 深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
  2. 在实际中应该避免浅拷贝

例: 对象的浅拷贝

#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...
//接下来程序出现异常,也就是运行错误

https://cdn.jsdelivr.net/gh/adan-ning/images/202405071829214.png

例: 对象的深拷贝

#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...

https://cdn.jsdelivr.net/gh/adan-ning/images/202405071832489.png

字符串

用字符数组存储和处理字符串

  1. 字符串常量(例:“program”)
    1. 各字符连续、顺序存放,每个字符占一个字节,以‘\0’结尾,相当于一个隐含创建的字符常量数组
    2. “program”出现在表达式中,表示这一char数组的首地址
    3. 首地址可以赋给char常量指针:
    4. const char *STRING1 = “program”;
  2. 字符串变量
    1. 可以显式创建字符数组来表示字符串变量,例如,以下三条语句具有等价的作用
    2. char str[8] = { ‘p’, ‘r’, ‘o’, ‘g’, ‘r’, ‘a’, ’m’, ‘\0’ };
    3. char str[8] = “program”;
    4. char str[] = “program”;

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

  1. 用字符数组表示字符串的缺点
    1. 执行连接、拷贝、比较等操作,都需要显式调用库函数,很麻烦
    2. 当字符串长度很不确定时,需要用new动态创建字符数组,最后要用delete释放,很繁琐
    3. 字符串实际长度大于为它分配的空间时,会产生数组下标越界的错误
  2. 解决方法
    1. 使用字符串类string表示字符串
    2. string实际上是对字符数组操作的封装
    3. 类string在string头文件中

string类

  1. 常用构造函数

    string(); //缺省构造函数,建立一个长度为0的串
    string(const char *s);  	//用指针s所指向的字符串常量初始化string类的对象
    string(const string& rhs);  //拷贝构造函数
    string(const char *sunsigned 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类的对象
    
  2. 例:

    string s1; //建立一个空字符串
    string s2 = abc;  //用常量建立一个初值为”abc”的字符串
    string s3 = s2;//执行拷贝构造函数,用s2的值作为s3的初值
    
  3. 常用操作符

    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的字符
    
  4. 例:

    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输入整行字符串

  1. 输入整行字符串

    1. 用cin的»操作符输入字符串,会以空格作为分隔符,空格后的内容会在下一回输入时被读取

    2. 用string头文件中的getline可以输入整行字符串,例如:

      getline(cin, s2);
      
  2. 以其它字符作为分隔符输入字符串

    1. 输入字符串时,可以使用其它分隔符作为字符串结束的标志(例如逗号、分号)

    2. 把分隔符作为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 

字符串常用成员函数

字符串常用成员函数