#include<iostream>
using namespace std;
/**
数组的存储与初始化
数组的存储:数组元素在内存中顺次存放,它们的地址是连续的。
元素间物理地址上的相邻,对应着逻辑次序上的相邻
数组的名字是指针类型/地址类型,数组名字是数组首元素的内存地址
一旦定义好数组后,数组名是地址类型的常量,不能再被赋值
对数组初始化,即定义数组时,需要给数组初始值
列出全部元素的初始值
例如:static int a[10]={0,1,2,3,4,5,6,7,8,9};
可以只给一部分元素赋初值
例如:static int a[10]={0,1,2,3,4};
在对全部数组元素赋初值时,可以不指定数组长度
例如:static int a[]={0,1,2,3,4,5,6,7,8,9},编译器会根据所给出的初始化数值确定数组元素的个数
二维数组的存储:
float a[3][4]
二维数组的每一行都可以看做是一维数组,二维数组在内存中按行存储
a[0] 也是地址类型,表示第0行的首地址
a[0][0],a[0][1],a[0][2],a[0][3],a[1][0],a[1][1],a[1][2],a[1][3]a[0][0],a[2][1],a[2][2],a[2][3]
**/
/** 求斐波那契数列的前n项数值 **/
int main(){
int n;
cin>>n;
int output[n]={1,1};
for(int i=2;i<n;i++){
output[i]=output[i-1]+output[i-2];
}
for(int i=0;i<20;i++){
if(i%5==0)
cout<<endl;
cout.width(20);
cout<<output[i];
}
}
#if 0
int main(){
int a[10];//一维数组的数组名是地址类型/指针类型,存放着数组第0个元素的首地址
int b[10];
for(int i=0;i<10;i++){
a[i]=2*i-1;//数组的下表从0开始
b[10-i-1]=a[i];
}
cout<<"address"<<a<<endl;
for(int i=0;i<10;i++){
cout<<"a["<<i<<"]="<<a[i]<<endl;
cout<<"b["<<i<<"]="<<b[i]<<endl;
}
return 0;
}
#endif
#include<iostream>
using namespace std;
/**
一维数组应用举例
循环从键盘读入若干组选择题答案,计算并输出每组答案的正确率,直到输入ctrl+z为止。
每组连续输入5个答案,每个答案可以是'a'..'d'。
**/
int main(){
const char correct[5]={'a','c','b','a','d'};
const int num=5;
char c;
int cor=0,i=0;
while(cin.get(c)){//如果当前输入的字符不是ctrl+Z,cin.get(char) 每次从键盘读入一个字符
if(c!='\n'){
if(correct[i]==c){
cout<<"*";
cor++;
}
else{
cout<<" ";
}
i++;
}
else{
cout<<" correct rate: "<<float(cor*1.0)/(num*1.0)*100<<"%"<<endl;
cor=0;
i=0;
}
}
}
#include<iostream>
using namespace std;
/**
int arr[5] ={1,2,3,4,5}
则 *(arr+1),*arr+1,arr[1]所得到的数值都是2
数组元素作实参,与单个变量一样。
数组名作参数,形、实参数都应是数组名(实质上是地址,关于地址详见6.2),
类型要一样,传送的是数组首地址。对形参数组的改变会直接影响到实参数组
数组名作为参数实际上传递的实参是数组的首地址(即数组名),
则函数中访问形参数组名就是在直接访问实参数组,信息是双向传递的
**/
/**
例6-2 使用数组名作为函数参数
主函数中初始化一个二维数组,表示一个矩阵,矩阵,并将每个元素都输出,然后调用子函数,
分别计算每一行的元素之和,将和直接存放在每行的第一个元素中,返回主函数之后输出各行元素的和。
**/
void rowSum(int a[][4],int num_row){
for(int i=0;i<num_row;i++){
for(int j=1;j<4;j++){
a[i][0]+=a[i][j];
}
}
}
int main(){
int table[3][4]={{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}};
rowSum(table,3);
for (int i = 0; i < 3; i++)
cout << "Sum of row " << i << " is " << table[i][0] << endl;
return 0;
}
/**
定义对象数组
类名 数组名[元素个数];
访问对象数组元素
通过下标访问
数组名[下标].成员名
对象数组中的每个元素是一个对象
结构体数组中的每个元素是个结构体,结构体中存放的大多是允许公开访问的成员数据,而类中除了成员数据外,还包含成员函数
元素所属的类不声明构造函数,则采用默认构造函数。
各元素对象的初值要求为相同的值时,可以声明具有默认形参值的构造函数。
(如果类有可能作为数组中的元素,则最好定义带有形参的构造函数)
各元素对象的初值要求为不同的值时,需要声明带形参的构造函数。
当数组中每一个对象被删除时,系统都要调用一次析构函数。
for循环通常用来处理循环次数已知的循环
while循环通常用来处理循环次数未知的循环
**/
/***
内存空间的访问方式:
(1)通过变量名访问
定义变量名时,就是将变量名与若干字节的内存空间结合起来,之后在变量名的作用域范围内,
就可以通过变量名访问一段内存空间
(2)通过地址访问
指针:是地址类型的变量,指针类型变量中的数据就是地址值
在指针中可以存放其他变量的地址,之后就可以通过指针加上*(寻址)找到指针所指向的内存空间
取地址运算符 & 和寻址运算符 * 互为逆运算
**/
对指针变量进行初始化,其中的数值不能是任意整数或者随意设计的地址
而必须是在程序运行阶段对所定义的变量合法获得的地址
/**
指针的算术运算和关系运算
int a[5];
*(a+1)
数组名a表示的是数组的首地址,即a为指向int类型的指针,a+1表示内存空间的下一个单元
* 表示寻址运算,即根据a+1的地址取出地址中的数值,即=a[1]
只有对于指向连续相同类型的内存空间的指针进行加法减法运算才有意义
short a[5]; // 表示初始化a为指向short类型数组的首地址
short *p=a;// a的数值就是数组a的起始地址,它是一个指向short数据类型的指针
*p=a[0]
*(p+1)=a[1]
*(p+2)=a[2]
**/
/**
编写矩阵转置函数,输入参数为3*3的整形数组,编写main()函数实现输入和输出
**/
#include<iostream>
using namespace std;
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
int main(){
int a[3][3];
cout<<"请输入9个数:"<<endl;
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
cin>>a[i][j];
}
}
cout<<"原始的数组"<<endl;
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
cout<<a[i][j]<<" ";
}
cout<<endl;
}
for(int i=0;i<3;i++){
for(int j=i+1;j<3;j++){
swap(a[i][j],a[j][i]);
}
}
cout<<"转置后的数组"<<endl;
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
cout<<a[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
/**
数组是由相同类型的元素组成的,数组中的元素在内存中是连续存放的
数组名就是数组元素的首地址,是一个常量
下标标志着原宿在数组中的位置序号
在访问数组中的元素时,给出数组名(数组名的数据类型是一个指针,指针中的数据是数组的起始地址)和下标
就能够根据数组名和下标得到当前需要访问的元素的起始地址,再根据数组的数据类型读取连续的内存字节
即可读出对应位置的元素
可以通过字符指针操作字符串
**/
#include<iostream>
using namespace std;
class Employee{
public:
Employee(char *n,char *a,char *c,char *co):name(n),address(a),city(c),code(co){}
//构造函数,定义构造函数时,最好能给出默认的形参数值,以初始化列表的形式初始化数据成员
void change_name(char *a){//成员函数直接定义在类內,内联函数
name=a;
}
void display(){
cout<<"name: "<<name<<endl;
cout<<"address: "<<address<<endl;
cout<<"city: "<<city<<endl;
cout<<"code: "<<code<<endl;
}
private:
char *name;
char *address;
char *city;
char *code;
};
int main(){
Employee em1("Lily","here","wuhan","430074");
em1.display();
em1.change_name("yixuan");
em1.display();
return 0;
}
#include<iostream>
using namespace std;
/**
用指针访问数组元素
数组是一组连续存储的同类型数据,可以通过指针的算术运算,使指针依次指向数组的各个元素,进而可以遍历数组。
定义指向数组元素的指针
定义与赋值
例: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]都是等效的。
注意 不能写 a++,因为a是数组首地址、是常量。
指针数组:数组中的每个元素是指向相同数据类型的指针
Point *pa[2] //pa是指向Point类型的指针数组,pa由两个元素构成,每个元素都是指向point类的指针
指针数组与二维数组的区别:
二维数组要求每一行的元素个数相同,而指针数组并不要求每一行的元素相同
指针数组的每个元素是地址(即一维数组的数组名),故而可以用一维数组的数组名去初始化指针数组
则指针数组的数组名就可以用来充当二维数组的数组名
二维数组
**/
int main(){
int line1[]={1,0,0};
int line2[]={0,1,0};
int line3[]={0,0,1};
// 定义整形指针数组并初始化,数组中的每个元素都是指向整型数据的指针
int *pline[]={line1,line2,line3};
// 输出矩阵
for(int i=0;i<3;i++){//外层循环:遍历指针数组的每个元素,pline[i]表示指针,指向一位数组首地址
for(int j=0;j<3;j++){
cout<<pline[i][j];
}
cout<<endl;
}
return 0;
}
/**
以指针作为函数参数
为什么需要用指针做参数?
需要数据双向传递时(引用也可以达到此效果)
用指针作为函数的参数,可以使被调函数通过形参指针存取主调函数中实参指针指向的数据,实现数据的双向传递
需要传递一组数据,只传首地址运行效率比较高
实参是数组名时形参可以是指针
主调函数和被调函数之间传递参数有两种形式
(1)单向传递:此时被调函数的形参是变量的值
(2)双向传递:被调函数的形参是引用(别名)或者指针
如果以指针作为被调函数的形参,将主调函数中已经定义好的变量地址放到指针里面,作为参数传递给被调函数
则被调函数就可以根据实参地址访问主调函数中的变量,这种情况就称为数据的双向传递,相当于主调函数给
被调函数对于相关变量的访问授权,被调函数可以直接修改或者访问变量
当所要操作的实参是大规模数组时,为了提高调用函数时的参数传递效率,
也可以将指针(将数据的起始地址传给被调函数)或者引用(数据的别名)作为函数形参
在主调函数中将实参定义为变量的地址给形参指针去接收,相当于将主调函数中的变量的访问授权送给了被调函数
被调函数中对于指针/地址所进行的操作实际上就是对主调函数中的实参进行操作
C++中被调函数的return语句只能返回一个数值
浮点数在机器内部是近似存储的,千万不要在程序中去比较2个浮点数是否相等,理论上相等,但可能由于机器的存储精度导致不相等
如果需要比较2个浮点数是否相等,可以比较 (A-B)<eps,eps定义成一个很小的数
指向常量的指针做形参,指针所指向的对象是常量,通过指针只能读取它所指向的对象,而没有权限修改
**/
/**
读入3个浮点数,将整数部分和小数部分分别输出**/
#include<iostream>
using namespace std;
void get_two_part(int *int_part,double *double_part,double nums);
int main()
{
for(int i=0; i<3; i++)
{
double nums;
cin>>nums;
int n;
double m;
get_two_part(&n,&m,nums);
//& 取地址运算符,将内存地址作为实参,传给被调函数
// 相当于授予了被调函数对于实参的访问授权
cout<<"整数部分: "<<n<<"小数部分: "<<m<<endl;
}
return 0;
}
void get_two_part(int *int_part,double *double_part,double nums)
{
// 被调函数的形参分别是指向整型数据的指针(里面的数据就是地址)
*int_part=static_cast<int>(nums);
*double_part=nums-*int_part;
}
指向常量的指针和常指针的区别
#include<iostream>
using namespace std;
int main(){
int a=5,c=3;
int *const b=&a;
//b是常指针,定义b是指向整数数据的指针,且b是常量,即指针b所保存的地址一旦初始化就不能再被赋值
//且对于任何数据类型,一旦定义成了常量,必须在定义的时候进行初始化
*b=8;
// b是常量指针,其含义是,b中所保存的地址数值不能更改,即b不能在存储别的变量的地址
// 但是通过对b进行寻址后,找到相应的内存空间,可以对其进行修改
cout<<a<<endl; // 8
// b=&c; 编译器报错,因为b是常量,其中的地址不能被修改
const int *d;//d是指向常量的指针(d中的地址并不需要指向常量),表示不能通过指针d修改d所指向的内存空间中的数值
d=&c;
//*d=8; //编译器报错,因为指向常量的指针只具有只读权限,并不能修改
return 0;
}
无论是指向什么数据类型的指针,只要是指针类型,其中所保存的必然是变量或者对象的地址/首地址,则指针类型的数据,在内存中它所存放的数值就是一个地址,故而所有指针类型的数据所占据的内存空间都相同。但是如果只知道地址而不知道需要在当前的首地址的基础上向后读取多少个字节,则只知道地址是没有意义的,只有知道指针的类型即需要在首地址的基础上向后读取多少个字节,才能正确地读取出数据。
#include<iostream>
using namespace std;
int main(){
int *p;
short *q;
int a[10];
int *r=a;
cout<<sizeof(p)<<endl;
cout<<sizeof(q)<<endl;
cout<<sizeof(r)<<endl;//输出值都是4,表示地址占据4位,
cout<<r<<endl;
return 0;
}
C++中说函数是什么类型指的是函数的返回值是什么类型
指针类型的函数:函数的返回值是指针
指针类型必须指明是指向什么数据类型的指针,从而才能对指针寻址操作*之后确定读取多少字节的数据,才能读取出完整的数据
如果不指明指针所指向的数据类型,而是仅仅给出指针的地址,是没有意义的
不要将非静态局部地址用作函数的返回值
错误的例子:在子函数中定义局部变量后将其地址返回给主函数,就是非法地址
非静态的局部地址在离开变量的作用域之后,系统将会回收局部变量的内存空间,局部变量的地址将会失效,变成非法地址
主函数中定义的数组,在子函数中对该数组元素进行某种操作后,返回其中一个元素的地址,这就是合法有效的地址
#include<iostream>
using namespace std;
int *function(){
int local=0;//非静态局部变量作用域和寿命都仅限于本函数体内
return &local;
}//函数运行结束时,变量local被释
int main(){
int *ptr=function();
*ptr=50;//非法地址
cout<<*ptr<<endl;
return 0;
}
#include<iostream>
using namespace std;
int *search_zero(int *arr,int nums);
int main(){
int array[10];
for(int i=0;i<10;i++){
cin>>array[i];
}
int *zero;
zero=search_zero(array,10);
cout<<*zero<<endl;
}
int *search_zero(int *arr,int nums){
for(int i=0;i<nums;i++){
if(arr[i]==0){
return arr+i;
}
}
}
/***
指向函数的指针,其中的数值是函数代码段的起始地址
指针函数和函数指针
(1)指针函数:函数的返回值是指针类型(需要指明是指向什么数据类型的指针)
指针类型名 *函数名(形参列表);
(2)函数指针:指向函数的指针
指针类型名/函数返回值类型 (*函数名)(形参列表)
无论是定义指向对象、基本数据或者指向函数的指针,都必须指明指针所指向对象的具体信息
如:指针所指向的基本数据类型或者对象的类名,而指向函数的指针则需要指明:函数的返回值,函数名和函数的形参列表
函数指针的典型用途是函数回调:即通过函数指针调用函数
将函数指针作为参数传递给另一个函数,使得在处理相似问题的时候可以灵活调用不同的方法
所调用的不同方法必须满足:具有相同的返回值类型和相同的形参列表
主调函数并不用关心被调函数具体是什么,只需要知道存在一个有特定原型和限制条件的被调用函数
**/
#include<iostream>
using namespace std;
int compute(int (*func)(int,int),int a,int b);
int find_max(int a,int b);
int find_min(int a,int b);
int sum(int a,int b);
int main(){
int a,b;
cin>>a>>b;
cout<<"max value: "<<compute(&find_max,a,b)<<endl;
cout<<"min value: "<<compute(&find_min,a,b)<<endl;
cout<<"summation: "<<compute(&sum,a,b)<<endl;
return 0;
}
int compute(int (*func)(int,int),int a,int b){
return func(a,b);
}
int find_max(int a,int b){
return (a>=b)? a:b;
}
int find_min(int a,int b){
return (a<=b)? a:b;
}
int sum(int a,int b){
return a+b;
}
#include<iostream>
using namespace std;
class Point{
public:
int getX(){return x;}
int getY(){return y;}
Point(int a=0,int b=0):x(a),y(b){}//构造函数
private:
int x;
int y;
};
int main(){
Point p1(5,8),*ptr;
ptr=&p1;
cout<<p1.getX()<<endl;
cout<<ptr->getX()<<endl;
return 0;
}
指向类对象的指针
/**
对象指针的定义形式:
类名 *对象指针名
Point a(5,10);
Point *p=&a;
通过指向对象的指针访问对象的成员(类的公有接口)
对象指针名->成员名 等同于 (*对象指针名).成员名
**/
#include<iostream>
using namespace std;
class Point{
public:
int getX(){return x;}
int getY(){return y;}
Point(int a=0,int b=0):x(a),y(b){}//构造函数
private:
int x;
int y;
};
int main(){
Point p1(5,8),*ptr;
ptr=&p1;
cout<<p1.getX()<<endl;
cout<<ptr->getX()<<endl;
return 0;
}
/**
动态内存分配
动态申请内存操作符 new
new 类型名T(初始化参数列表)
功能:在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值。
结果值:成功:T类型的指针,指向新分配的内存;失败:抛出异常。
释放内存操作符delete
delete 指针p
功能:释放指针p所指向的内存。p必须是new操作的返回值。
C++中,可以用变量名访问内存空间,也可以用指针里面存放的地址访问内存空间
可以用数组名访问数组中的元素,也可以用指针访问数组元素
当使用动态分配的方式去申请内存单元时,则通过动态分配的方式获取的内存单元将没有机会给
它取变量名,动态分配内存的操作符返回的就是首地址(用指针类型存储),
则只能通过对指向动态分配的到的内存空间首地址进行寻址操作(*)来访问动态分配内存空间中的对象
在C++中的动态申请内存是一个操作,操作符是new,operator
new的返回值是一个指向类型T的指针,指向的是动态分配的到的内存空间的起始地址
**/
#include<iostream>
using namespace std;
class Point{
public:
Point():x(0),y(0){cout<<"调用默认构造函数"<<endl;}//默认构造函数不接受任何形参
Point(int a,int b):x(a),y(b){cout<<"调用构造函数"<<endl;}
~Point(){cout<<"调用析构函数"<<endl;}
private:
int x,y;
};
int main()
{
Point *ptr=new Point();// 将所构造的对象在内存空间中的首地址赋值给ptr
delete ptr;
//删除的是根据ptr中的地址寻址后得到的对象所占据的那部分内存空间,ptr作为函数的局部变量并没有被删除
// note: delete 操作删除的是指针所指向的对象,而不是指针本身
cout<<"ptr所指向的内存首地址: "<<ptr<<endl;
cout<<"ptr指针所占据的内存空间首地址: "<<&ptr<<endl;
ptr=new Point(5,8);//更新ptr指针中所存放的地址值,让它指向重新动态分配的内存空间
cout<<"重新赋值后,ptr所指向的内存首地址: "<<ptr<<endl;
cout<<"重新赋值后,ptr指针所占据的内存空间首地址: "<<&ptr<<endl;
// 重新赋值后,ptr所占据的内存空间首地址并不会改变,但是ptr所指向的内存空间首地址会发生改变
delete ptr;
return 0;
}
/**
分配和释放动态数组
分配: new 类型名T[数组长度]
数组长度可以是任何表达式,在运行时计算
释放:delete[] 数组名p
释放指针p所指向的数组。 p必须是用new分配得到的数组首地址。
**/
#include<iostream>
using namespace std;
int main(){
short a[3][4];//定义静态的二维数组
for(short i=0;i<3;i++){
for(short j=0;j<4;j++){
a[i][j]=i+j;
}
}
for(short i=0;i<3;i++){
for(short j=0;j<4;j++){
cout.width(5);
cout<<a[i][j];
cout<<" 地址 "<<&a[i][j]<<endl;
}
cout<<endl;
}
for(short i=0;i<3;i++){
for(short j=0;j<4;j++){
//cout<<"--------"<<sizeof(short *)<<endl;
//cout<<a[i];
short *p;
p=a[i]+j;
// a[i]表示二维数组第i行的行数组首地址,
// 由于p是指向short类型的指针,则指针进行算术运算符的时候就移动 sizeof(short) 字节
short *q;
q=(short *)a+i*4+j;
cout<<*p<<" "<<*q<<" "<<*(a[i]+j)<<endl;
/**
二维数组:数组中的所有元素存放在紧邻着的内存空间中, a+i, 由于i是指向int类型的指针,
则将会向前走i*4个字节
**/
//cout<<a<<endl;
}
}
int line_0[4]={0,1,2,3};
int line_1[4]={1,2,3,4};
int line_2[4]={2,3,4,5};
int *p[3]={line_0,line_1,line_2};
// 定义指针数组,数组中的每个元素是指向int类型的指针
// 每个int指针可能保存的是数组的首地址,而不同的元素所对应的不同的数组可以长度不同
for(int i=0;i<3;i++){
for(int j=0;j<4;j++){
cout.width(5);
cout<<*(*(p+i)+j);
// p是指向类型A的指针,说明p中存放的地址是数据类型为A的地址
// p是指针的指针(说明p中存放的是指针类型数据的地址)
// 通过 *(p+i) 找到指针数组中第i个元素,表示第i个一维数组的首地址
// 数组p中的每个元素是指向int类型数据的指针
// *(p+i) 指针 + j, 得到int数组的第j个元素
//cout<<p[i][j];
cout<<" 数据的地址为: "<<(*(p+i)+j)<<endl;
}
cout<<endl;
}
for(int i=0;i<3;i++){
cout<<*(*(p+i))<<endl;
}
// 由new操作符为二维数组动态分配内存空间
short (*ptr)[4];//指向一个具有4个元素的一维数组的指针(指向数组的指针),ptr+1,则将越过整个行
ptr=new short[3][4];
// new 操作符返回多维/二维数组的首地址
// 多维数组的数组名(或者说多维数组的首地址)是指向数组的指针
// 二维数组的首地址就是指向一维数组的指针,即用动态分配内存方式得到的二维数组首地址
// 其中是存放各个一维数组首地址的数组的起始地址
// 指向数组中每一行的指针,+1操作时,指向的是内存中下一个数组元素
// 而指向数组的指针,+1则会跳跃整个数组的空间
// 由new操作符创建的多维数组,在内存中每个数组元素仍然是按行连续存储的
for(short i=0;i<3;i++){
for(short j=0;j<4;j++){
*(*(ptr+i)+j)=i+j;
}
}
for(short i=0;i<3;i++){
for(short j=0;j<4;j++){
cout<<ptr[i][j]<<" "<<"它的地址是: "<<*(ptr+i)+j<<endl;
}
cout<<endl;
}
delete[] ptr;
return 0;
}
#include<iostream>
using namespace std;
class Point{
public:
Point():x(0),y(0){cout<<"调用默认构造函数"<<endl;}//默认构造函数,不接受任何形参
Point(int a,int b):x(a),y(b){cout<<"调用构造函数"<<endl;}//构造函数
~Point(){cout<<"调用析构函数"<<endl;}//析构函数
int getX(){return x;}
int getY(){return y;}
void move(int a,int b){x=a;y=b;}
private:
int x,y;
};
int main(){
Point *ptr;
ptr=new Point[2];
// ptr[0].move(5,8);
// ptr[1].move(-2,-8);
(*ptr).move(5,8);
(*(ptr+1)).move(-2,-7);
cout<<1<<" "<<(*ptr).getX()<<", "<<(*ptr).getY()<<endl;
cout<<2<<" "<<(*(ptr+1)).getX()<<", "<<(*(ptr+1)).getY()<<endl;
delete[] ptr;
return 0;
}
/**
将动态数组封装成类
更加简洁,便于管理
可以在访问数组元素前检查下标是否越界
**/
#include<iostream>
#include<cassert>
// 引入assert
using namespace std;
class Point{
public:
Point():x(0),y(0){cout<<"Point调用默认构造函数"<<endl;}
Point(int a,int b):x(a),y(b){cout<<"Point调用构造函数"<<endl;}
~Point(){cout<<"Point调用析构函数"<<endl;}
int getX(){return x;}
int getY(){return y;}
void move(int a,int b){x=a;y=b;}
private:
int x,y;
};
class ArrayOfPoint{
public:
ArrayOfPoint(int length):size(length){
cout<<"ArrayOfPoint调用构造函数"<<endl;
points=new Point[size];
}
~ArrayOfPoint(){
cout<<"ArrayOfPoint调用析构函数"<<endl;
delete[] points;
}
Point wrong_get_element(int index){//返回值是引用类型
assert(index>=0 and index<size);
return *(points+index);}
Point& get_element(int index){//返回值是引用类型
assert(index>=0 and index<size);
return *(points+index);//等同于 point[index]
/**
函数返回的是引用类型,引用可以用来操作封装数组对象内部的数组元素,如果返回的是数值而并非引用
则在返回时,系统会在内存中开辟新的空间,将所要返回的值复制一份,
**/
}
Point * get_address(){return points;}
private:
int size;
Point *points;
};
int main(){
int length;
cin>>length;
ArrayOfPoint array_point(length);
Point *p=array_point.get_address();
for(int i=0;i<length;i++){
array_point.wrong_get_element(i).move(i,i+5);
cout<<i<<" "<<array_point.get_element(i).getX()<<", "<<array_point.get_element(i).getY()<<endl;
cout<<i<<" "<<(*(p+i)).getX()<<", "<<(*(p+i)).getY()<<endl;
}
for(int i=0;i<length;i++){
array_point.get_element(i).move(i,i+5);
cout<<i<<" "<<array_point.get_element(i).getX()<<", "<<array_point.get_element(i).getY()<<endl;
cout<<i<<" "<<(*(p+i)).getX()<<", "<<(*(p+i)).getY()<<endl;
}
return 0;
}
/**
浅层复制与深层复制
浅层复制
实现对象间数据元素的一一对应复制。
深层复制
当被复制的对象数据成员是指针类型时,不是复制该指针成员本身,
而是将指针所指对象进行复制。
类中默认的复制构造函数实现的是对象中数据成员之间一一对应的复制
这种默认的复制构造函数实现的对象复制都是浅层复制
当类的数据成员中包含指针,且指针所指向的内存空间是在构造类对象时,通过
动态内存分配的方式从内存中获得的空间,这种情况下需要进行深层复制
**/
#include<iostream>
#include<cassert>
using namespace std;
/**
这份代码会运行出错,因为当类中的私有数据成员是指针(地址)时,而它所指向的对象是动态内存分配的地址空间
则在调用类的默认复制构造函数是对对象进行复制时,会将对象的私有数据成员对应地复制一份
则复制后的对象的指针成员中存放的仍然是相同的地址,仍然与被复制对象的指针成员指向的是相同的内存空间
故而只实现了浅层复制,即复制指针/地址,并没有实现对地址中的类数组进行复制
则在程序结束之前,释放被复制对象的内存空间时,调用类的析构函数,则会将对象地址所指向的动态分配的对象数组
的空间释放掉,而在析构拷贝对象的内存空间时,由于拷贝对象的指针成员所指向的内存区域已经被释放了,故而无法析构成功
程序报错
默认的复制构造函数只能实现浅层复制,当类中包含由new分配内存得到的指针成员时,会导致拷贝副本析构失败
**/
class Point{
public:
Point():x(0),y(0){cout<<"正在调用默认的复制构造函数"<<endl;}
Point(int a,int b):x(a),y(b){cout<<"正在调用复制构造函数"<<endl;}
~Point(){cout<<"正在调用析构函数"<<endl;}
int getX(){return x;}
int getY(){return y;}
void move(int a,int b){x=a;y=b;}
private:
int x,y;
};
class ArrayOfPoint{
public:
ArrayOfPoint(int length):size(length){
cout<<"调用ArrayOfPoint构造函数"<<endl;
ptr=new Point[size];
}
~ArrayOfPoint(){
cout<<"调用ArrayOfPoint析构函数"<<endl;
delete[] ptr;
}
Point& get_element(int index){
assert(index>=0 and index<size);
return *(ptr+index);
}
private:
int size;
Point *ptr;
//类中实际上包含的是由系统进行动态内存分配得到的内存首地址
};
int main(){
int count;
cin>>count;
ArrayOfPoint arr(count);
for(int i=0;i<count;i++){
//*(arr.ptr+i).move(i,i+10);
//不能通过这种方式,因为ptr是对象的私有数据成员,外界(实例化后的对象)不可调用私有成员
arr.get_element(i).move(i,i+10);
}
ArrayOfPoint arr_copy(arr);//调用默认的复制构造函数,创建arr对象的副本
cout<<"原始对象移动前,"<<endl;
for(int i=0;i<count;i++){
cout<<"原始点:"<<arr.get_element(i).getX()<<", "<<arr.get_element(i).getY()<<endl;
cout<<"拷贝点:"<<arr_copy.get_element(i).getX()<<", "<<arr.get_element(i).getY()<<endl;
}
for(int i=0;i<count;i++){
arr.get_element(i).move(i-100,i-10);
}
cout<<"原始对象移动后,"<<endl;
for(int i=0;i<count;i++){
cout<<"原始点:"<<arr.get_element(i).getX()<<", "<<arr.get_element(i).getY()<<endl;
cout<<"拷贝点:"<<arr_copy.get_element(i).getX()<<", "<<arr_copy.get_element(i).getY()<<endl;
}
return 0;
}
对象的浅层复制(类的默认复制构造函数的操作)
对象的深层复制
#include<iostream>
#include<cassert>
using namespace std;
class Point{
public:
Point():x(0),y(0){cout<<"正在调用默认的复制构造函数"<<endl;}
Point(int a,int b):x(a),y(b){cout<<"正在调用复制构造函数"<<endl;}
~Point(){cout<<"正在调用析构函数"<<endl;}
int getX(){return x;}
int getY(){return y;}
void move(int a,int b){x=a;y=b;}
private:
int x,y;
};
class ArrayOfPoint{
public:
ArrayOfPoint(int length):size(length){
cout<<"调用ArrayOfPoint构造函数"<<endl;
ptr=new Point[size];
}
~ArrayOfPoint(){
cout<<"调用ArrayOfPoint析构函数"<<endl;
delete[] ptr;
}
Point& get_element(int index)const{
assert(index>=0 and index<size);
return *(ptr+index);
}
ArrayOfPoint(const ArrayOfPoint &obj){
// 自定义复制构造函数,就是说已经存在被复制的对象v,希望根据v对当前对象的成员初始化
size=obj.size;
ptr=new Point[size];
for(int i=0;i<size;i++){
(*(ptr+i)).move(obj.get_element(i).getX(),obj.get_element(i).getY());
// 将参数中指针变量所指向的对象数组中的每个值一一赋值给当前对象(拷贝后的对象)
// 上一行报错,因为const引用的对象只能调用const成员函数
//(const函数是保证在函数内部,不对对象的数据做修改的函数)
// 或者 ptr[index]=obj.ptr[i];
}
}
private:
int size;
Point *ptr;
//类中实际上包含的是由系统进行动态内存分配得到的内存首地址
};
int main(){
int count;
cin>>count;
ArrayOfPoint arr(count);
for(int i=0;i<count;i++){
arr.get_element(i).move(i,i+10);
}
ArrayOfPoint arr_copy(arr);//调用默认的复制构造函数,创建arr对象的副本
cout<<"原始对象移动前,"<<endl;
for(int i=0;i<count;i++){
cout<<"原始点:"<<arr.get_element(i).getX()<<", "<<arr.get_element(i).getY()<<endl;
cout<<"拷贝点:"<<arr_copy.get_element(i).getX()<<", "<<arr.get_element(i).getY()<<endl;
}
for(int i=0;i<count;i++){
arr.get_element(i).move(i-100,i-10);
}
cout<<"原始对象移动后,"<<endl;
for(int i=0;i<count;i++){
cout<<"原始点:"<<arr.get_element(i).getX()<<", "<<arr.get_element(i).getY()<<endl;
cout<<"拷贝点:"<<arr_copy.get_element(i).getX()<<", "<<arr_copy.get_element(i).getY()<<endl;
}
return 0;
}
C++中的移动构造
/***
移动构造
C++11标准中提供了一种新的构造方法——移动构造。
C++11之前,如果要将源对象的状态转移到目标对象只能通过复制。在某些情况下,我们没有必要复制对象——只需要移动它们。
C++11引入移动语义:
源对象资源的控制权全部交给目标对象
移动构造函数
问题与解决
当临时对象在被复制后,就不再被利用了。我们完全可以把临时对象的资源直接移动,这样就避免了多余的复制操作
移动构造函数使用的场景:当对象即将消亡,但是又需要保存其中的数值,则需要调用移动构造函数
移动构造函数的效率将会比复制构造函数更高,因为它不需要在内存中开辟额外的空间给新对象,然后删除原有对象
而是直接将原有对象的资源控制权交给重新创建的对象
例如:如果函数的返回值是对象,则函数结束时,对象的生存期结束,则需要释放对象的资源,但是又需要
对象中的数据成员,这时就会调用移动构造函数,直接将局部变量/对象的数据资源交给所要返回的对象
***/
#include<iostream>
using namespace std;
class IntNum{
public:
IntNum(int x=0):ptr(new int(x)){//为int类型数据x动态申请4个字节的内存空间,并将内存空间首地址赋值给xptr指针
cout<<"调用默认的构造函数"<<endl;
}
IntNum(IntNum & n){
ptr=new int(*(n.ptr));
cout<<"调用深层的复制构造函数"<<endl;
}
IntNum(IntNum && n){// && 是右值引用,函数返回的临时变量是右值
// 调用移动构造函数
ptr=n.ptr;
n.ptr=nullptr;
cout<<"调用移动构造函数"<<endl;
}
~IntNum(){
delete ptr;
cout<<"调用析构函数"<<endl;
}
int getNum(){cout<<ptr<<endl;return *ptr;}
private:
int *ptr;
};
/* 例:函数返回含有指针成员的对象(版本1) 使用深层复制构造函数(由于返回的对象中有指针类型作为数据成员,则必须调用深层复制构造函数)
返回时构造临时对象,动态分配将临时对象返回到主调函数,然后删除临时对象。
在主函数中调用本函数,函数的返回值是对象,将对象返回的过程中需要调用复制构造函数或者移动构造函数*/
IntNum getNumber(){
IntNum a;
a.getNum();
cout<<"即将返回"<<endl;
return a;//由于函数结束后,对象a的生存期就结束了,故而需要调用a的深层复制构造函数或者移动构造函数
//将a中的数据资源的控制权交由新的对象
}
int main(){
cout<<getNumber().getNum()<<endl;
return 0;
}