上一节学习了C++的函数:c++:-2,本节学习C++的数组、指针和字符串

数组

定义和初始化

定义



例如:int a[10];

表示a为整型数组,有10个元素:a[0]...a[9]

例如: int a[5][3];

表示a为整型二维数组,其中第一维有5个下标(04),第二维有3个下标(02),数组的元素个数为15,可以用于存放5行3列的整型数据表格。

使用

  • 必须先声明,后使用。
  • 只能逐个引用数组元素,而不能一次引用整个数组

例如:a[0]=a[5]+a[7]-a[2*3]

例如:b[1][2]=a[2][3]/2

存储

(1)一维数组的存储

数组元素在内存中顺次存放,它们的地址是连续的。元素间物理地址上的相邻,对应着逻辑次序上的相邻。



(2)二维数组的存储

按行存放

例如: float a[3][4];

可以理解为:



其中数组a的存储顺序为:

\[a00,a01,a02,a03,a10,a11,a12,a13,a20,a21,a22,a23
\]

初始化

(1)一维数组的初始化

  • 在定义数组时给出数组元素的初始值。
  • 列出全部元素的初始值

    例如: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}

(2)二维数组的初始化

  • 将所有初值写在一个{}内,按顺序初始化

    例如:static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12
  • 分行列出二维数组元素的初值

    例如:static int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
  • 可以只对部分元素初始化

    例如:static int a[3][4]={{1},{0,6},{0,0,11}};
  • 列出全部初始值时,第1维下标个数可以省略

    例如:static int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};

    或:static int a[][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
  • 注意:

    如果不作任何初始化,内部auto型数组中会存在垃圾数据,static数组中的数据默认初始化为0

    如果只对部分元素初始化,剩下的未显式初始化的元素,将自动被初始化为零

传参

  • 数组元素作实参,与单个变量一样。
  • 数组名作参数,形、实参数都应是数组名(实质上是地址),类型要一样,传送的是数组首地址。
  • 对形参数组的改变会直接影响到实参数组。

对象数组

定义和访问

(1)定义对象数组

类名 数组名[元素个数];

(2)访问对象数组元素

通过下标访问:数组名[下标].成员名

初始化

  • 数组中每一个元素对象被创建时,系统都会调用类构造函数初始化该对象。
  • 通过初始化列表赋值。

    例:Point a[2]={Point(1,2),Point(3,4)};
  • 如果没有为数组元素指定显式初始值,数组元素便使用默认值初始化(调用默认构造函数)。
  • 元素所属的类不声明构造函数,则采用默认构造函数。
  • 各元素对象的初值要求为相同的值时,可以声明具有默认形参值的构造函数。
  • 各元素对象的初值要求为不同的值时,需要声明带形参的构造函数。
  • 当数组中每一个对象被删除时,系统都要调用一次析构函数

基于范围的for循环

原for循环

#include "iostream"

using namespace std;
int main() {
int array[3] = {1, 2, 3};
int *p;
for (p = array; p < array + sizeof(array) / sizeof(int); ++p) {
*p += 2;
std::cout << *p << std::endl;
}
}

新for循环

#include "iostream"

using namespace std;
int main() {
int array[3] = {1, 2, 3};
for(int & e : array)
{
e += 2;
std::cout<<e<<std::endl;
}
return 0;
}

指针

(1)内存空间的访问方式

  • 通过变量名访问
  • 通过地址访问

(2)指针的概念

  • 指针:内存地址,用于间接访问内存单元
  • 指针变量:用于存放地址的变量

定义

(1)例:

static int i;

static int* ptr = &i;

(2)例:

*ptr = 3;

(3)与地址相关的运算——“*”和“&”

  • 指针运算符:*
  • 地址运算符:&

初始化

(1)语法形式:存储类型 数据类型 *指针名=初始地址;

例:int *pa = &a;

(2)注意事项

  • 用变量地址作为初值时,该变量必须在指针初始化之前已声明过,且变量类型应与指针类型一致。
  • 可以用一个已有合法值的指针去初始化另一个指针变量。
  • 不要用一个内部非静态变量去初始化 static 指针。

赋值

(1)语法形式:指针名=地址

注意:“地址”中存放的数据类型与指针类型必须相符

(2)向指针变量赋的值必须是地址常量或变量,不能是普通整数。例如:

  • 通过地址运算“&”求得已定义的变量和对象的起始地址
  • 动态内存分配成功时返回的地址

(3)例外:整数0可以赋给指针,表示空指针

(4)允许定义或声明指向 void 类型的指针。该指针可以被赋予任何类型对象的地址。

例: void *general;

指针空值nullptr

(1)以往用0或者NULL去表达空指针的问题:

C/C++的NULL宏是个被有很多潜在BUG的宏。因为有的库把其定义成整数0,有的定义成 (void*)0。在C的时代还好。但是在C++的时代,这就会引发很多问题。

(2)C++11使用nullptr关键字,是表达更准确,类型安全的空指针

指向常量的指针

不能通过指向常量的指针改变所指对象的值,但指针本身可以改变,可以指向另外的对象。

例:

int a;
const int *p1 = &a; //p1是指向常量的指针
int b;
p1 = &b; //正确,p1本身的值可以改变
*p1 = 1; //编译时出错,不能通过p1改变所指的对象(常量)

指针类型的常量

若声明指针常量,则指针本身的值不能被改变。

例:

int a;
int * const p2 = &a;
p2 = &b; //错误,p2是指针常量,不能改变

运算

算术运算

  • 指针p加上或减去n

    其意义是指针当前指向位置的前方或后方第n个数据的起始位置。
  • 指针的++、--运算

    意义是指向下一个或前一个完整数据的起始。
  • 运算的结果值取决于指针指向的数据类型,总是指向一个完整数据的起始位置。
  • 当指针指向连续存储的同类型数据时,指针与整数的加减运和自增自减算才有意义。

    (1)指针与整数相加的意义

关系运算

  • 指向相同类型数据的指针之间可以进行各种关系运算。
  • 指向不同数据类型的指针,以及指针与一般整数变量之间的关系运算是无意义的。
  • 指针可以和零之间进行等于或不等于的关系运算。

    例如:p==0或p!=0

指针与数组

用指针访问数组

数组是一组连续存储的同类型数据,可以通过指针的算术运算,使指针依次指向数组的各个元素,进而可以遍历数组。

(1)定义与赋值:

例: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]都是等效的。

(2)注意:

不能写 a++,因为a是数组首地址、是常量。

(3)举例:

设有一个int型数组a,有10个元素。用三种方法输出各元素:

方法1:使用数组名和下标

#include "iostream"
using namespace std;
int main() {
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
for (int i = 0; i < 10; i++)
cout << a[i] << " ";
cout << endl;
return 0;
}

方法2:使用数组名和指针运算

#include "iostream"
using namespace std;
int main() {
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
for (int i = 0; i < 10; i++)
cout << *(a+i) << " ";
cout << endl;
return 0;
}

方法3:使用指针变量

#include "iostream"
using namespace std;
int main() {
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
for (int *p = a; p < (a + 10); p++)
cout << *p << " ";
cout << endl;
return 0;
}

指针与函数

以指针作为函数参数

为什么需要用指针做参数?

  • 需要数据双向传递时(引用也可以达到此效果)
  • 用指针作为函数的参数,可以使被调函数通过形参指针存取主调函数中实参指针指向的数据,实现数据的双向传递
  • 需要传递一组数据,只传首地址运行效率比较高
  • 实参是数组名时形参可以是指针

指针类型的函数



(1)不要将非静态局部地址用作函数的返回值



(2)例子

#include "iostream"
using namespace std; int *search(int *a,int num)
{
for(int i=0;i<10;i++)
{
if(a[i]==0)
return &a[i];
}
} int main()
{
int arr[10];
int *search(int *a,int num);
for(int i=0;i<10;i++){
cin >> arr[i];
}
int *zeroptr= search(arr,10);
return 0;
}

指向函数的指针

(1)定义形式

存储类型 数据类型 (*函数指针名)();

(2)含义

函数指针指向的是程序代码存储区。

(3)用途

实现函数回调

  • 通过函数指针调用的函数
  • 例如将函数的指针作为参数传递给一个函数,使得在处理相似事件的时候可以灵活的使用不同的方法。

    调用者不关心谁是被调用者
  • 需知道存在一个具有特定原型和限制条件的被调用函数。

对象指针

(1)定义

类名 *对象指针名;

(2)例

Point a(5,10);
Piont *ptr;
ptr=&a;

(3)通过指针访问对象成员

对象指针名->成员名

例:ptr->getx() 相当于** (*ptr).getx();**

(4)举例

#include <iostream>
using namespace std; class Point {
public:
Point(int x = 0, int y = 0) : x(x), y(y) { }
int getX() const { return x; }
int getY() const { return y; }
private:
int x, y;
};
int main() {
Point a(4, 5);
Point *p1 = &a; //定义对象指针,用a的地址初始化
cout << p1->getX() << endl;//用指针访问对象成员
cout << a.getX() << endl; //用对象名访问对象成员
return 0;
}
输出:
4
4

this指针

  • 指向当前对象自己
  • 隐含于类的每一个非静态成员函数中
  • 指出成员函数所操作的对象

    当通过一个对象调用成员函数时,系统先将该对象的地址赋给this指针,然后调用成员函数,成员函数对对象的数据成员进行操作时,就隐含使用了this指针。
  • 例如:Point类的getX函数中的语句:return x;

    相当于: return this->x;
class Fred; //前向引用声明
class Barney {
Fred *x;
};
class Fred {
Barney y;
};

动态内存分配

分配与释放

(1)动态申请内存操作符 new

new 类型名T(初始化参数列表)

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

结果值:成功:T类型的指针,指向新分配的内存;失败:抛出异常。

(2)释放内存操作符delete

delete 指针p

功能:释放指针p所指向的内存。p必须是new操作的返回值。

(3)举例

#include <iostream>
using namespace std; class Point {
public:
Point() : x(0), y(0) {
cout<<"Default Constructor called."<<endl;
}
Point(int x, int y) : x(x), y(y) {
cout<< "Constructor called."<<endl;
}
~Point() { cout<<"Destructor called."<<endl; }
int getX() const { return x; }
int getY() const { return y; }
void move(int newX, int newY) {
x = newX;
y = newY;
}
private:
int x, y;
}; int main() {
cout << "Step one: " << endl;
Point *ptr1 = new Point; //调用默认构造函数
delete ptr1; //删除对象,自动调用析构函数
cout << "Step two: " << endl;
ptr1 = new Point(1,2);
delete ptr1;
return 0;
}
输出:
Step one:
Default Constructor called.
Destructor called.
Step two:
Constructor called.
Destructor called.

动态数组



动态多维数组





智能指针

  • 显式管理内存在是能上有优势,但容易出错。
  • C++11提供智能指针的数据类型,对垃圾回收技术提供了一些支持,实现一定程度的内存管理
  • 三类:

    unique_ptr :不允许多个指针共享资源,可以用标准库中的move函数转移指针

    shared_ptr :多个指针共享资源

    weak_ptr :可复制shared_ptr,但其构造或者释放对资源不产生影响

vector对象

(1)为什么需要vector?

封装任何类型的动态数组,自动创建和删除。

数组下标越界检查。

(2)vector对象的定义

vector<元素类型> 数组对象名(数组长度);

(3)例:

vector<int> arr(5)//建立大小为5的int数组

vector对象的使用

(1)对数组元素的引用

与普通数组具有相同形式:vector对象名 [ 下标表达式 ]

(2)vector数组对象名不表示数组首地址

获得数组长度用size函数:数组对象名.size()

(3)举例:

#include <iostream>
#include <vector>
using namespace std; //计算数组arr中元素的平均值
double average(const vector<double> &arr) //vector的引用
{
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;
}
输出:
n = 5
Please input 5 real numbers:
1
2
3
4
6
Average = 3.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...
接下来程序出现运行错误。

深层复制

当被复制的对象数据成员是指针类型时,不是复制该指针成员本身,而是将指针所指对象进行复制

当返回的对象含有动态创建的空间时,需要用深层复制

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

移动构造函数

在现实中有很多这样的例子,我们将钱从一个账号转移到另一个账号,将手机SIM卡转移到另一台手机,将文件从一个位置剪切到另一个位置……移动构造可以减少不必要的复制,带来性能上的提升。

  • C++11标准中提供了一种新的构造方法——移动构造。
  • C++11之前,如果要将源对象的状态转移到目标对象只能通过复制。在某些情况下,我们没有必要复制对象——只需要移动它们。
  • C++11引入移动语义:源对象资源的控制权全部交给目标对象

问题

当临时对象在被复制后,就不再被利用了。我们完全可以把临时对象的资源直接移动,这样就避免了多余的复制操作。

移动构造

什么时候该触发移动构造?

有可被利用的临时对象

移动构造函数:class_name ( class_name && )

字符串

字符数组

  • 字符串常量

    例:"program"
  • 各字符连续、顺序存放,每个字符占一个字节,以‘\0’结尾,相当于一个隐含创建的字符常量数组
  • “program”出现在表达式中,表示这一char数组的首地址
  • 首地址可以赋给char常量指针:

    const char *STRING1 = "program";

缺点

  • 执行连接、拷贝、比较等操作,都需要显式调用库函数,很麻烦
  • 当字符串长度很不确定时,需要用new动态创建字符数组,最后要用delete释放,很繁琐
  • 字符串实际长度大于为它分配的空间时,会产生数组下标越界的错误

string

使用字符串类string表示字符串

string实际上是对字符数组操作的封装

构造函数

(1)string(); //默认构造函数,建立一个长度为0的串

例:

string s1;

(2)string(const char *s); //用指针s所指向的字符串常量初始化string对象

例:

string s2 = “abc”;

(3)string(const string& rhs); //复制构造函数

例:

string s3 = s2;

常用操作

  • 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的字符

例:

string s1 = "abc", s2 = "def";
string s3 = s1 + s2; //结果是"abcdef"
bool s4 = (s1 < s2); //结果是true
char s5 = s2[1]; //结果是'e'

getline

如何输入整行字符串?

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

  • getline可以输入整行字符串(要包string头文件),例如:getline(cin, s2);
  • 输入字符串时,可以使用其它分隔符作为字符串结束的标志(例如逗号、分号),将分隔符作为getline的第3个参数即可,例如:getline(cin, s2, ',');

程序

数组

存放数组

求Fibonacci数列的前20项

#include <iostream>
using namespace std; int main() {
int i;
int f[20] = {1,1}; //初始化第0、1个数
for (i = 2; i < 20; i++) //求第2~19个数
f[i] = f[i - 2] + f[i - 1];
for (i=0;i<20;i++) { //输出,每行5个数
if (i % 5 == 0) cout << endl;
cout.width(12); //设置输出宽度为12
cout << f[i];
}
return 0;
}
输出:
1 1 2 3 5
8 13 21 34 55
89 144 233 377 610
987 1597 2584 4181 6765

统计正确率

循环从键盘读入若干组选择题答案,计算并输出每组答案的正确率,直到输入ctrl+z为止。每组连续输入5个答案,每个答案可以是'a','b','c','d'。

#include <iostream>
using namespace std; int main() {
const char key[ ] = {'a','c','b','a','d'};
const int NUM_QUES = 5;
char c;
int ques = 0, numCorrect = 0;
cout << "Enter the " << NUM_QUES << " question tests:" << endl;
while(cin.get(c)) //cin.get(c)得到的是输出流,所以只需要一次输入
{
if(c != '\n') {
if(c == key[ques]) {
numCorrect++; cout << " ";
} else
cout<<"*";
ques++;
} else {
cout << " Score " << static_cast<float>(numCorrect)/NUM_QUES*100 << "%";
ques = 0; numCorrect = 0; cout << endl;
}
}
return 0;
}
输出:
Enter the 5 question tests:
abcda
**** Score 20%
abcad
** Score 60%
cabcd
** * Score 40%
ddaca
***** Score 0%

使用数组名作为函数参数

主函数中初始化一个二维数组,表示一个矩阵,矩阵,并将每个元素都输出,然后调用子函数,分别计算每一行的元素之和,将和直接存放在每行的第一个元素中,返回主函数之后输出各行元素的和。

#include <iostream>
using namespace std; void rowSum(int a[][4], int nRow) {
for (int i = 0; i < nRow; 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}};
//输出数组元素
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++)
cout << table[i][j] << " ";
cout << endl;
}
rowSum(table, 3); //调用子函数,传入的是数组名,计算各行和
//输出计算结果
for (int i = 0; i < 3; i++)
cout << "Sum of row " << i << " is " << table[i][0] << endl;
return 0;
}
输出:
1 2 3 4
2 3 4 5
3 4 5 6
Sum of row 0 is 10
Sum of row 1 is 14
Sum of row 2 is 18

对象数组应用

//Point.h
#ifndef _POINT_H
#define _POINT_H
class Point { //类的定义
public: //外部接口
Point();
Point(int x, int y);
~Point();
void move(int newX,int newY);
int getX() const { return x; }
int getY() const { return y; }
static void showCount(); //静态函数成员
private: //私有数据成员
int x, y;
};
#endif //_POINT_H //Point.cpp
#include <iostream>
#include "Point.h"
using namespace std;
Point::Point() : x(0), y(0) {
cout << "Default Constructor called." << endl;
}
Point::Point(int x, int y) : x(x), y(y) {
cout << "Constructor called." << endl;
}
Point::~Point() {
cout << "Destructor called." << endl;
}
void Point::move(int newX,int newY) {
cout << "Moving the point to (" << newX << ", " << newY << ")" << endl;
x = newX;
y = newY;
} #include "Point.h"
#include <iostream>
using namespace std;
int main() {
cout << "Entering main..." << endl;
Point a[2];
for(int i = 0; i < 2; i++)
a[i].move(i + 10, i + 20);
cout << "Exiting main..." << endl;
return 0;
}
输出:
Entering main...
Default Constructor called.
Default Constructor called.
Moving the point to (10, 20)
Moving the point to (11, 21)
Exiting main...
Destructor called.
Destructor called.

指针

void指针

#include <iostream>
using namespace std; int main() {
//!void voidObject; 错,不能声明void类型的变量
void *pv; //对,可以声明void类型的指针
int i = 5;
pv = &i; //void类型指针指向整型变量
int *pint = static_cast<int *>(pv); //void指针转换为int指针
cout << "*pint = " << *pint << endl;
return 0;
}
输出:
*pint = 5

指针表示矩阵

#include "iostream"
using namespace std; int main()
{
int line1[]={1,0,1};
int line2[]={1,0,1};
int line3[]={1,0,1}; //定义整型指针数组并初始化
int *pline[3]={line1,line2,line3};
cout <<"矩阵:"<< endl; //输出
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
cout <<pline[i][j]<<" ";
}
cout << endl;
}
return 0;
}
输出:
矩阵:
1 0 1
1 0 1
1 0 1

指针做形参

读入三个浮点数,将整数部分和小数部分分别输出

#include "iostream"
using namespace std; void splitFloat(float x, int *intPart, float *fracPart) {
*intPart = static_cast<int>(x); //取x的整数部分
*fracPart = x - *intPart; //取x的小数部分
}
int main() {
cout << "Enter 3 float point numbers:" << endl;
for(int i = 0; i < 3; i++) {
float x, f;
int n;
cin >> x;
splitFloat(x, &n, &f); //变量地址作为实参
cout << "Integer Part = " << n <<endl<< "Fraction Part = " << f << endl;
}
return 0;
}

指向常量的指针做形参

#include "iostream"
using namespace std; const int N = 6;
void print(const int *p, int n);
int main() {
int array[N];
for (int i = 0; i < N; i++)
cin>>array[i];
print(array, N);
return 0;
}
void print(const int *p, int n) {
cout << "{ " << *p;
for (int i = 1; i < n; i++)
cout << ", " << *(p+i);
cout << " }" << endl;
}
输出:
1
2
3
4
5
6
{ 1, 2, 3, 4, 5, 6 }

函数指针

编写一个计算函数compute,对两个整数进行各种计算。有一个形参为指向具体算法函数的指针,根据不同的实参函数,用不同的算法进行计算。编写三个函数:求两个整数的最大值、最小值、和。分别用这三个函数作为实参,测试compute函数

#include <iostream>
using namespace std; int compute(int a, int b, int(*func)(int, int))
{ return func(a, b);}
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 main()
{
int a, b, res;
cout << "请输入整数a:"; cin >> a;
cout << "请输入整数b:"; cin >> b;
res = compute(a, b, & max);
cout << "Max of " << a << " and " << b << " is " << res << endl;
res = compute(a, b, & min);
cout << "Min of " << a << " and " << b << " is " << res << endl;
res = compute(a, b, & sum);
cout << "Sum of " << a << " and " << b << " is " << res << endl;
}
输出:
请输入整数a:12
请输入整数b:32
Max of 12 and 32 is 32
Min of 12 and 32 is 12
Sum of 12 and 32 is 44

动态数组类

#include <iostream>
#include <cassert>
using namespace std;
class Point { //类的声明同例6-16 … };
class ArrayOfPoints { //动态数组类
public:
ArrayOfPoints(int size) : size(size) {
points = new Point[size];
}
~ArrayOfPoints() {
cout << "Deleting..." << endl;
delete[] points;
}
Point& element(int index) {
assert(index >= 0 && index < size);
return points[index];
}
private:
Point *points; //指向动态数组首地址
int size; //数组大小
}; int main() {
int count;
cout << "Please enter the count of points: ";
cin >> count;
ArrayOfPoints points(count); //创建数组对象
points.element(0).move(5, 0); //访问数组元素的成员
points.element(1).move(15, 20); //访问数组元素的成员
return 0;
}
运行结果:
Please enter the number of points:2
Default Constructor called.
Default Constructor called.
Deleting...
Destructor called.
Destructor called.

vector数组

#include <vector>
#include <iostream> int main()
{
std::vector<int> v = {1,2,3};
for(auto i = v.begin(); i != v.end(); ++i)
std::cout << *i << std::endl;
for(auto e : v)//带范围的for循环
std::cout << e << std::endl;
}

移动构造

函数返回含有指针成员的对象

(1)先给出使用深层复制构造函数,返回时构造临时对象,动态分配将临时对象返回到主调函数,然后删除临时对象。

#include<iostream>
using namespace std; class IntNum {
public:
IntNum(int x = 0) : xptr(new int(x)){ //构造函数
cout << "Calling constructor..." << endl;
}
//n是参数对象的引用;*n.xptr取值;xptr(new int(*n.xptr)是深拷贝
IntNum(const IntNum & n) : xptr(new int(*n.xptr)){//复制构造函数
cout << "Calling copy constructor..." << endl;
};
~IntNum(){ //析构函数
delete xptr;
cout << "Destructing..." << endl;
}
int getInt() { return *xptr; }
private:
int *xptr;
};
//返回值为IntNum类对象
IntNum getNum() {
IntNum a;//新建一个局部对象,调用构造函数
return a; //返回一个对象,调用复制构造函数
}
int main() {
IntNum p=getNum();
cout<<p.getInt()<<endl;
return 0;
}
输出:
Calling constructor...
Calling copy constructor...
Destructing...
0
Destructing...

(2)使用移动构造函数,将要返回的局部对象转移到主调函数,省去了构造和删除临时对象的过程。

#include<iostream>
using namespace std; class IntNum {
public:
IntNum(int x = 0) : xptr(new int(x)){ //构造函数
cout << "Calling constructor..." << endl;
}
IntNum(const IntNum & n) : xptr(new int(*n.xptr)){//复制构造函数
cout << "Calling copy constructor..." << endl;
//注:•&&是右值引用;•函数返回的临时变量是右值
}
IntNum(IntNum && n): xptr( n.xptr){ //移动构造函数
n.xptr = nullptr;
cout << "Calling move constructor..." << endl;
}
~IntNum(){ //析构函数
delete xptr;
cout << "Destructing..." << endl;
}
int getInt() { return *xptr; }
private:
int *xptr;
};
//返回值为IntNum类对象
IntNum getNum() {
IntNum a;
return a;
}
int main() {
cout << getNum().getInt() << endl; return 0;
}
输出:
Calling constructor...
Calling move constructor...
Destructing...
0
Destructing...

上面两个程序输出是有问题的,待解决!

字符串

string

#include <string>
#include <iostream>
using namespace std; //根据value的值输出true或false
//title为提示文字
inline 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;
}
输出:
s1 is DEF
Please enter s2: eqwe
length of s2: 4
s1 <= "ABC" returns false
"DEF" <= s1 returns true
s2 = s2 + s1: eqweDEF
length of s2: 7

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;
}
输出:
San Francisco,the United States
City:San Francisco,State:the United States
Beijing,China
City:Beijing,State:China

习题

(1)以下关于地址和指针的叙述中正确的是

  • 可以取变量的地址赋值给同类型的指针变量(对)
  • 可以取常量的地址赋值给同类型的指针变量
  • 可以取一个指针变量的地址赋给本指针变量,这样就使得指针变量指向自身
  • 所有指针变量如果未赋初值,则自动赋空值NULL

解析:A选项正确。常量存储在编译文件中,不能取地址。B选项错误。一个指针变量的地址只能赋给指向这种类型的指针变量,与其本身类型不同,不能赋值,C选项错误。未赋初值的指针变量自动赋任意地址值,D选项错误。

(2)要定义一个引用变量p使之引用类MyClass的一个对象,正确的定义语句是

  • MyClass p=MyClass;
  • MyClass p=new MyClass;
  • MyClass &p=new MyClass;
  • MyClass a, &p=a;(对),别名

(3)在C++的动态存储分配,下列说法正确的是?

  • new和delete是C++语言中专门用于动态内存分配和释放的函数
  • 动态分配的内存空间也可以被初始化(对)
  • 当系统内存不够时,会自动回收不再使用的内存单元,因此程序中不必用delete释放内存空间
  • 当动态分配内存失败时,系统会立刻崩溃,因此一定要慎用new

    分析:选项A,错,new 与delete是用于动态平衡分配与释放空间的运算符,不是函数;选项C,错,要使用delete释放空间,系统会统一管理,而不用delete释放的空间,会造成内存泄漏,这种程序用的次数多,会造成内存耗尽;选项D,错,不成功,会返回0。

    (4)C++11中,&表示左值引用;&&表示右值引用——下列关于左值和右值的说法正确的是
  • 表达式等号左边的是左值,等号右边的是右值
  • 左值是指表达式结束后依然存在的持久对象(对)
  • 右值是指表达式结束后依然存在的持久对象
  • 可以对右值取地址

    分析:左值和右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久对象右值指表达式结束时就不再存在的临时对象——显然右值不可以被取地址。

c++:-3的更多相关文章

  1. java web 开发三剑客 -------电子书

    Internet,人们通常称为因特网,是当今世界上覆盖面最大和应用最广泛的网络.根据英语构词法,Internet是Inter + net,Inter-作为前缀在英语中表示“在一起,交互”,由此可知In ...

  2. 所有selenium相关的库

    通过爬虫 获取 官方文档库 如果想获取 相应的库 修改对应配置即可 代码如下 from urllib.parse import urljoin import requests from lxml im ...

  3. In-Memory:内存数据库

    在逝去的2016后半年,由于项目需要支持数据的快速更新和多用户的高并发负载,我试水SQL Server 2016的In-Memory OLTP,创建内存数据库实现项目的负载需求,现在项目接近尾声,系统 ...

  4. 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代

    2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...

  5. 【.net 深呼吸】细说CodeDom(8):分支与循环

    有人会问,为啥 CodeDom 不会生成 switch 语句,为啥没生成 while 语句之类.要注意,CodeDom只关心代码逻辑,而不是语法,语法是给写代码的人用的.如果用.net的“反编译”工具 ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  7. App开发:模拟服务器数据接口 - MockApi

    为了方便app开发过程中,不受服务器接口的限制,便于客户端功能的快速测试,可以在客户端实现一个模拟服务器数据接口的MockApi模块.本篇文章就尝试为使用gradle的android项目设计实现Moc ...

  8. TODO:macOS编译PHP7.1

    TODO:macOS编译PHP7.1 本文主要介绍在macOS上编译PHP7.1,有兴趣的朋友可以去尝试一下. 1.下载PHP7.1源码,建议到PHP官网下载纯净到源码包php-7.1.0.tar.g ...

  9. In-Memory:在内存中创建临时表和表变量

    在Disk-Base数据库中,由于临时表和表变量的数据存储在tempdb中,如果系统频繁地创建和更新临时表和表变量,大量的IO操作集中在tempdb中,tempdb很可能成为系统性能的瓶颈.在SQL ...

  10. In-Memory:内存优化表的事务处理

    内存优化表(Memory-Optimized Table,简称MOT)使用乐观策略(optimistic approach)实现事务的并发控制,在读取MOT时,使用多行版本化(Multi-Row ve ...

随机推荐

  1. Vue Avoided redundant navigation to current location Error

    这个报错的根源就是vue-router组件,错误内容翻译一下是: Avoided redundant navigation to current location === 避免冗余导航到当前位置 这个 ...

  2. 什么是 OAuth?

    OAuth 代表开放授权协议.这允许通过在 HTTP 服务上启用客户端应用程序(例 如第三方提供商 Facebook,GitHub 等)来访问资源所有者的资源.因此,您可 以在不使用其凭据的情况下与另 ...

  3. WebSQL是什么?

    WebSQL是客户浏览器端的结构化的关系数据库.这是浏览器内部的本地RDBMS,你可以在这个本地RDBMS上执行SQL查询.

  4. 学习GlusterFS(六)

    一.GlusterFS概述 分布式文件系统由来 在介绍之前我们先来看下文件系统及典型的NFS文件系统. 计算机通过文件系统管理,存储数据的.而现在数据信息时代中人们可获取数据成指数倍的增长,单纯通过增 ...

  5. ACM - 动态规划 - P1282 多米诺骨牌

    多米诺骨牌由上下 \(2\) 个方块组成,每个方块中有 \(1 \sim 6\) 个点.现有排成行的上方块中点数之和记为 \(S_1\),下方块中点数之和记为 \(S_2\),它们的差为 \(\lef ...

  6. 4-Pandas数据预处理之数据转换(df.map()、df.replace())

    在数据分析中,根据需求,有时候需要将一些数据进行转换,而在Pandas中,实现数据转换的常用方法有: 利用函数或是映射 可以将自己定义的或者是其他包提供的函数用在Pandas对象上实现批量修改. ap ...

  7. C语言泛型编程——泛型冒泡排序

    在实际编程中,常常会需要一些方法(函数),比如排序,它们具体实现基本一致,仅仅只有参数类型不同, 那么可不可以有一种通用的函数,不管是什么类型的参数都可以通用呢? 泛型编程:泛型即是指具有在多种数据类 ...

  8. .NET Best Practices: Architecture & Design Patterns (5 Days Training)

    .NET Best Practices: Architecture & Design Patterns (5 Days Training) .NET最佳实践:架构及设计模式 5天培训课程 课程 ...

  9. 用JS写一个计算器(兼容手机端)

    先看成果:1.PC端2. 首先确立html,有哪些东西我们要知道.布局大概的样子在心里有个数 <!DOCTYPE html> <html> <head> <m ...

  10. 制作html5微信页面的经验总结。

    先罗列一下我遇到的问题: 用户可选择图片上传,但是图片比较大(基本都是2M以上),而且还得异步上传. 由于操作上比较多的设计都是隐性的例如滑动之类,需要手势动画作提示. 块内元素滚动时不流畅,或不能滚 ...