C++学习笔记——数组、指针与字符串

来源:互联网 发布:俄罗斯经济 知乎 编辑:程序博客网 时间:2024/06/05 16:35

数组、指针与字符串

1. 数组的定义与使用

数组:具有一定顺序关系的若干相同变量的集合体

数组的定义:

类型说明符 数组名[常量表达式][常量表达式]….;

int a[10];int a[5][3];

数组元素的使用:先定义后使用,逐个引用数组元素

b[1][2] = a[2][3];

用数组下标作为循环控制变量,可以依次处理大批量数据

数组的存储与初始化:

  • 数组元素在内存中依次存放,地址是梁旭的,物理地址的相邻对应着逻辑次序上的相邻

  • 数组的名字是数组首元素的地址,是一个常量

  • 一维数组初始化方法:

    • 列出全部元素的初始值
    static int a[10]={0,1,2,3,4,5,6,7,8,9}
    • 可以只给出一部分元素指定的初值

    • 列出去全部元素初值时可以不指定数组长度

  • 二维数组存储方法:按行存放,例如float a[3][4]a[0] 代表第一行的首地址

  • 二维数组的初始化:

    • 将所有初值写在一个{} 内,按顺序初始化
    • 分行列出二维数组元素的初值
    • 可以只对部分元素初始化
    • 列出全部初始值时,第一维下标可以省略
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}};
  • 如果不作任何初始值,局部作用域的非静态数组中会存在垃圾数字,static数组默认初始化为0;
  • 如果对部分元素初始化,则其他元素被初始化为0

2. 数组作为函数的参数

  • 数组元素作实参,与单个变量一样
  • 数组名作参数,形参实参都应是数组名,类型要一样,传送的数组地址,对形参数组的改变会直接改变实参数组

3. 对象数组

对象数组的定义与访问:

类名 数组名[元素个数]

通过下标来访问对象数组元素,访问对象的成员方法不变

对象数组的初始化:数组中每个元素对象被创建时,系统都会调用类构造函数,如果没有显示指定初始值,则按默认初始化

数组元素的构造和析构:

  • 构造数组时,未声明构造函数,则采用默认构造函数
  • 各元素对象的初值为相同值时声明带默认参数值的构造函数
  • 每一个元素被删除时都会调用一次析构函数
Point a[2];for (int i = 0, i < 2, i ++)  a[i].move(i+10,i+20);

4. 基于范围的for循环

功能:自动遍历容器内所有的元素

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

5. 指针的定义与运算

内存空间的访问方式:通过变量名访问、通过地址访问

指针:内存地址,用于间接访问内存单元

指针变量:用于存放指针的变量,也会占用一定的内存空间

static int i;static int* ptr = &i;*p = 3;

在定义指针变量时必须要指定所指向对象的类型,* 表示指针类型

赋值运算时中的 * 是一个寻址运算,实际是对指向的地址进行操作

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

两种与地址相关的运算互为逆运算

指针变量的初始化:

存储类型 数据类型 * 指针名 = 初始地址

初始地址不能是自己设定整数,必须是合法获得的地址

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

指针变量的赋值运算:

  • “地址”中存放的数据类型与指针类型必须相符
  • 合法的地址:地址运算符获得的变量或对象的起始地址,动态内存分配成功时返回的地址
  • 例外:整数0可以赋给指针表示空指针
  • 允许定义或声明指向void类型的指针,该指针可以被赋予任何类型对象的地址,但不能访问
  • C++11使用nullptr关键字说明空指针
void *pv;int i = 5;pv = &i;int *pint = static_cast<int*>(pv);cout << "*pint" << *pint << endl;

指向常量的指针:通过const修饰,不同通过指针改变所指对象的值,但指针本身可以改变,可以指向其他对象

int a;const int *p1 = &a;  // 指向常量的指针int b;p1 = &b;             // p1本身可以改变*p1 = 1;             // 编译出错

指针类型的常量:指针本身的值不能改变,一旦指向某个变量,其指向的地址就不能再改变

指针的运算:

  • 算术运算:
    • 指针p加上或减去n,其意义是指针指向当前位置的前方或后方的第n个数据的起始位置
    • 指针的++、–运算是指向前一个或下一个完整数据的起始
    • 进行运算的指针一般指向的时连续存储同类型数据的内存空间
  • 关系运算:
    • 指向相同类型数据的指针之间可以进行关系运算
    • 指针可以和0之间进行等于或不等于的关系运算

6. 指针与数组

指向数组元素的指针:

int a[10], *pa;pa = &a[0];pa = a;

此时a[i]、*(pa+i)、*(a+i)、pa[i] 均是等效的

指针数组:数组的元素是指针类型

int line1[] = {1,0,0};int line2[] = {0,1,0};int line3[] = {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] << ' ';  cont << endl;}

注意指针数据与二维数组的不同:指针数组各行的内存空间并不是连续的,每一行的长度可变。

image2

7. 指针与函数

以指针为参数的函数:

可以以指针作为函数的参数,使用的情况如下:

  • 需要数据双向传递(类似于引用)
  • 需要传递一组数据,只传首地址运行效率高

如果不希望主调函数内的值被修改,则可以使用指向常量的指针作为函数的参数(注意最小授权原则)

指针类型的函数:

若函数的返回类型是指针,该函数就是指针类型的函数

存储类型 数据类型 *函数名(){

​ // 函数体

}

  • 不要将非静态局部地址用作函数的返回值,因为离开函数后该地址就失效了
  • 返回的地址应该是在主调函数中仍然合法有效的地址
int *search(int* a,int num){  for (int i = 0; i<num; i++)    if(a[i] == 0)      return &a[i];}int array[10];int* zeroptr = search(array,10);
  • 子函数中通过动态内存分配new操作取得的内存地址返回给主函数是函数合法有效的,但注意不能忘记释放,避免内存泄漏
int main(){  int* newintvar();  int* intptr = newintvar();  *intptr = 5;  delete intptr;  /* 这里如果忘记释放,  会造成内存泄漏 */  return 0;}int* newintvar(){  int* p = new int();  return p;}

指向函数的指针:

函数指针的定义:指向程序代码存储区的指针

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

典型用途:实现函数回调

  • 将函数的指针作为参数传递给另一个函数,使得在处理相似事件时比较灵活
  • 调用者不用关心调用的是哪一个函数
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 a, b, res;res = compute(a,b,&max);res = compute(a,b,&min);res = compute(a,b,&sum);
  • 函数回调时可以直接传函数名,也可以加地址运算符

8. 对象指针

指向对象的指针称为对象指针

定义方法:

类名 * 对象指针名

访问方法:

对象指针名 -> 成员名

ptr -> getX();(*ptr).getX();  // 二者等价

this 指针:指向当前对象自身的指针,隐含于类的每一个非静态成员函数中,指出成员函数所操作的对象

/* 下面的例子利用对象指针解决前向引用声明无法调用未声明类细节的问题 */class Fred;class Barney{  Fred *x;   // 在构造函数中动态分配内存};class Fred{  Barney *y;}

9. 动态内存分配

动态申请内存操作符 new

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

功能:在程序执行期间,申请用于存放T类型对象的内存空间

结果:成功时返回T类型指针,指向新分配内存首地址,失败抛出异常

释放内存操作符 delete

delete 指针P

Point *ptr1 = new Point;delete ptr1;// 注意此处指针并未被删除,只是释放内存 

分配和释放动态数组:

分配:new 类型名T [数组长度]

释放:delete[] 数组名p

  • 数组长度可以是任何整数类型的表达式
  • 释放数组时必须加[]才能全部释放,否则只释放首元素地址

动态创建多维数组:

new 类型名T[第一维长度][第二维长度]…

  • 注意此时必须用指向数组的指针来接收返回值
  • 得到首地址后可以利用数组方式访问元素
  • 习惯上for循环中内层循环遍历右侧下标

可以将动态数组封装成类,注意在析构函数中释放内存

10. 智能指针

  • unique_ptr:不允许多指针共享资源,可以用move函数转移指针,转移后原指针失效
  • shared_ptr:多个指针共享资源
  • weak_ptr:可复制shared_ptr,但其构造或释放对资源不缠身影响

11. Vector对象

  • 封装任何类型的动态数组,自动创建和删除
  • 数组下标越界检查

定义的方法:

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

  • 数组元素引用与普通数组形式相同
  • size函数可以获得数组长度,调用形式为“vector对象名.size()”
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)  std::cout << *e <<std::endl;

12. 对象的复制和移动

  • 浅层复制:实现对象成员的对应复制
  • 深层复制:类中有指针指向的动态分配的内存时,仅仅复制指针是不行的

浅层复制时两个对象会出现“联动”的现象(并没有真的复制)

image3

深层复制方法:重新分配相同大小内存空间,再把元素一一复制到新的内存空间中

image4

移动构造:将源对象的控制权全部交给新对象,一般用于有可利用的临时对象的情况,该对象即将消亡但其内容还有用,有两种不同的解决方案:

  • 深层复制构造函数
// 深层复制构造函数IntNum(const IntNum &n):xptr(new int(*n.xptr)){}// 析构函数~IntNum(){  delete xptr;}/* 类外的一个函数,返回值为IntNum类对象,在返回前需要创建临时无名对象,然后再让a消亡,这期间调用深层复制构造函数 */IntNum getNum(){  IntNum a;  return a;}
  • 移动构造函数(C++11)
/* 移动构造函数,&&为右值引用,即将消亡的值,例如函数返回的临时变量就是右值,这种方法效率高 */IntNum(IntNum && n):xptr(n.xptr){  n.xptr = nullptr;}

13. 字符串

①C风格字符串:

字符串常量:各字符连续顺序存放,每个字符占一个字节,以‘\0’ 结尾,相当于一个隐含创建的字符常量数组,字符串相当于一个char数组首地址,可以赋给一个char常量指针

用字符数组存储字符串,注意’\0’

C风格字符串有很多缺点,C++不建议使用

②String类

C++标准库中定义的一个类,String类常用构造函数如下:

  • string():默认构造函数,建立长度为0 的串
  • string(const char *s) 用指针s指向字符串常量初始化string对象
  • string(const string& rhs) 复制构造函数
string s1;string s2 = "abc";string s3 = s2;

image5

  • 输入整行字符串:getline(cin,s) 默认以换行符作为结束标识,还可以用第三个参数指定,如getline(cin,s,',')
string city, state;getline(cin,city,',');getline(cin,state);cout << "City:" << city << "State:" << state << endl;
原创粉丝点击