目录
一、数组
* 数组是具有一定顺序关系的若干对象的集合体,组成数组的对象称为该数组的元素
* 同一数组的各元素具有相同类型,每个元素有n个下标的数组称为n维数组
(一)数组的声明与使用
1、数组的声明
* 数组属于自定义数据类型,声明一个数组要注意以下几个方面
* (1)确定数组的名称
* (2)确定数组元素的类型
* (3)确定数组的结构(包括数组维数,每一维的大小)
* 一般形式:
* 数据类型 标识符[常量表达式1][常量表达式2]……
* 数据类型:可以是基本类型(除了void),也可以是结构体、类等用户自定义类型
* 数组名称:由标识符指定
* 常量表达式:称为数组的界,编译时就可以求出,值必须是正整数
* 数组元素个数:各下标乘积
2、数组的使用
* 使用数组时只能对于数组的各个元素分别进行操作,数组元素是由下标区分的,对于一 个已经声明过的数组,其元素使用形式为
* 数组名[下标1][下标2]……
* 数组下标的表达式可以是任意合法的算数表达式,但是结果必须是整数
* 数组元素的下标不能超过声明时所确定的上下界,否则运行时出现数组越界错误
//数组的声明和使用
int main()
{
int arr[10] = { 1,1,2 };
for (int i = 0; i < 10; i++)
{
arr[i] = i * 2;
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
}
(二)数组的存储和初始化
1、数组的存储
* 数组在内存中的顺序是顺序的连续存储的,二维数组被当成一维数组的数组,存放也是 按照顺序存放的
2、数组的初始化
* 在声明数组时给部分或者全部元素赋初值
* 对于基本类型的数组:就是给数组元素赋值
* 对于对象数组:每个元素都是类的一个对象,初始化就是调用该对象的构造函数
* int a[3]={1,2,3}; int a[]={1,2,3}; int a[5]={1,2,3}//未定义的自动为0
* 对于指定初值个数小于数组大小时,未赋值都为0,
* 若定义的数组没有指定任何一个元素的初值,对于静态生存期,每个元素会为0,若是动态生存期的数组,每个元素的初值不确定
(三)数组名作为函数参数
* 数组元素和数组名都可以作为参数实现函数之间数据的传递和共享
* 数组元素:数组元素做函数参数,与使用该类型的变量(对象做实参是完全一样的)
* 数组名:实参和形参都应该是数组名,且类型相同,使用数组名传递参数时,传递的时 地址,形参和实参首地址重合,后边元素按照顺序
* 在调用函数中,如果对形参数组元素的值发生改变,实参数组元素值也会改变
//使用数组名作为函数的参数列表
void num1(int brr[3][4])
{
for (int i = 0; i < 3; i++)
{
int a = brr[i][0];
for (int j = 0; j < 4; j++)
{
brr[i][0] += brr[i][j];
}
brr[i][0] = brr[i][0] - a;
}
}
int main()
{
int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
cout << arr[i][j]<<" ";
}
cout << endl;
}
num1(arr);
for (int i = 0; i < 3; i++)
{
cout << arr[i][0] << " ";
cout << endl;
}
}
*
(四)对象数组
* 对象数组的元素是对象,不仅具有数据成员,而且还有函数成员,因此,和基本数据类 型数组相比,还有一些特别之处
* 声明一个一维对象数组
* 类名 数组名[常量表达式]
* 每一个数组元素都是一个对象,通过这个对象可以访问到它的公有成员
* 数组名[下标表达式].成员名
* 对象数组初始化过程其实就是调用构造函数的过程,在声明数组时给每一个数组元素指 定初始值,在数组初始化过程中,自动调用与形参相匹配的构造函数
* 当一个数组中的元素对象被删除,就需要调用析构函数来完成收尾工作
//对象数组
class point
{
public:
point()
{
x = 1;
y = 1;
}
point(int a,int b)
{
x = a;
y = b;
}
point(int a)
{
x = a;
y = 1;
}
point(point& p)
{
p.x = x;
p.y = y;
}
void show(int a,int b);
private:
int x;
int y;
};
void point::show(int a,int b)
{
cout << x << y << endl;
}
int main()
{
point arr[3] = {point(),point(2,2),point(2)};
for (int i = 0; i < 3; i++)
{
arr[i].show(2,2);
}
}
二、指针
(一)内存空间的访问方式
* c++通过两个方式利用内存单元存取数据,一是通过变量名,二是通过地址
* 静态生存期的变量在程序开始前就被分配空间,动态生存期的变量在程序运行时,遇到变量声明语句时,被分配内存空间
* 在变量获得内存空间的同时,变量名也就成了相应内存空间的名称,在变量整个生存周期都可以用这个变量名去访问
* 有时候变量名不够方便或者变量名不够时,可以用地址来访问内存单元
(二)指针变量的声明
* 指针也是一种数据类型,具有指针类型的变量称为指针变量,指针变量用于存放内存单元地址
* 通过变量名访问一个变量是直接的,通过指针访问一个变量是间接的
* 指针也是先声明后使用
* 数据类型*标识符
* *表示一个指针类型的变量
* 数据类型可以是任意类型,指的是指针所指向的对象的类型,说明了指针所指向的内存单元可以存放什么类型的数据
* int *ptr;//定义了一个int类型的指针,名字是ptr 专门用来存放int型数据的地址
(三)与地址相关的运算符*和&
* *:指针运算符//用来获取指针所指向变量的值
* &:取地址运算符//用来获取一个对象的地址
//指针的*和&
int main()
{
int a = 3;
int* p ;
int* p1 = &a;
p = &a;//将a的存储单元地址给p;
cout << p<<endl;//a的地址
cout << *p << endl;//指针p所指向的内容
cout << &p;//指针p的存储单元地址
return 0;
}
(四)指针的赋值
* 定义了一个变量,但是变量中并没有确定值,其中地址值是一个不确定的数,定义指针之后必须先赋值再访问
* 在定义指针的同时初始化赋值语句,语法为:
* 存储类型 数据类型*指针名=初始地址
* 在定义之后单独使用赋值语句,语法为:
* 指针名=地址;
* 一个数组可以直接用它的名称来表示它的起始地址,数组名称实际上就是一个不能被赋值的指针,即指针常量
* int a[10];int *ptr=a;ptr中存的是a数组的首地址
* 注意:
* 1、可以声明指向常量的指针,但是不能通过指针来改变所指对象的值,但是指针本身可以改变,可以指向另外的对象
* 2、可以声明指针类型的常量,这时指针的值不能被改变;
* 3、一般情况下,指针只能赋给相同类型的指针,但是void类型指针可以存储任何类型的对象的地址,就是说所有类型的额指针都可以赋值给void类型的指针变量
//指向数组的指针
int main()
{
int a[10] = { 1,2,3 };
int* p = a;
cout << *p;
cout << ++*p;
return 0;
}
//指向常量的指针和指针常量
int main()
{
int a=2;
int b=1;
const int* p = &a;
p = &b;//可以改变指针内容(指向的地址)
//不可以通过指针来改变被指向地址的常量的值
int *const p1 = &a;
*p1 = 2;//可以通过指针来改变指向地址的值,但是不能改变指针内容(指向的地址)
return 0;
}
//void类型的指针的使用
int main()
{
int a = 1;
double b = 1;
int* p1;
void* p;
p = &a;
p1 = static_cast<int*>(p);//static_cast类型转换
cout << *p1;
p = &b;
return 0;
}
(五)指针运算
* 指针的算数运算指的是相同数据类型数据指针之间进行的运算关系,如果两个相同类型的指针相等,就表示这两个指针指向同一个地址
* 指针变量可以和0进行比较,0专用于表示空指针(不指向任何有效地址的指针)//也可以用NULL表示
* 为什么要将指针为空,在没有赋值给一个已经定义的指针时里面的值是不确定的,防止将某个地址赋值给他,会造成错误
(六)用指针处理数组元素
//用指针处理元素数组
int main()
{
int arr[5] = { 1,2,3 };
int* p = arr;
cout << *p << endl;//指向数组的首地址,也就是arr[0]
cout << p[3] << endl;//和*(p+3),在首地址的基础上诺3位
cout << *(p+3) << endl;
//有一个int型数组a,一共有十个元素,用三种办法输出
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
//1、正常输出
for (int i = 0; i < 10; i++)
{
cout << a[i];
}
cout << endl;
//指针输出
int* p1 = a;
for (int i = 0; i < 10; i++)
{
cout << a[i];
cout << *p1;
}
cout << endl;
//使用指针变量
for (int* p2 = a; p2 < (a + 10); p2++)
{
cout << *p2;
}
return 0;
}
(七)指针数组
* 如果一个数组的每个元素都是指针变量,那么这个数组就是指针数组,指针数组的每个元素都必须是同一类型的指针
* 数据类型*数组名[下标表达式]
* 数据类型;表示每个元素指针的类型
* 下标表达式:指出数组元素的个数
* 数组名:指针数组的数组名,同时也是这个数组的首地址
//利用指针数组,输出单位矩阵
int main()
{
//定义单位矩阵
int a[] = { 1,0,0 };
int b[] = { 0,1,0 };
int c[] = { 0,0,1 };
int* d[] = { a,b,c };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
//cout << d[i][j];
cout << *(d[i] + j);
}
cout << endl;
}
return 0;
}
//二维数组用指针输入输出
int main()
{
int a[3][4] = { {1,2,3,4},{1,2,3,4},{1,2,3,4} };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
cout << *(*(a+i)+j);
}
cout << endl;
}
return 0;
}
(八)用指针作为函数参数
* 用指针作为函数参数的三个作用
* (1)使实参和形参指针指向共同的内存空间,达到参数双向传递
* (2)减少函数调用时数据传输的开销
* (3)通过指向函数的指针传递函数代码的首地址
//读入三个浮点数,将整数部分和小数部分分别输出
void shuchu(float a,int*p1,float*p2)
{
*p1 = static_cast<int>(a);//取整数部分
*p2 = a - *p1;//取小数部分
}
int main()
{
for (int i = 0; i < 3; i++)
{
float a, b;
int n;
cin >> a;
shuchu(a,&n,&b);
cout << n<<endl << b;
}
return 0;
}
(九)指针型函数
* 当一个函数的返回值是指针类型时,它就是指针函数
* 数据类型*函数名(){函数体}
(十)指向函数的指针
* 每个函数都有函数名,函数名其实就是函数的代码在内存中的首地址,调用函数的是指就是函数代码首地址
* 函数指针就是专门用来存放函数代码首地址的变量
* 可以像使用函数名一样使用指针来调用函数,函数指针一旦指向了一个函数那么它和函数名具有相同的作用
* 声明一个函数指针也需要说明函数的返回值,形参列表
* 数据类型(*函数名)(形参列表)
* 函数指针也要赋值,使指针指向一个已经存在代码地址的起始地
* 函数指针名=函数名;
//函数指针
void p()
{
cout << 1<<endl;
}
void q()
{
cout << 2 << endl;
}
void m()
{
cout << 3 << endl;
}
int main()
{
typedef void(*A)();//更方便给复杂类型其别名
A fun1;
void (*fun)();//定义一个函数指针
fun = p;
p();
fun = q;
q();
fun = m;
m();
return 0;
}
(十一)对象指针
1、对象指针的一般概念
* 每一个对象在初始化后,都会在内存中占据一定的空间,可以通过对象名也可以通过对象地址来访问一个对象
* 对象所占据的内存只是数据成员的,函数不在每一个对象中存储副本,,对象指针就是用来存放对象地址的变量
* 一般语法形式;
* 类名*对象指针
* 对象指针访问对象中的成员一般语法形式:
* 对象指针->成员名;
//用对象指针访问对象成员;
class point1
{
public:
point1()
{
x = 1;
y = 1;
}
point1(int a, int b) :x(a), y(b) {}
point1(point1& p);
void showpoint();
~point1() {};
private:
int x;
int y;
};
void point1::showpoint()
{
cout << x << y << endl;
}
int main()
{
point1 A;
point1* p=&A;
p->showpoint();
}
2、this指针
* 隐含与每一个类的非静态成员中的特殊函数(包括构造函数和析构函数),它用于指向正在被成员函数操作的对象
* 是类成员函数的一个隐含参数,在调用类的成员函数时,目的对象的地址会自动作为该参数的值,传递给被调用的成员函数
* 这样被调函数就能通过this指针来访问目的对象的数据成员,对于常成员哈桑农户来睡,这个隐含的参数是常指针类型的
* this 是一个指针常量,对于常成员,this又是一个指向常量的指针,在成员函数中可以用*this来标识正在调用该函数的对象
3、指向类的非静态成员的指针
* 先声明再赋值再引用
* 声明指针语句的一般形式;
* 类型说明符 类名::*指针名//指向数据成员的指针
* 类型说明符 (类名::*指针名)(形参表)//指向函数成员的指针
* 指针赋值的一般形式:
* 指针名=&类名::数据成员名;
* 访问数据成员的一般形式:
* 对象名.*类成员指针名
* 对象名->类成员指针名
//访问对象的公有成员函数的不同方式
class point1
{
public:
point1()
{
x = 1;
y = 1;
}
point1(int a, int b) :x(a), y(b) {}
point1(point1& p);
void showpoint();
int A();
~point1() {};
private:
int x;
int y;
};
int point1::A()
{
return x;
}
void point1::showpoint()
{
cout << x << y << endl;
}
int main()
{
point a(4, 5);
point* p1 = &a;
//int(point:: * fun)()const = &point::point ;
a.*fun;//成员函数指针访问成员函数
p1->fun;//成员函数指针和对象函数指针访问成员函数‘
a.getx;//对象名访问成员函数
p1->getX();//对象指针访问成员函数
}
4、指向类的静态成员的指针
* 对类的静态成员访问是不依赖于对象的,可以用普通的指针来指向和访问静态成员
三、动态内存分配
* 可以保证在运行过程中,按照实际需要的内存进行申请,使用结束后被释放
* 这种在程序运行过程中申请和释放的存储单元也称为堆,申请和释放的过程一般被称为建立和删除
* 建立和删除使用两个运算符:new和delete;
* 运算符new的功能是动态内存分配,称为动态创建堆对象
* new 数据类型 (初始化列表)
* 申请分配用于存放指定类型数据的内存空间,并根据初始化参数列表中的值进行初始化
* 申请成功:new运算便返回一个新分配内存首地址类型的指针,可以通过这个给指针对堆进行访问
* 申请失败:抛出异常
* 可以保证在运行过程中,按照实际需要的内存进行申请,使用结束后被释放
* 这种在程序运行过程中申请和释放的存储单元也称为堆,申请和释放的过程一般被称为建立和删除
* 建立和删除使用两个运算符:new和delete;
* 运算符new的功能是动态内存分配,称为动态创建堆对象
* new 数据类型 (初始化列表)
* 申请分配用于存放指定类型数据的内存空间,并根据初始化参数列表中的值进行初始化
* 申请成功:new运算便返回一个新分配内存首地址类型的指针,可以通过这个给指针对堆进行访问
* 申请失败:抛出异常
* (1)、 如果建立的对象十一个基本类型变量,初始化过程就是赋初值
* int*point;
* point=new int(2);
* 动态分配了存储int类型数据的空间,并将初值2,放入空间中,然后将首地址赋给point
* (2)、如果建立对象是某一个类的实例对象,就要根据初始化参数列表的参数类型和个数调用该类的构造函数
* 如果类存在用户定义的默认的构造函数,那么
* new T和new T()两种写法效果相同
* 如果类存在用户定义的默认的构造函数,那么
* new T:会调用系统隐含的默认构造函数
* new T():除了调用构造函数之外,还会为基本数据类型和指针类型的成员用0赋初值,并且是递归的,如果该对象的成员对象也没有默认的构造函数,也会用0赋初值
* delete:用来删除由new建立的对象,释放对象所指向的内存空间
* delete 指针名
* 如果删除的是对象,则,对象中的析构函数被调用,对于new建立的空间只能用delete进行删除,且不能重复删除
* 必须删除,否则会导致程序所占的内存越来越大,这叫做内存泄漏
* new也可以创建数组类型的对象,要给出数组的结构说明
* new 类型名[数组长度]//加()就初始为0,不加就不用
* 删除时 delete[]指针名
//动态内存
int main()
{
int* p;
p = new int(2);
int* p1 = new int;//不希望赋初值可以将括号去了
int* p2 = new int();//带括号但是不写默认用0赋初值
cout << *p << *p1 << *p2;
}
四、用vector创建数组对象
* vector定义动态数组的形式:vector<元素类型>数组对象名(数组长度)
* vector<int>arr(10);
* 所有数组对象都会初始化,基本类型0,类类型,调用构造函数,必须保证作为数组元素的类必须有构造函数
* 也可以赋初值:vector<int>arr(10,2)//初值可以自己指定,但是只能为所有元素指定相同的值
* 数组访问和普通数组一样
* 数组名称[下标表达式]
* vector表示的就是一个数组对象,而不是数组的首地址,因为数组对象不是数组,而是封装了数组的对象
* vector有一个size()函数可以返回数组大小
//vector应用举例
//计算arr数组中元素的平均值
double pingjun(const vector <double>&arr)
{
double sum=0;
for (int i = 0; i < arr.size(); i++)//调用size()函数可以计算数组的长短;
{
sum += arr[i];
}
return sum / arr.size();
}
int main()
{
vector <double>arr(10,1);//定义一个动态数组
cout << pingjun(arr);
return 0;
}
五、深复制和浅复制
* 浅复制:复制出来一个和元对象同一地址的对象
* 深复制:跟源地址不同地址的对象,原对象发生改变对新对象没影响
六、字符串
(一)用字符数组存储和处理字符串
* char str[]={'a','b','f','g','h','j'};
* char str[8]="abfghj";
* char str[]="abfghj";
* 这三种写法是一样的
(二)string类
* 使用数组来存放字符串,调用系统函数来处理字符串不是特别的方便
* string类提供了对字符串进行处理所需要的操作
* 1、string类的构造函数
* 2、string类的操作符
* +:a+b,a和b连接成一个新的字符串
* =:a=b,用b更新a
* +=:和数字计算一样
* ==:相等
* !=:不等于
* <:小于
* <=:小于等于
* >:大于
* >=:大于等于
* 字符串比较规则:
* (1)如果a和b的长度相等,且所有字符完全相同,a=b
* (2)如果字符不完全相同,比较第一对字符的asll码,小的小
* (3)如果a的长度小于b,前面的字符又完全相等,a<b;
(三)常用成员函数功能简介
//string类的构造函数
class string
{
string();//默认构造函数
string(const string& rhs);//复制构造函数
string(const char*s);//用指针s所指向的字符串常量初始化string类的对象
//string(const string&rhs,unsigned int pos,unsigned int n)
//将对象rhs中的串从位置pos开始取n个字符,用来初始化string类的对象
//string(const char* s, unsigned int n);
//用指针s指向的字符串的前n个字符初始化为string类的对象
//string(unsigned int n, char c);
//将参数c中的字符重复n次,用来初始化string类的对象;
};
//string的简单使用
int main()
{
string a="a";
string str2 = "hello";
string str1[] = { "12345676" };
for (int i = 0; i < str2.size();i++)
{
cout << str2[i];
}
cin >> a;
cout <<endl<< a;
return 0;
}
七、深度探索
(一)指针与引用
* 普通指针可以被多次赋值()多次更改指向的对象
* 引用只能在初始化时指定被引用的对象,后期不能更改
* 引用的功能相当于指针常量
* 引用相比于指针,对于参数的传递和减少大随性的参数传递开销来说,引用可以很好的代替指针,使用引用比指针更加简洁安全
* 如果对象在初始化后要再次赋值应该用指针而不是引用
* 指针的值可以为空,但是没有空引用这一说法,有时候空指针会表达特殊的含义
* 函数指针不能被引用代替
* new创建动态对象或者数组要用指针来存储地址//引用也可以,但是不自然,避免
* 以数组形式传递参数时,需要用指针类型接收参数
(二)指针的安全隐患以及应对方案
1、地址安全性
* 变量的地址是由编译器分配的,引用变量时,编译器会使用适当的地址,由编译器保证所引用的地址是分配给这个变量的有效地址
* 而不会访问到其他地址,对于指针,指针的存储地址是程序运行时确定的,如果程序没有给指针赋予有效的值,会造成地址安全隐患
* 一个具有动态生存期的普通变量如果不赋初值就使用,同样会造成安全隐患
* 指针的算数运算:限定在通过指向数组中某一个元素的指针,得到指向同一个数组中另一个地址的指针
2、类型安全隐患
3、堆对象的管理
* new创建delete删除
4、const_cast
* 将数据类型中的const属性删除
* void a(const*cp)
* { int *p=const_cast<int*>(cp) ;
* *p++; }
* 可以将常指针转换为普通指针,将常引用转换为普通引用