内容包括
- 创建和使用数组
- 创建使用C-风格字符串
- 创建和使用string类型字符串
- 使用方法getline() 和get()读取字符串
- 混合输入字符串和数字
- 创建和使用结构
- 创建和使用共用体
- 创建和使用枚举
- 创建和使用指针
- 使用new和delete管理动态内存
- 创建动态数组
- 创建动态结构
- 自动存储 静态存储 和动态存储
- vector 和 array类
4.1 数组
声明数组的通用格式:
typeName arrayName[arraySize]
例如:
int nums[12]
初始化规则:
只有定义时才能进行初始化,也不能将一个数组赋值给另一个数组
int cards[3] = {1,2,3} // ok
int hands[3] // ok
hands[3] = {4,5,6} // not allowed
hands = cards // not allowed
初始化时,提供的数值可以少于数组长度,其他位置用0值填充。
初始化方法:
- 初始化时可省略等号: int cards[3]{1,2,3};
- 可不在大括号内包含任何东西,这将把所有元素都设置为零值;
- 禁止初始化缩窄转换
long plifs[3] = {12,34,3.0} // no allowed covert float to long
char slifs[4] = {12,44444,'i'} // not allowed 44444 out of 8 byte
char tlifs[4] = {12,'a','b','c'} // allowed
4.2 字符串
C- 风格字符串具有一种特殊性质:以空字符为结尾,空字符写作’\0’其ASCII码为0,用来标记字符串的结尾。
char dog[3] = {'d','o','g'};
char cat[4] = {'c','a','t','\0'};
char fish[5] = "fish";
拼接字符串常量:
C++ 允许拼接字符窜面值,即将两个用引号括起来的字符串合并在一起。任何两个由空白(空格 制表符和换行符)分隔的字符串常量都将自动拼接成一个,下面的所有输出语句都是等效的。
cout << "i have a dog " "and a cat\n";
cout << "i have a dog and a cat\n";
cout << "i have a dog "
"and a cat\n";
在数组中使用字符串:
要将字符串存储到数组中,常用两种方法:
- 将数组初始化为字符串常量
- 将键盘或文件出入读取到数组中
#include <iostream>
#include <cstring>
#define Size 30
using namespace std;
int main(int argnum,char *args[]) {
char name1[Size];
char out[Size] = "hello,your name?";
cout << out << endl;
cin >> name1;
cout << "well, your name has " << strlen(name1) << " letters, and you have " << sizeof(name1) << " place to save it";
return 0;
}
// 运行
hello,your name?
luslin
well, your name has 6 letters, and you have 30 place to save it
字符串输入:
上面的代码有缺陷,只是由于输入的原因被掩盖掉了,下面的例子:
#include <iostream>
#include <cstring>
#define Size 6
using namespace std;
int main(int argnum,char *args[]) {
char name1[Size];
char name2[Size];
cout << "name1 ?" << endl;
cin >> name1;
cout << "name2 ?" << endl;
cin >> name2;
cout << "well, name1 is " << name1 << " , name2 is " << name2 << endl;
return 0;
}
// 运行
name1 ?
lu lin
name2 ?
well, name1 is lu , name2 is lin
原因:cin 使用空白符(空格,制表符,换行符)来确定字符串的结束位置这意味着cin在取得’‘lu’'后将其放到name1中,再次获取时,发现了输入队列中的lin然后赋值给name2.
另一个问题是,输入字符可能比目标数组长,这将不能组织将一个长度30的字符串放到长度为10的数组中
getline() 每次读取一行字符串输入:
getline()函数每次读取一行。它通过换行符来确定行尾,但不保存换行符,在储存字符串时使用空白符来替换换行符
#include <iostream>
#include <cstring>
#define Size 6
using namespace std;
int main(int argnum,char *args[]) {
char name1[Size];
char name2[Size];
cin.getline(name1,6);
cin.getline(name2,6);
cout << "well, name1 is " << name1 << " , name2 is " << name2 << endl;
return 0;
}
// 运行
sdf h
sdf
well, name1 is sdf h , name2 is sdf
问题:
当输入字符串长度大于指定长度时,getline() 会设置失效位,并关闭掉后面的输入:
dsfasdf sadf
well, name1 is dsfas , name2 is
get()面向行的输入:
该函数有几种变体,其中一种变体的工作方式与getline()类似,它们接受的参数相同,解释参数的方式也相同,并且都读取到行尾。但get()并不丢弃换行符,而是将其保留在输入队列中。假如我们两次调用get():
cin.get(name,6);
cin.get(name2,6); // problem
由于第一次没有将换行符取出,因此,第二次调用后看到的是换行符。如果不借助帮助,get()不能跨过换行符
正确的方法
#include <iostream>
#include <cstring>
#define Size 6
using namespace std;
int main(int argnum,char *args[]) {
char name1[Size];
char name2[Size];
cin.get(name1,Size);
while (cin.get() != '\n');
cin.get(name2,Size);
while (cin.get() != '\n');
cout << "well, name1 is " << name1 << " , name2 is " << name2 << endl;
return 0;
}
4.3 String 类简介:
要使用string类,必须在程序中包含头文件string。string类位于名称空间std中。
- 可以使用C-风格字符串来初始化string对象
- 可以使用cin输入到string,使用cout读取string
- 可以使用数组表示法访问存储在string中的字符
int main(int argnum,char *args[]) {
string name1;
cin >> name1;
cout << "well, name1 is " << name1 << " , title is " << name1[0] << endl;
return 0;
}
4.3.1 c++11 字符串初始化:
string n2 = {"asdf"};
string n3 = "asdfasdf";
4.3.2 赋值、拼接和附加:
-
不能将一个数组赋值给另一个数组,但可以将一个string对象赋值给另一个string对象。
-
简化了合并操作,可以使用运算符+将两个string对象合并起来,还可使用 += 将字符串附加到string对象末尾。
-
使用 string.size()获取字符串长度
-
可以使用cin输入到string,使用cout读取string, 可以使用getline(cin,str)获取一行字符串
int main(int argnum,char *args[]) {
string n1;
string n2;
getline(cin,n1);
getline(cin,n2);
cout << "well, n1 is " << n1 << " , n2 is " << n2 << endl;
return 0;
}
4.4 结构简介:
定义结构:
struct person{
string name;
char age;
bool sex;
};
int main(int argnum,char *args[]) {
person pi {"lin",12, false};
cout << "well, n1 is " << pi.sex << " , n2 is " << pi.age+1000 << endl;
return 0;
}
属性:
-
支持将列表初始化用于结构
-
c++ 使用用户定义的类型与内置类型相似。例如,可以将结构作为参数传递给函数,也可以让函数返回一个结构。还可以使用赋值运算符将结构赋值给另一个同类型的结构,这样结构中每个成员都将被设置成另一个结构中相应成员的值,即使成员是数组。这种赋值被成为成员赋值
-
可以同时完成定义结构和创建结构变量的工作;
struct person{ string name; char age; bool sex; }p1,p2;
-
可以创建元素为结构的数组,用法与数组相同。
-
允许指定占用特定位数的结构成员,这使用创建与某个硬件设备上的寄存器对应的数据结构非常方便
struct person{
string name;
unsigned int age :8; // 8 bits for age
int :4; // 4 bits unused
int sex ;
}p1,p2;
int main(int argnum,char *args[]) {
p1 = {"12",255, 1000};
cout << "well, n1 is " << p1.sex << " , n2 is " << p1.age << endl;
return 0;
}
4.5 共用体:
共用体(union)是一种数据格式,它能够储存不同的数据类型,但只能同时存储其中一种类型,用途之一是当数据使用两种或更多格式时,可节省空间
struct person{
string name;
unsigned int age :8;
int type ;
union id {
long id_num;
char id_char[20];
}id_val;
int sex ;
}p1,p2;
int main(int argnum,char *args[]) {
p1 = {"12",255, 1,22,0};
cout << "well, n1 is " << p1.id_val.id_num << " , n2 is " << p1.age << endl;
return 0;
}
匿名共用体没有名称,其成员将成为位于相同地址的变量。显然,每次只有一个成员是当前成员
struct person{
string name;
unsigned int age :8;
int type ;
union {
long id_num;
char id_char[20];
};
int sex ;
}p1;
cout << "well, n1 is " << p1.id_num
4.6 枚举:
C++的enum工具提供了另一种创建符号常量的方式,这种方式可以代替const。他还允许定义新类型。
enum color {red,blue,yellow,white,black,green}
- color为新的类型名称,称为枚举,red、blue等作为符号常量,对应整数值0~7.这些常量称为枚举常量
- 在不进行强制类型转换的情况下,只能将定义枚举时使用的枚举常量赋值给这种枚举的变量。
4.7 指针和只有存储空间:
-
声明和初始化指针:
int *p1; int main(int argnum,char *args[]) { int i = 12; int * j; j = &i; cout << j << *j << endl; return 0; }
-
危险:
在创建指针时,计算机将分配用来存储地址的空间,但没有分配用来存储指针所指向数据的内存,这种错误隐蔽,不易发现。
int * j; *j = 12;
-
指针和数字:
指针不是整型,虽然计算机通常把地址当做整数来处理。但从概念来说,指针与整数是截然不同的类型。整数是可以执行加减乘除的数字,而指针描述的是位置,将两个地址相乘没有意义。要将数字当做指针使用,需要通过强制转换
int *j; j = (int *) 0xB8000000;
-
使用new来分配内存:
int *j = new int; *j = 123;
-
使用delete 释放内存:
它使得在使用完内存后,能够将其归还给内存池,这是通向最有效使用内存的关键一步。归还或释放的内存可供程序的其他部分使用。使用delete时,后面要加上指向内存块的指针
int *j = new int; .... delete j;
只能用delete来释放使用new分配的内存。然而对空指针使用delete是安全的;一般来说,不要创建两个指向同一内存块的指针,因为这将增加错误的删除同一个内存块两次的可能性
-
使用new创建动态数组:
int *j = new int; *j = 123; int *i = new int[*j]; delete []i; delete j;
总之,使用new和delete时要遵循一下原则:
- 不要使用delete来释放不是用new创建的内存
- 不要使用delete释放同一内存块两次
- 如果使用new[] 创建,要使用delete[]释放
- 对空指针使用delete是安全的
-
使用动态数组
创建的指针指向的是动态数组的第一个元素
int * j = new int[10]
这里 *j 指的就是第一个数据, *(j + 1) 或 j[2] 指第二个数据
-
指针与数组等价的基本原因在于指针算术和c++内部处理数组的方式。当指针变量增加1时,增量等于指针指向的数据类型的字节数,多数情况下,C++将数组名解释为数组第一个元素的地址
-
创建动态结构时,使用- > 访问成员变量;
struct person { string name; int age; }; int main(int argnum,char *args[]) { person * p1 = new person; p1->age = 12; }
4.8 数组的替代品:
-
模板类 vector:
模板类vector类似于string类,也是一种动态数组。可以在运行期间设置vector对象的长度,可以在末尾附加新数据,可以在中间插入新数据。
要使用vector类需要包含头文件vector,命名空间std
vector <int> ps;
-
vector 的功能强大,但付出的代价是效率低下,如果使用的是固定长度的数组,可以使用array类,它效率与数组相同,但是更加安全、方便,需要头文件array,命名空间std
array <int,12> as = {1,2,3,4,4};
cout << as.at(7);
- 无论是vector、array 还是数组,都可以使用标准数组表示法来访问各个元素。从地址区分,array和数组对象存储在相同的内存区域(栈)中,而vector对象存储在堆或自由存储区中。最后array对象可以赋值给另一个对象,但是对于数组而言,必须逐元素复制