C++|C++学习笔记|三、数组、字符串、指针

三. 数组、字符串、指针

参考书籍《Visual C++ 2012入门经典》(Ivor Horton’s Beginning Visual C++ 2012)

1. 数组

  数组就是一组名为数组元素(或简称元素)的内存位置。各个内存位置存储相同数据类型的数据项。

1.1. 数组声明

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

  数组可以声明称任意类型。
  数组的所有元素都存储在连续的内存区域内。
  _countof()可以获取数组元素个数。

1.2. 数组元素引用

  数组元素的引用:

数组名[索引值]

  数组的索引值可以是任何结果为整数的表达式(0~元素个数N-1)。
  当使用非法索引值时,编译或运行不会产生任何警告。

1.3. 数组初始化

类型名 数组名[元素个数N] = {常量1,常量2,......,常量M};  N>=M     

  如果没有初始化列表,数组元素将都是无效值。
  - N > M:第0个元素~第M-1个元素赋给定值;自第M个元素起,后续N-M个元素均被初始化为0;
  - N = M:此时可以直接省略元素个数N不写。

1.5. 字符数组和字符串处理

char    Array_1 =   "Hello World!";

  字符串是附加有特殊字符(字符串结尾标志,即’\0’空字符,占用一个字节,8位全为0)的字符序列。
  终止符(’\0’)是编译器自动添加的,如果在该字符串字面值中显式添加’\0’,则最终将得到两个空字符。
  在指定字符数组的元素个数时,必须考虑到终止字符的存在。

1.6. 多维数组

  初始化:

int Array_2[M][N]   =   {
                            {}
                            {}
                            {}
                            ...
                            {}
                        }   

  多为数组可以省略第一唯的大小,即M;

2. 间接数据访问

2.1. 指针的概念

  用来存储数据值的各个内存位置都有地址。地址给PC硬件提供了引用具体数据项的途径。
  指针变量存储特定类型另一个变量地址

2.2. 声明指针

类型名*    变量名;

  即,指向“类型名”类型的指针。

注:指针的长度
在32位系统中,地址长度为 4 Bytes,即指针长度为 4 Bytes;
在64位系统中,地址长度为 8 Bytes,即指针长度为 8 Bytes;

  取址运算符 &
  一元运算符,用于获得变量的地址。也可以称为引用运算符。
  可以使用&运算符获得任何变量的地址,但需要有适当类型的指针来存储地址。

2.3. 使用指针

2.3.1. 间接寻址运算符 *

  将间接寻址运算符*与指针联用,可以访问该指针指向的变量的内容。
  该运算符也称为解除引用运算符(引用即寻址符&);而访问指针所指的变量中数据过程称为解除引用指针。

2.3.2. 需要使用指针的原因  
  • 可以处理数组中的数据,执行速度更快
  • 动态地(即程序执行过程中)为变量分配存储空间
2.3.3. 初始化指针
int     ivar    =   0;
int*    ivar_p  =   &ivar;  //int*  ivar_p  =   nullptr;

  指针字面值nullptr不指向任何对象。(在C中,使用0和NULL)
  nullptr可以隐式(自动)转换为任何指针类型,但不能隐式转换为除bool类型以外的任何整形。
  nullptr为false,其他任何指针值都转换为true。

2.3.4. 指向char类型的指针
char*   cvar_p  =   "Hello World!";

  "Hello World!"实际上是一个const char类型的数组({'H','e','l','l','o',' ','W','o','r','l','d','\0'}),并将该数组的首地址存储在cvar_p指针中。

注意:
对于
cout << ivar_p << endl;
cout << cvar_p << endl;
前者输出的仅仅是一个地址,而后则输出的是一串字符。
这是因为输出操作看待“指向char”类型的指针类型的方式,即将其视为字符串

2.3.5. sizeof

  sizeof生成size_t(通常为unsigned int)类型的整数值,给出其操作数占用的字节量。

sizeof 变量名;

  当sizeof作用于一个数组名称时,返回整个数组的字节量。

sizeof iarr_1;

  sizeof也可以作用与类型名称而不是变量。但在这种情况下,类型名称用括号括起来

sizeof(int);

  区分3种与const、指针及指针指向的对象有关的情况:
  
- 指向常量对象的指针 const int* ptr 不能修改指向对象
- 指向某个对象的常量指针 int* const ptr 不能修改指针
- 指向常量对象的常量指针 const int* const ptr 均不能修改

注:
const char* ptr_1;
const char* const ptr_2;
前者声明的是指针ptr_1指向的对象是const型;而不是指针。
后者声明的才是const型指针。

2.3.6. 指针与数组

  如果单独使用一维数组的名称,则该名称自动转换为指向数组第一个元素的指针。

int*    iarr_p  =   nullptr;
int     iarr_1[5];
iarr_p  =   iarr_1;     //iarr_p        =   &iarr_1[0];
2.3.6.1. 指针处理多维数组

  对于多维数组:
  int iarr[M][N];
  声明一个指针:
  int(* iarr_p)[N] = iarr;
  是一个指向具有N个数组元素的一维数组的数组指针。
  *(*(iarr_p+i)+j) 等价于 iarr[i][j]

3. 动态内存分配

  在程序执行期间,经常需要根据输入的数据,来决定应该给存储不同类型的变量分配空间量。

3.1. 堆——空闲存储器

  大多数情况下,执行程序时,计算机有部分未使用的内存。这些内存在C++中称为堆(空闲存储区)

3.2. new & delete操作符

  new操作符:

double* dvar_p  =   nullptr;    //首先定义一个指针
dvar_p  =   new double;         //new操作符返回堆中分配给后接类型(此处为double)的地址
*dvar_p =   1.0;                //在该地址存储一个double变量

  如果堆中内存已用尽或者没有足够的连续字节提供给需要获得空间的变量(如double型为8个字节,如果堆中有空间但最大连续字节为7),则返回一个异常,终止程序。
  new创建的变量也可以初始化,但是不能用“=”,需要用“()”形式:

dvar_p  =   new double(1.0);    //分配内存与初始化同时进行

  更进一步,可以将上述三条语句合并在一起:

double* dvar_p(new  double(1.0));

  delete操作符:
  当不再需要某个变量时,利用delete释放内存:

delete  dvar_p;

  在释放指针指向的内存时,还应该把指针的值设置为nullptr

内存泄漏:
如果不是用delete,随后又在dvar_p中存入一个不同的地址值(是改变dvar_p的值,而不是改变*davr_p的值),那么将无法再释放这块内存,或使用其包含的变量,因为失去了访问该地址的途径。

3.3. 为数组动态分配内存

char*   carr_p  =   nullptr;    
carr_p  =   new char[n];        //n为数组元素个数,自定,首地址赋给指针
    ...
delete  []  carr_p;             //“[]”表明所需删除的是一个数组
carr_p  =   nullptr;

  不能在动态分配内存过程中给数组初始化

3.4. 多维数组动态分配内存

char    (*carr_p)[m]    =   nullptr;    
carr_p  =   new char[n][m];     //首地址赋给指针,这里m,n都是常量
    ...
delete  []  carr_p;             //“[]”表明所需删除的是一个数组,无论维数多少
carr_p  =   nullptr;

  在new分配中,可以使用变量来指定数组最左边那一维

4. 使用引用

4.1. 引用的概念

  引用分两种类型:
- lvalue
- rvalue
  实质上,引用是可用作其他对象的别名的一个名称。
  lvalue引用的是一个可出现在赋值操作左边的持久存储位置;
  lvalue引用是另一个变量的别名;是别名而非指针,声明引用时必须指出对应的变量;不能修改引用,使其表示另一个变量。
  rvalue实质上是一个暂存的临时值。

4.2. 声明并初始化lvalue引用

int a   =   0;
int&    ref_a   =   a;      //声明一个lvalue引用,该引用初始值为a变量名;
                            //之后可以使用ref_a代替原来的变量名,共享内存
                            //变量ref_a   属于“指向int的引用”;与指针的初始化类似

  对于常量:

const   int&    ref_Data    =   5;      //必须添加const限定

  指针与引用之间最大区别:指针需要解除引用才能访问其指向的变量,而引用不需要(引用不能改为引用别的变量)。
  lvalue引用完全等价于被引用的变量

4.4. rvalue引用

  此处略微介绍,后补全。
  C++中每个表达式要么是lvalue,要么是rvalue
  变量是lvalue,一个内存位置。
  rvalue引用是对包含表达式结果的内存位置的引用。

int x   =   5;
int&&   ref_exp =   2*x+3;              //rvalue引用,引用2x+3求值结果,是一个临时值
cout    <<  ref_exp <<  endl;
int&    ref_x   =   x;                  //lvalue引用
cout    <<  ref_x   <<  endl;

4.5. 字符串库函数 string.h

4.5.1. 以空字符结尾的字符串长度

  strlen()char*类型的参数字符串的长度(不包含结尾的空字符)作为一个size_t类型的值。
  wcslen()则针对wchar_t*类型的字符串。
  strlen()wcslen()都是以字符串结尾的空字符来确定长度。如果没有空字符,函数会继续检查整个内存。

4.5.2. 以连续空字符结尾的字符串

  strcat(str1,str2)函数用来拼接两个以空字符结尾的字符串,第二个字符串首字符覆盖第一个字符串的空字符。第一个字符数组必须有足够的空间容纳两个数组。strcat(str1,str2)返回首字符地址。
  wcscat(str1,str2)用于宽字符。
  strncat(str1,str2,n)功能同strcat(str1,str2),但第三个参数指定第二个字符串要拼接的字符数量。并在结尾自动加上一个空字符。
  wcsncat(str1,str2,n)用于宽字符。
  
  由于上述介绍函数都是以寻找空字符来实现的,这是不安全的
  在cstring.h中,strcat_s(str1,m,str2)、wcscat_s(str1,m,str2)、strncat_s(str1,m,str2)、wcsncat_s(str1,m,str2)提供了安全的方法。mstr1数组大小。
  如果在拼接过程中出现错误(源/目标字符串不存在、目标字符数组容量不足),将产生错误,而且目标字符数组不会发生改变。

4.5.3. 复制以空字符结尾的字符串  

  strcpy(str1,str2)将str2复制到str1中覆盖。
  strcpy_s(str1,m,str2)

4.5.4. 比较以空字符结尾的字符串

  strcmp(str1,str2)第一对不同的字符确定了第一个字符串是小于/大于第二个字符串。
  小于,则返回负值
  等于,则返回零
  大于,则返回正值

4.5.5. 搜索以空字符结尾的字符串

  strspn(str,set)在搜索str中不包含在set中的第一个字符,并返回索引(不是地址)。

char*   str =   "I am idot";
char*   vowel   =   "aeiouAEIOU ";
unsigned    char    index   =   strspn(str,vowel);

  其中'I'、' '、'a'均可在vowel中搜索到,而'm'不再vowel中,所以函数返回3
  strstr(str1,str2)返回一个指针,指向str1str2指定的子字符串的位置。

char*   str1 = "Jones had had \" had had\" had had \"had\"."
char*   str2 = "had";
char*   ptr  = nullptr;
ptr = strstr(str1,str2);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值