文章目录
数组、指针、字符串
数组
数组的定义和使用
数组:具有一定顺序的相同类型的变量的集合。
数组必须先定义后使用。
数组的存储和初始化
–》局部作用域中定义的数组,1.static类型的数组,未初始化时默认值为0。2.非静态的数组,存储的是垃圾数据。
一维数组在内存中是连续存储的,逻辑上的相邻对应着内存中的相邻。
数组名是数组首元素的地址;
数组名是常量不能赋值。
一维数组的初始化:
-
可以全部初始化,也可以只初始化部分元素。
-
全部初始化时,可以省略下标个数。
二维数组在内存中是按行存储的。例如:float a[3][2]
的逻辑结构为
a[0][0] | a[0][1] |
---|---|
a[1][0] | a[1][1] |
a[2][0] | a[2][1] |
内存中存储的方式为:
a[0][0] a[0][1] a[1][0] a[1][1] a[2][0] a[2][1]
其中a[0]
和a[1]
和a[2]
分别是第一行第二行和第三行的首元素的地址。
a
是a[0][0]
的地址。
二维数组的初始化:
-
可以将全部元素都初始化,即
a[3]={1,2,3,4,5,6};
。 -
也可以只初始化部分元素,
-
如
a[3][2]={1,2,3};
,初始化部分元素时,编译器自动按顺序给值,在此例中就会把a[0][0]
到a[1][0]
进行赋值,其他元素不赋值(经过试验,在VS2015中,未初始化的元素的值为0)。 -
另一种方式:精确地给某几行的某前几列的元素赋值:
int a[3][4] = { {}, {1,2,3},{3,4} };
(这种方式是正确的)注意:部分元素初始化时,只能随意决定哪几行而在行确定之后不能确定是哪几列,只能给前某列进行初始化操作。例如:
int a[3][4] = { {}, {1,,3},{3,4} };
就是错误的。
-
-
全部元素都初始化时,只能省略第一维的下标个数
数组名作函数参数
传递的是数组首元素的地址。此时对形参数组的操作就相当于对实参数组的操作。
对象数组
声明语法:类名 数组名[个数]
。
访问数组元素:单个元素进行访问。语法:数组名[下标].成员名
,通过这种方式可以访问对象数组的对象元素的公有成员。
对象数组的初始化
例如Point a[2]={Point(),Point(1,2)};
在初始化过程中,会自动调用对应形参类型的构造函数进行对象的初始化。若没有声明构造函数,则调用默认构造函数。
当一个数组中的对象元素被删除时,会自动调用析构函数进行扫尾工作。(删除对象元素后边会涉及到)
指针
指针的定义和使用
指针变量是用于存放内存单元地址的。
指针变量的声明语法:数据类型 标识符;
例如:int *ptr;
**运算符 * 和 & **
二者是一对相反的运算符
*
代表指针运算符,作用是寻址,即将根据某个地址,得到这个地址所存的变量的值。
&
是地址运算符,作用是得到一个变量的地址。例如:&a
就是返回变量a的地址。
指针的初始化和赋值
指针的初始化
初始化语法:存储类型 数据类型 *指针名 = 初始地址;
例如:int *p = &a;
注意:
-
用变量地址作初始地址时,该变量必须在初始化之前已经声明过,
且变量类型要和指针类型一致。
-
可以用一个已有合法值的指针去初始化另一个指针变量。如:
int *p2 = p1;
其中p1是拥有合法地址值的指针变量。 -
不要用一个内部非静态变量去初始化另一个static指针。
指针的赋值
语法:指针 = 地址;
注意:
-
地址中存放的数据类型必须和指针的类型一致。
-
- 向指针赋的值必须是地址常量或变量,不能是普通整数。
- 合法的地址,比如:
-
- 通过“&”运算符求得的变量的地址。
- 动态分配的内存(malloc)
-
整数 0 可以赋值给指针,代表空指针,作用与NULL和nullptr 相同。nullptr是C++11的特性,更加安全。
-
允许定义和声明指向 void 类型的指针,该指针可以被赋予任何类型对象的地址。
- void 类型的指针,如
void *vp;
不能被引用(*vp)。因为void 类型的指针只有所存的地址,而不知道长度,因此无法进行寻址。 - 关于void类型指针的介绍
- void 类型的指针,如
-
空指针(void)类型指针的使用,不能像普通指针一样直接通过解引用(&)运算符使用,但是可以先将其使用 static_cast<>() 函数强制转换为某种具体类型的指针在引用。如:
-
void *p; int a=89; p = &a; int *p2 = static_cast<int *>(p); printf("%p\t%d", static_cast<int *>(p),*p2 );
const 指针
const 指针:指向常量的指针。
定义语法:const 指针类型 *指针名 = 地址
例如:
int a=67;
const int *p = &a;
int b = 89;
p = &b; //正确,可以改变const指针指向的对象
*p = 90;//错误,不能通过const指针修改指向的对象
const 指针代表可以改变该指针所指向的对象(比如以前指向变量a,后来指向变量b),但是不能通过const指针修改它所指向的对象的值。正如上例所示。它相当于一个只读的指针
指针类型的常量
定义语法:指针类型* const 指针名 = 地址;
(注意与const指针的区别)
若声明指针类型的常量,则指针的值不能改变。即只能通过初始化指向一个地址,然后就不能更改了。
例如:
int a;
int b;
int * const p = &a;//正确
p = &b;//错误,不能修改指针常量
指针的代数运算和关系运算
代数运算
- 指针变量加上 n 或减去 n 的意义是指针当前位置的后n个或前n个数据的起始位置。(p+1)
- 指针的自增、自减运算:指针指向的位置向后或向前移动了一个单位(sizeof(指针类型))
- 当指针指向连续存储的同类型数据时,代数运算和自增、自减运算才有意义。
关系运算
- 指向相同类型数据的指针之间可以进行关系运算。
- 指向不同类型数据的指针、指针与普通整数之间的关系运算是无意义的。
- 指针和 0 之间可以进行等于和不等于的关系运算。p==0; p!=0;
指针访问数组
指针存放的是地址,而数组名也是数组首元素的地址。可以将数组名赋值给指针,然后通过指针来访问数组。
int a[3] = {…}; int *p=a;
a[0]与* §;a[i]与 * (p+i)是一致的。而 p+i 也就是a[i] 的地址。
指针数组
指针数组就是元素为指针的数组。
定义语法:指针类型 *指针数组名[下标个数] = {元素值列表};
相当于二维数组,但是指针数组每行的列数是可以不同的。
实例程序:
int main(){
int line1[3] = { 1,0,0 };
int line2[3] = { 0,1,0 };
int line3[3] = { 0,0,1 };
//指针数组
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 << "\n";
}
return 0;
}
//输出结果
/*
1 0 0
0 1 0
0 0 1
*/
指针函数
指针函数:返回值为指针的函数。
指针函数的定义形式:
存储类型 数据类型 *函数名(参数列表){
函数体
}
注意:不要将非静态局部地址(在被调函数内部声明的非静态地址)作为返回值。如:函数体内定义的局部变量的地址就不能作为返回值。
合法的例子:主调函数中作为参数传递给被调函数的地址可以作为返回值。再比如,被调函数中使用 new 运算符分配的内存的地址(后面记得释放即可)。
函数指针
函数指针是一个指向函数的指针。
定义语法:[存储类型] 数据类型 (*指针名)(参数表列)
例如:int *(func)(int ,int)
含义:函数指针指向的是程序代码存储区。
函数指针的用途:可以实现函数回调,即函数回调是函数指针的一种用法。这样的方式调用函数,有很多的好处,比如提高了程序的灵活性,并且可以实现解耦,关于回调函数的详细介绍,可以参照百度。回调函数的讲解
程序实例:
int max(int a, int b) {
return a > b ? a : b;
}
int min(int a, int b) {
return a < b ? a : b;
}
int sum(int a, int b) {
return a + b;
}
int compute(int a, int b, int(*func)(int, int)) {
return func(a, b);
}
int main(){
int a, b, res;
cout << "请输入a:"; cin >> a;
cout << "请输入b:";cin >> b;
res = compute(a, b, &max);
cout << "The max of " << a << " and " << b << " is " << res << endl;
res = compute(a, b, &min);
cout << "The min of " << a << " and " << b << " is " << res << endl;
res = compute(a, b, &sum);
cout << "The sum of " << a << " and " << b << " is " << res << endl;
return 0;
}
对象指针
语法形式:类名 *指针名;
例如:
Point a(3,4);
Point *p;
p=&a;
通过指针访问对象成员:
对象指针名->成员名
ptr->getX();//等价于
(*ptr).getX();
this 指针
- 隐藏于类的每一个非静态成员函数中。
- 指出成员函数所操作的对象
- 当通过一个对象调用成员函数时,系统先将该对象的地址赋给this指针,然后调用成员函数,成员函数对对象的数据成员进行操作时,就隐含地使用了this指针
- 例如Point 类中的getX()函数中的语句:return x;就相当于return this->x;
->通过对象指针,我们可以解决前面前向引用声明部分的错误了,
class Fred;
class Barney{
Fred x; //错误:类Fred的声明尚不完善
};
class Fred{
Barney y;
};
/*解决方法:将Fred x改为Fred *x,然后再通过动态内存分配给指针x一个合适大小的地址。
不过在实际情况中,需要这种代码的现象很少
*/
动态内存分配
在我们不知道问题规模有多大的时候,无法使用数组来进行分配内存,只能通过动态内存分配的形式完成工作,然而此时我们又无法给它一个变量名或数组名,那么我们就只能通过指针来访问这一段内存了。这也说明了我们为什么在有了数组名访问数组元素的情况下还要学习指针的使用。
动态申请内存操作符:new
new 类型名T (初始化参数列表)
功能:在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值。
结果值:T类型的指针,指向新分配到内存;若失败,抛出异常
释放内存操作符 delete
delete 指针p;
功能:释放指针p所指向的内存。p必须是new 操作的返回值。
这种在程序运行过程中申请和释放的存储单元也称为堆对象,申请和释放过程一般称为建立和删除。
注意:
用 new 分配的内存,必须用de!ete 加以释放,否则会导致动态分配的内存无法回收,使得程序占据的内存越来越大,这叫做"内存泄漏"。
分配和释放动态数组
- 分配:new 类型名T [数组长度]
- 数组长度可以是任意整数表达式,在运行时计算
- 释放 :delete[] 数组名p
- 释放指针p所指向的数组。p必须是动态分配得到的数组首地址。
- 若省略方括号,则只释放数组首元素地址的内存
动态创建多维数组
语法:new 类型名T[一维下标][二维下标]...
若申请内存成功,new运算返回一个指向新分配内存首地址的指针。
例如:char (*fp)[2]; fp = new char[3][2];
智能指针
vector
为什么需要vector?
- 封装任何类型的动态数组,自动创建和删除。
- 数组下标越界检查。
定义和使用vector:
- 先包含头文件
#include<vector>
- vector对象定义的语法:vector<元素类型> 数组对象名(数组长度);
- 例:
vector<int> arr(3);
建立长度为3的int数组
使用:
- 引用数组元素:与普通数组相同。vector对象名[下标表达式]
- vector数组对象名不代表数组首地址
- 获得数组长度: vector对象名.size()
深层复制与浅层复制
二者的区别主要体现在复制出的对象的指针成员所指向的内存(通常是动态分配出来的)是否是同一片。
浅层复制:同名的指针指向同一片地址。
深层复制:同名的指针指向不同的地址。
移动构造
C++11提供的构造方式
C++11引入移动语义:将源对象资源控制权全部交给目标对象。
使用情况:需要一个临时无名对象而这个无名对象快要消亡时,但是我们任然需要它的一些资源,除了使用复制构造函数,还可以使用移动构造函数。
程序:
class IntNum {
public:
IntNum(int x=0) :xptr(new int(x)) {
cout << "Calling constructor \n";
}
IntNum(const IntNum &x) :xptr(new int(*x.xptr)) {
cout << "Calling copy constructor\n";
}
//移动构造
IntNum(IntNum && n) :xptr(n.xptr) {
n.xptr = nullptr;
cout << "Calling move constructor\n";
}
~IntNum() {
cout << "Destructor\n";
}
int getInt() { return *xptr; }
private:
int *xptr;
};
IntNum getIntNum() {
IntNum a;
return a;
}
int main(){
cout << getIntNum().getInt() << endl;
return 0;
}
对上述程序的补充:
- &&代表右值引用
- 函数返回的临时变量是右值
在某个类有移动构造函数时,会优先调用移动构造函数进行资源的交接(C++11)。
移动构造函数的关键就在右值引用和源对象的指针置为空指针两点上。
字符串
C风格字符串
字符串常量
- 各字符连续、顺序存放,每个字符占一个字节,以’\0’结尾,相当于一个隐含创建的字符常量数组。
- "program"出现在表达式中,表示这一字符数组的首地址。
- 首地址可以赋值给char类型指针–有无 const 皆可
用字符数组存放字符串(C风格字符串)
char s1[8] = { 'p','r','o','g','r','a','m' };
char s2[8] = "program";
char s3[] = "program";
char *s4 = "program";
//以上四种都是可以的
string类
–》先包含string头文件
string类常用的构造函数:
-
string()//默认构造函数
string s1;//构造空字符串s1
-
string(const char *str)//用指针str指向的字符串常量初始化string对象
string s2="abc";
-
string(const string& res)//复制构造函数
string s3=s2;//复制字符串s2到s3
string类常用的操作:
- 拼接:s+t
- 更新:s=t
- 判断是否相等或不等:s==t ,s != t 返回bool值
- 判断大小:s<t, s>t, s<=t,s>=t
- 访问串中的字符:s[i],返回值为char类型
getline()函数:共有两种用法
- getline(cin,str,char):以第三个char类型的参数作为结束符
- getline(cin,str):以空格作为结束符
int main() {
for (int i = 0; i < 2; i++) {
string city, state;
getline(cin,state,',');
getline(cin, city);
cout << "state: " << state << " city: " << city << endl;
}
return 0;
}
结果:
China,Beijing
state: China city: Beijing
China,Shanghai
state: China city: Shanghai
补充
floor函数:
数学上称为高斯函数。
包含在 cmath 头文件中。作用是将一个小数进行向下取整,即得到不大于一个小数的最大整数(相当于直接斩尾)。
常用操作:
- 四舍五入取整:floor(a+0.5);将小数a进行四舍五入。
- 保留两位小数:floor(amount * 100 + 0.5) / 100
与它功能类似的函数 :ceil() 函数,向上取整,得到不小于一个数的最小整数。
这两个函数的参数都是double型的。
++) {
string city, state;
getline(cin,state,’,’);
getline(cin, city);
cout << "state: " << state << " city: " << city << endl;
}
return 0;
}
结果:
China,Beijing
state: China city: Beijing
China,Shanghai
state: China city: Shanghai
# 补充
**floor函数:**
数学上称为高斯函数。
包含在 cmath 头文件中。作用是将一个小数进行**向下取整**,即得到不大于一个小数的最大整数(相当于直接斩尾)。
常用操作:
- 四舍五入取整:floor(a+0.5);将小数a进行四舍五入。
- 保留两位小数:floor(amount * 100 + 0.5) / 100
与它功能类似的函数 :ceil() 函数,向上取整,得到不小于一个数的最小整数。
这两个函数的参数都是double型的。