c++之指针与数组入门

c++ —指针与数组入门

指针数组

数组的元素是指针类型
例:Point *pa[2];
由pa[1]、pa[2]两个指向Point类的指针构成。

#include <iostream>
using namespace std;

int main()
{
	int line1[3]={1,2,3};
	int line2[3]={4,5,6};
	int line3[3]={7,8,9};
	int *pLine[3]={line1,line2,line3};     //指针数组 
	
	for(int i=0;i<3;i++)
	{
		for(int j=0;j<3;j++)
			cout<<pLine[i][j]<<" ";     //此时的指针数组就可以当二维数组用 
		cout<<endl;
	}
}

注:二维数组中数据都是连续存放的,但指针数组中每个指针地址并不连续。

常指针

const int *p;
指针只能读取,不能修改指向的值。
int *const p;
指针只能指向一个量,但可读可写。
const int *const p;
指针只能读取,且只能指向一个量

指针函数

存储类型 数据类型 *函数名()
{
//函数体
}
注:
1、不能讲一个非静态的局部地址返回给主调函数
2、但如果在函数体中通过动态内存分配new操作取得的内存地址返回给主调函数,是合法的;但是内存的分配和释放不在同一级别,要注意不能忘记释放,避免内存泄露。

指向函数的指针

存储类型 数据类型 (*函数名)();
函数指针指向的是程序代码的储存区
典型用途:实现函数回调
通过函数指针调用该函数,使得处理相似事件时可以灵活用各种不同的方法。
调用者不关心谁是被调用者,需知道存在一个具有特定原型的限制条件的被调用函数。
例:

#include <iostream>
using namespace std;

int compute(int a,int b,int (*fun)(int,int))
{
	return fun(a,b);
}

int max(int a,int b)
{
	return a>b?a:b;
}

int min(int a,int b)
{
	return a>b?b:a;
}

int sum(int a,int b)
{
	return a+b;
}

int main()
{
	int a,b,c;
	cout<<"请输入整数a:";
	cin>>a;
	cout<<"请输入整数b:";
	cin>>b;
	c=compute(a,b,&max);
	cout<<"a和b的最大值为:"<<c<<endl;
	
	c=compute(a,b,min);                //函数名就代表它的地址,可以不加& 
	cout<<"a和b的最小值为:"<<c<<endl;
	
	c=compute(a,b,&sum);
	cout<<"a和b的和为:"<<c<<endl;
}
         //这样就可以用一个compute函数来完成三个相似的功能

对象指针

类名 *对象指针名;
例:
Point a(4,5);
Point *p;
p=& a;
通过指针访问对象成员:
对象指针名 - >成员名
例:
p - > getX();相当于(*p) . getX();
this指针:
1、隐含于类的每一个非静态成员中
2、指出成员函数所操作的对象:
当通过一个对象调用成员函数时,系统先将该对象的地址赋给this指针,然后调用成员函数,成员函数对对象的数据成员进行操作时,就隐含使用了this指针。
例:Point类中getX语句:return x;
相当于return this -> x;

动态分配与释放内存

动态申请内存操作符: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<<"默认构造函数"<<endl;
	}
	Point(int x,int y):x(x),y(y){
	cout<<"构造函数"<<endl; 
	}
	~Point(){
		cout<<"析构函数"<<endl;
	}
	int getX(){return x;}
	int getY(){return y;}
private:
	int x,y;
}; 

int main()
{
	Point *p=new Point;//调用默认构造函数
	delete p;//仅仅删除p所指向的空间,而不删除p,p仍为一个指针可以在后续代码中使用
	p=new Point(4,5);//调用构造函数
	delete p;
}

结果:
在这里插入图片描述

申请和释放动态数组

分配:new 类型名T[数组长度]
(数组长度可以是任何整数类型表达式,在运行时计算,而普通数组的长度必须是一个已经确定的值)
释放:delete[] 数组名p
(释放指针p所指向的数组,p必须是用new分配得到的数组首地址)
多维数组:new 类型名T [第一维长度] [第二维长度]
若为二维数组,则返回首个一位数组的地址。
三维及以上:三维则用指向二维得到数组首地址访问

#include <iostream>
using namespace std;

int main()
{
	int (*cp)[8][9]=new int[7][8][9];
	//三维数组[7][8][9]就是7个二维数组cp[8][9]所组成的,用指向[8][9]的二维数组的指针来接收它动态分配得到的首地址
	for(int i=0;i<7;i++)
		for(int j=0;j<8;j++)
			for(int k=0;k<9;k++)
				*(*(*(cp+i)+j)+k)=(i*100+j*10+k);//遍历三维数组方式一
	for(int i=0;i<7;i++){
		for(int j=0;j<8;j++){
			for(int k=0;k<9;k++)
				cout<<cp[i][j][k]<<" ";//遍历三维数组方式二
			cout<<endl;
		}
		cout<<endl;
	}
	delete[] cp;//删除只需要加一个[]就行
	return 0;	
}

将动态数组封装成类:
1、更加简洁,便于管理(在用时不用考虑new分配内存和delete释放内存,用起来更加方便和安全);
2、可以在访问数组元素前检查下标是否越界

#include <iostream>
#include <cassert>
using namespace std;

class Point{
public:
	Point():x(0),y(0){
		cout<<"默认构造函数"<<endl;
	}
	Point(int x,int y):x(x),y(y){
		cout<<"构造函数"<<endl;
	}
	~Point(){
		cout<<"析构函数"<<endl; 
	}
	int getX(){
		return x;
	}
	int getY(){
		return y;
	}
	void move(int newX,int newY){
		x=newX;
		y=newY;
	}
private:
	int x,y;
};

class ArrayOfPoints{
public:	
	ArrayOfPoints(int size){
		points=new Point[size];//构造函数,对points数组初始化 
	}
	~ArrayOfPoints(){
		cout<<"删除数组中"<<endl;
		delete[] points;//通过析构函数来释放动态内存 
	}
	Point& element(int index){    //目的是真正地移动点的位置,而不是仅仅移动它的一个副本,故用引用 
		assert(index>=0&&index<size);//其作用是如果它的条件返回错误,则终止程序执行,从而检查是否越界
		return points[index];//在类外无法访问类内的points,故需要在类内写个函数来调用points 
	}
private:
	Point *points;
	int size;
};
int main()
{
	int count;
	cin>>count;
	ArrayOfPoints m(count);
	m.element(0).move(4,5);
	m.element(1).move(1,2);
	//先通过ArrayOfPoints中的element函数访问各个点,再调用各个点的move函数进行移动 
	return 0;
}

c++11中的智能指针(了解)

unique_ptr:
不允许多个指针共享资源,可以用标准库中的move函数转移指针
shared_ptr:
多个指针共享资源
weak_ptr:
可复制shared_ptr,但其构造或者释放对资源不产生影响

vector对象

1、封装 任何类型 的动态数组,自动创建和删除;
2、数组下标越界检查。
vector对象的定义:
vector<元素类型>数组对象名(数组长度);
例:vectorarray(5);
vector对象的使用:
1、与普通数组具有相同形式:
对象名[下标表达式]
(因为vector中重载了一个下标运算符函数)
注: vector数组对象名不代表首地址。
2、获得数组长度:用size函数
对象名 . size();
(vector包含于头文件中)

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	int n;
	cin>>n;//赋值必须在定义之前 
	vector<int>array(n);
	for(int i=0;i<array.size();i++)//调用vector中的size函数 
		array[i]=i+1;
	for(int k=0;k<n;k++)
		cout<<array[k]<<" ";
	return 0;
}

深层复制与浅层复制

浅层复制:
实现对象成员间一一对应的复制(默认复制构造函数)
深层复制:
当被复制的对象数据成员是指针类型时,不是复制该指针成员本身,而是将指针所指对象进行复制。

class ArrayOfPoints{
public:	
	ArrayOfPoints(int size){
		points=new Point[size];
	}
	~ArrayOfPoints(){
		cout<<"删除数组中"<<endl;
		delete[] points;
	}
	Point& element(int index){   
		assert(index>=0&&index<size);
		return points[index];
	}
	ArrayOfPoints(const ArrayOfPoints& v);
private:
	Point *points;
	int size;
};
//深层复制函数
ArrayOfPoints::ArrayOfPoints(const ArrayOfPoints& v){
	size=v.size;//将被复制的对象的数组大小传给新复制的对象大小
	points=new Point[size];//按此大小为新复制的对象另开辟一个数组空间
	for(int i;i<size;i++)
		points[i]=v.points[i];//再将被复制对象的每个数组元素复制给新对象数组的各个元素
}

因为若用默认的复制构造函数,则会把原对象的地址复制给新对象,则原对象改变时新对象也随之改变,并且原对象被析构时将数组空间删除,此时新对象再被析构是也会删除同样的数组空间,便会出错。

移动构造

1、是c++11标准中新的构造方法;
2、在此之前若想将原对象的状态转移到一个新的目标对象则只能通过复制。但在某些情况下,我们只需要移动,而不需要复制。
3、语义:将原对象的控制权全部交给目标对象。
在这里插入图片描述
移动构造函数:class_name(class_name &&)

#include <iostream>
using namespace std;

class IntNum{
public:
	IntNum(int x=0):xptr(new int(x)){//返回值是指向int的指针 
		cout<<"构造函数"<<endl;
	}
	IntNum(const IntNum & n):xptr(new int(*n.xptr)){
	//深层复制:将对象n的 xptr指针所指向的值复制给新的int对象,然后再将新对象的指针初始化新的xptr 
		cout<<"复制构造函数"<<endl;
	}
	
	//移动构造函数
	IntNum(IntNum && n):xptr(n.xptr){//看似为浅层复制 ,将原对象的指针复制给新对象 
		n.xptr=nullptr;//实则将原对象的指针指向空指针,原对象消亡时调用析构函数删除空指针不会对新对象产生影响 
		cou<<"移动构造函数"<<endl;
	} 
	
	~IntNum(){
		delete xptr;
		cout<<"析构函数"<<endl; 
	}
	int getInt(){
		return *xptr;
	}
private:
	int *xptr;
};

IntNum getNum(){
	IntNum a;
	return a;
}//先定义一个对象a,再将a返回给主调函数,返回的是一个临时变量,此时便要调用复制构造函数将a复制给临时变量 

int main()
{	
	cout<<getNum().getInt()<<endl;
	return 0;
}

移动构造其实就是将原对象中指针元素的地址复制给新对象,然后在将原对象的指针指向空指针。

字符串

字符串常量:
1、相当于一个隐含创建的字符常量数组,以‘\0’结尾。
2、表示这一char数组的首地址,可以赋值给char指针。
3、例:const char *p=“program”;
字符数组表示字符串缺点:(c风格的字符串)
1、进行连接、拷贝、比较等操作,都需显式调用库函数,很麻烦。
2、字符串长度不确定时,需要用new动态创建字符数组,最后用delete释放,很繁琐。
3、有下标越界的风险。
string类:
1、string();默认构造函数,建立一个长度为0的串
例:string s;
长度可以根据给s的字符串长度进行延展。
2、string(const char *s);用指针s所指向的字符串常量初始化string对象。
例:string s2=“abc”;
3、string(const string & rhs);复制构造函数
例:string s3=s2;
4、string常用操作:
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的字符
输入整行字符串:
1、getline可以输入整行字符串(包括空格,cin无法读取空格)
2、输入时可以使用其他分隔符作为字符串结束标志(默认为换行),将此分隔符作为第三个参数即可。
例:getline(cin,s2,’,’);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章