4.1 数组
数组是同一类数据的集合。数组的特点是 以顺序结构结构存储,一点定义就无法更改数组大小。
数组定义很简单:
int a[2] ; // 定义了一个能容纳两个int类型数据的数组
const int sz = 2 ;
myclass ls[sz] ;
定义数组的时候系统可能会自动初始化数组的每个项目,但也可以显示提供值。
int a[3] {1,2,3} ;
可以既指定数组大小又提供初始化列表,此时列表内元素数不能大于维数; 如果列表提供元素小于维数则从数组第一个元素开始显示初始化,没有提供的数组元素会自动初始化步。
自动初始化遵从下面规则:
Ø对内置类型来说:
int a[2] ; // 表示要自动初始化。如果a是全局定义的则每一项都初始化为0,否则会分配随机数,表示未初始化
也可以显示给值例如:
int a[] {1,2,3} ; // 显示初始化时候可以不必提供数组大小,系统会自动推断大小
Ø对于类类型来说:
myclass ls[2] ; // 无论在是全局定义还非全部定义都会调用类型的默认构造函数,如果类没有默认构造函数则编译出错
Øchar类型数组比较特殊:
char a[]{'a', 'b', 'c'} ; // 标准定义
char b[]{'a', 'b', 'c', '\0'} ; // 一个C风格的字符串,等价于 char b[] = "abc" 二者的字符数组大小都是4
上面两种定义方式唯一不同的就是最后一个字符是否是 '\0' (结束符)如果是则表明定义了一个C风格的字符串。
要注意:虽然char b[] = "abc" 内容只有三个字符,由于这种定义是C风格字符串的定义法所以系统会自动在对应的字符数组后面加上 '\0'
在指定大小定义C风格字符串时要注意前后长度匹配 char b[3] = "abc" 会产生错误,需要存放四个字符大小。
和其他类型定义不同,数组定义是没有类似 int a[](b) 这种定义方式的,因为数组不支持复制操作。
数组最常用的操作办法是小标操作 a[0] 表示第一个项,它是左值操作。
表示数组下标和数组大小的数据类型和bitset一样是size_t 但是数组只有下标操作,没有取大小等其他操作。
int ls[sz];
for(size_t i = 0; i < sz; if++)
{
cout << ls[i] << endl;
ls[i] = i + 1;
}
4.2 指针的引进
指针一般是对对象的存放地址的直接指向,一般定义形式为:
int *cur ; // 定义了一个未初始针未初始化指针值是个随机数
int *cur = 0 ; // 可以给指针赋予初始0值表示该指针没有指向任何对象,不应对其有任何操作 。这个赋值表达式中0值是有约束的,必须是字面常量或者编译时常量值0
int *cur = 121 ; // 也可以给指针赋予一个整数常量,但最好不要这么做。因为该常量表示内存地址而我们并不十分清楚这个地址中存放了什么数据。一单指针做了某些操作就可能破坏这些数据,对一个未知的内存做指针指向是危险的(未初始化指针情况也类似)
应该在定义是就给予其初始化工作:
int a = 123 ;
int *cur = &a ;
有一类比较灵活的指针 *void 可以指向任何类型的变量;
int a = 123 ;
string b = "abc" ;
void p1 = &a ;
void p2 = &b ;
这类指针表示指向某个内存地址的数据,但不清楚改数据类型。void指针操作有限。由于不知道指向的数据类型所以不能对指向数据做任何操作。
有一类指针可以指向另外的指针,也就是指向指针的指针定义如下:
int a = 123 ;
int *cur = &a ;
int **curr = &cur ; 这类指针需要做两次解引(**curr)才能获取真正的对象值。
指针操作一般有两个意思:
Ø对指针指向的数据做操作(需要解引):
int a = 123 ;
int *cur = &a ;
*cur = 456 // 等价于 a = 456; (对所指向的数据做操作需要做解引在做操作(就是在变量引用时保持*号))
Ø对指针本身操作是指改变指针的指向对象:
int a = 123 ;
int *cur = &a ;
int b = 123 ;
int *curb = &b ;
a = b ; // 等价于 a = &b (对指针本身操作不能带解引符号*) a, b两个指针现在都指向了变量 b ,变量 a 现在没有任何指针指向。
上面讲了数组的基本概念,数组和指针有着非常紧密的联系。 由于数组本身只有下标操作太过简单,所以大部分情况下都使用指针来操作数组。
指针不能直接指向数组变量,它是通过指向数组中得某个项并前后移动来操作数组;
int a[count] = {1,2,3,...} ;
int *cur = a ; // 该操作是简化操作实则是指针指向了数组的第一项,等同于 int *cur = &a[0]
也可以明确指定 int *cur = &a[2] ;
指定了数组的指针可以前后移动,导航到当前项的前后项指针:
int *next = cur +1 ;
int *prv = cur -1 ;
int *next3 = cur +3 ; // 当前项向后移动三位的项指针
数组在数据范围前后一位溢出位的指针表示叫哨兵位,一旦指针到了任意一个哨兵位就不能再做一般化处理。
int a[10] = {1,2,3,...} ;
int *cur = &a[0] ;
int *cur1 = cur -1 ; // cur1是哨兵位,表示指针已经移出数组
int *cur2 = cur +10 ; // cur1是哨兵位,表示指针已经移出数组
哨兵位的作用是标识指针已经超出了数组有效范围
两个数组指针可以相减,值是类型为 ptrdiff_t 的项间距(两个操作数中不能有哨兵位)如下:
int a[] = {1,2,3,...} ;
int *cur1 = &a[1] ;
int *cur2 = &a[5] ;
ptrdiff_t pd = cur2 - cur1 ; (也可以交换两个操作数的位置,结果允许为负数)
指针可以在数组有效范围内任意移动,找到相应项后如何操作数组的真实值呢? 只需要解引既可。
int a[] = {1,2,3,...} ;
int *cur = &a[1] ;
*cur = 1 ; // 等同于 a[1] = 1
数组最重要的是下标操作。数组指针也有下标操作,但是意义和数组下标不一样
int a[] = {1,2,3,...} ;
int *cur = &a[1] ;
cur[1] = 2 ; // 等价于 *(cur + 2) = 2 也等价于 a[3] = 2
int i = cur[-2] ; // 等价于 int i = *(cur - 2) 也等价于 int i = a[1]
指针是数组的迭代器,最后让让我们看看用指针如何实现迭代
int arr[count] = {1,2,3...};
for(int *cot = arr; *end = arr + count; cot != end; ++ cot)
{
cout << *cot << endl;
}
可以用const修饰定义指针。
Ø指向常量的指针:
const int a = 123 ;
const int *cur =&a ; // const 必须
需要注意:常量的指针必须是指向常量指针,但指向常量指针可以指向常量也可以指向变量。无论如何都不能对它所指内容做更改,即使它实际指向了变量,但可以更改这个指针的指向
int c =456 ;
cur = &c ; // 更改了指针的指向,现在指向的实际是个变量
*cur = 789 ; // 不允许,虽然指向的是变量但系统认为是常量所以不允许修改
Ø常量指针:
表示不能重新再做指向更改,但也许可以修改它指向的对象的值,这取决于它指向的值是变量还是常量 如下:
const int a = 123 ;
int b = 456 ;
int *const cur1 = &a ; // 此时不允许允许 *cur = 789,因为指向了一个常量
int *const cur2 = &b ; // 此时允许允许 *cur = 789,因为指向了一个变量
cur1 = &b ; // 错误,不允许更改指针的指向
需要注意的是这类指针在定义时必须初始化。
如果我定义了这样一个指针
const int *const cur = &a;
那么既不能更改指向的数据值,也不能重新指向其他数据
4.3 c风格字符串
前面内容了解了char 数组可以表示c风格字符串,既然数组可以用指针表示那么char*也可以表示c风格串
char a[]{'a','b','c','\0'} ; // 第一种数组定义语法
char a[] = “abc” ; // 第2种数组定义语法
char *a = “abc” ; // 指针表示法
c风格的字符串有多个操作函数: strlen(), strcpy(),strcat()以及strcmp()
函数中所说的结束标示NULL实际上就是最后一个字符 '\0' 。
char s2[20] = {'A','N','S','I','\0','C','+','+'};
char s3[6] = {'I','S','O','C','+','+'};
cout<<strlen(s1)<<endl; // 5
cout<<strlen(s2)<<endl; // 4
cout<<strlen(s3)<<endl; // 不确定
strlen(s1)==5,原因是{'m','o','b','i','l'};指定一部分内容的时候,剩余部分会自动赋值为空字符,而'\0'是转义字符表示的就是空字符.
strlen(s2)==4,因为第五个字符是字符串结束符'\0'(==0)。
strlen(s3)==?,因为他没有结束符。
可以创建动态数组,动态数组创建时更具类型不同其初始化值也不同。
int *a = new int[10] ; // 包含10个未初始化元素的数组
int *b = new int[10]() ; // 包含10个初始化为0的元素数组
// 类类型
string *c = new string[10] ; // 包含10个调用了类默认构造函数完成初始化的元素数组
可以看到内置类型需要在后面加上()才可以让元素初始化。
动态数组定义之后一定要手动删除掉,否则会造成内存泄露
delete [] a ; // 方括号必不可少,表示要释放一个动态数组