一 数组
数组说明
存储多个同类型的值。声明应该指出以下3点:
- 存储在每个元素中的值的类型
- 数组名
- 数组中的元素数
初始化:int a[3]={0,1,2};若没有初始化函数中定义的数组,则其元素值 将是不确定的,意味着元素的值为以前驻留在改内存单元中的值。
sizeof(a)得到的是数组中的字节数。 12
sizeof(a[0])得到的是一个元素的字节数
初始化规则
只有定义数组时才能初始化,之后就不能使用了,也不能将一个数组赋给另一个数组,然而可以通过下标分别给数组元素赋值。 - int a[5] = {0,2};//前两个为0 2 ,其余为0
- int a[5]={1};//前一个为1.其余为0
- 将数组中的所有元素设置为0 ,int a[5] = {0};
- 如果初始化数组时,括号里数组个数为0.编译器将计算数组个数。 int a[] = {0,1,2,3,4};编译器将初始化数组长度为5。
int a[] = {0,1,2,3,4};
int len = sizeof(a)/sizeof(int); //数组长度尽量这样求
C++11数组初始化方法
数组以前就可以使用列表初始化,C++11的列表初始化中新增一些功能。 - 可以省略=号
int a[5] {0,1,2,3,4}; - 可以在大括号不写任何东西,这样将被初始化为0
int a[5]= {}; 所有都被初始化为0; - 列表初始化禁止缩窄转换
long a [] = {25,92,3.0} // 不行 3.0 为浮点,double-》long
char a[4] = {‘a’,‘v’,1112,’\0’} ; //不行。1112越界
char a[4] = {‘a’,‘v’,112,’\0’} ; //可以 虽然112为int 但在char的取值范围内
二 字符串
简介
字符串是存储在连续字节中的一系列字符。C++处理字符串的方式有两种:1.c风格的字符串;2. string
C风格的字符串有一种特殊的性质,以空字符结尾,’\0’,其ascii码为0,用来标记字符串的结尾。
char dog[5] = {‘a’,‘b’,‘c’,’ ',‘i’}; //不是字符串
char cat[5] = {‘a’,‘b’,‘c’,‘1 ‘,’\0’}; //是字符串
使用cout显示上面的char数组,它会逐个显示字符,知道遇到空字符为止。dog输出5个后会一直输出,知道空字符。cat显示abc1即结束。
上述字符串写太复杂。因此引入字符串常量
char bird[11] = “Cheeps”; 用引号括起的字符串隐式的包括结尾的空字符。另外各种C++输入工具通过键盘输入,将字符串读入char数组时,会自动加上结尾的空字符。
#include <iostream>
using namespace std;
int main()
{
char a[5] = "wth";
cout << sizeof(a) << endl; //5
cout << strlen(a) << endl; //3 不计算0
char b[] = "wth123";
cout << sizeof(b) << endl; //7 计算0
cout << strlen(b) << endl; //6 不计算0
char c[7] = { 'a','a', 'a', '\0', 'a' };
cout << sizeof(c) << endl; //7 计算0
cout << strlen(c) << endl; //3 不计算0
cout << c << endl; //aaa
char d[7] = { 'a','a', 'a', '\0'};
cout << sizeof(d) << endl; //7 计算0
cout << strlen(d) << endl; //3 不计算0
cout << d << endl; //aaa
//sizeof计算的是占内存大小的字节数,开辟了多少内存的字节
return 0;
}
注意:字符串常量(双引号)不能与字符常量(单引号)互换。
char a = ‘b’;
char b =“b”; 不一样,一个有\0 一个没有。
char c = “b”; "b"实际表示的是字符串所在的内存地址,因此将内存地址赋给哦一个char是不合理的。
拼接字符串常量
C++允许拼接字符串字面值,即将两个用双引号括起来的字符串常量合为一个。事实上,任何两个空白(空格,制表符,换行符)分割的字符串常量都可以拼接为一个。(拼接时不会在连接的字符串之间形成空格,第一个字符串的\0被第二个字符串的第一个字符取代)
在数组中使用字符串
将字符串存入数组中,常见的方式有两种:1.将数组初始化为字符串常量。 2. 将键盘或文件输入读入字符串中。
#include <iostream>
using namespace std;
int main()
{
const int size = 15;
char name1[size];
char name2[size]="wangyu!";
cout << "hello ,I'm " << name2;
cout << " and your name?\n";
cin >> name1;
cout << "well ,your name has :" << strlen(name1) << " leters\n";
cout << "in an array of " << sizeof(name1) << " bytes\n";
cout << "your init is " << name1[0] << " \n";
name2[3] = '\0'; //会将第四个字符替换为0 ,但在内存中其它数据都在
cout << "here is my name 3 charactor " << name2 << endl;
return 0;
}
输出:
hello ,I’m wangyu! and your name?
warr
well ,your name has :4 leters
in an array of 15 bytes
your init is w
here is my name 3 charactor wan
字符串输入
缺陷:
#include <iostream>
using namespace std;
int main()
{
const int size = 20;
char name[size];
char like[size];
cout << "Enter your name: " << endl;
cin >> name;
cout << "Enter your like : " << endl;
cin >> like;
cout << " your like is " << like << " and your name is " << name;
return 0;
}
正常输出
Enter your name:
wang
Enter your like :
apple
your like is apple and your name is wang
错误输出:(名字输入含有空格)
Enter your name:
wang tt
Enter your like :
your like is tt and your name is wang (都没有输入like就显示了)
说明:
cin通过空白(空格,制表,换行符),确定字符串结束的位置。即cin读取输入时只读取一个字符串,将其放入数组中,加上0.
本例是cin将wang放入name数组中,将tt留在输入队列,当cin在输入队列中搜索like时,读到tt,将它放入like数组中。
另外一个问题是输入字符串可能比目标字符串长。
按行读取
getline()和get()都读取一行输入,直到到达换行符,不同的是getline读取换行符,并把它替换为\0, 而get将换行符留在输入队列中。
- getline(),通过回车键输入的换行符确定结尾。两个参数,一个是用来存储输入行输入的数组的名称,另一个是要读取的字符数。若20,则读取19个,留一个存 \0.getline()在读取指定数目的字符或遇到换行符时停止读取。(它读取换行符并丢弃,替换为 \0)
- get(),与getline相似,不同在于将换行符留在输入队列。
方法1:
cin.get(name,20);
cin.get();//跳过换行符,读取下一行
cin.get(like,20);
方法2: cin.get(name,20).get();
cin.get(like,20);
cin.get()返回一个cin对象,随后可以用来调用get()函数。
因此,也可以 cin.getline(name.20).getline(like,20);
注意:推荐使用get(),因为首先老式实现没有getline(),其次get()使得输入更仔细。如何知道停止读取的原因是读取了一行还是到了换行符。get()可以判断下一个字符,若为换行,则读取了一行,getline则不行。- 空行和其它问题
get或getline读取空行时会发生的问题。当get读取空行时,将设置失效位(failbit),接下来的输入将被阻断,可以用cin.clear();恢复输入。
另一问题是:输入过长,get和getline会将余下的字符留在输入队列,getline设置失效位,并关闭后面的输入。
- 空行和其它问题
混合输入字符串和数字
cout << "your house build ?\n";
int year;
cin >> year;
cout << "your house stress ?\n";
char address[80];
cin.getline(address, 80);
cout << "your address :" << address << endl;
cout << "your bilid :" <<year<< endl;
输出:
your house build ?
159
your house stress ?
your address :
your bilid :159
说明:cin读取年份,将回车符键生成的换行符留在了输入队列,getline读取时,认为这是一个空行,并将空字符赋给address 即address读到的是换行符。解决方法读取地址之前,读取并丢弃换行符。
方法1:cin>>year; cin.get();
方法2:(cin>>year).get();
C++程序通常使用指针而不是数组来处理字符串。
三 string类简介
使用string类需要包含头文件,string类位于名称空间std中,因此必须使用using namespace std; 或std::string
使用string对象与数组相似。
- 使用C风格字符串初始化string对象 string str = “hello”;
- 使用cin cout 输入,显示string
- 数组表示法访问string
类设计让程序能够自动处理string的大小。
C++11字符串初始化
char a[] = “hello world”;
string a = {“hello world”};
string b {“hello world”};
赋值 拼接 附加
string str = “hello”;
string str1 = str;
string str2 = “world”;
str += str2;
str += “world”;
若想输出“ ” 则需要在每个“前加 \
string 类的其它操作
对于C风格的字符串,使用c函数库(cstring头文件),strcpy ,strcat追加
char a[20];
char b[20] = “hello”;
strcpy(a, b);
cout << a << endl; //hello
strcat(a, “world”); // helloworld
string 类的IO
#include <iostream>
#include<string>
using namespace std;
int main()
{
char a[20];
string str; //未初始化长度未0
cout << "before input a : " << strlen(a) << endl; //未初始化长度未知,找到为\0的长度
cout << "before input str : " << str.size() << endl;
cout << "enter a line text :\n";
cin.getline(a, 20);
cout << "your enter is " << a << endl;
cout << "enter other line text :\n";
getline(cin, str);
cout << "your enter is " << str << endl;
cout << "after input a : " << strlen(a) << endl; //未初始化长度未知,找到为\0的长度
cout << "after input str : " << str.size() << endl;
}
输出:
before input a : 1
before input str : 0
enter a line text :
hello world
your enter is hello world
enter other line text :
heklko chineise
your enter is heklko chineise
after input a : 11
after input str : 16
其它形式的字符串字面值
除了char类型外,C++还有wchar_t ,C++11新增char16_t char32_t,这些使用前缀L,u,U表示
wchar_t title[] = L"Chief Astrogator"; //w_char string
char16_t name[]=u"Felonia Ripova"; // char_16 string
char32_t car[]= U"Humber Super Snipe";// char_32 string
C++11 新增原始字符串(raw)
cout <<R"(hello “king” is “\n” )" << endl;
显示:hello “king” is “\n” //不用在
cout <<R"+((hello “king” is “\n”) )+" << endl;
显示:(hello “king” is “\n”) 使用+*( 替换(;
四 结构
同一结构可以存储多种数据类型。用户自定义的数据类型
//声明在不同的位置,作用域不同
struct Person
{
int age;
char name[20];
};
Person p; //C++风格
struct Person p1; //C风格
//初始化
Person p{ 18,"hello" };
Person p1 = { 18,"hello" };
cout << p1.age << " " << p1.name << endl;
结构可以将string类作为成员嘛
可以,只要编译器支持对以string对象作为成员的结构进行初始化。
如果指定的初始值比成员少,余下的成员将被设置为0.
其它结构属性
可以将结构作为参数传递,也可以结构赋值
Person p{ 18,“hello” };
Person p1=p; //不要有指针
struct Person
{
int age;
char name[20];
}p1,p2; //p1. p2 为两个Person变量
struct
{
int age;
char name[20];
}p1; //名为p1的结构变量
Person p{ 18,"hello" };
Person p1=p; //不要有指针
结构数组
struct Person
{
int age;
char name[20];
};
Person p[2] ={ {17,"wth"},{18,"eee"}};
cout << p[1].name << endl;
结构体中的位字段
与c语言一样,C++也允许指定占用特定位数的结构成员,使得与创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型为整型或枚举,之后: 加一个数字,指定了使用的位数。每个成员都被称为位字段。
struct torgle_register {
unsigned int SN : 4;
unsigned int :4;// 使用没有名称的字段提供间距。
bool goodIn : 1;
};
torgle_register tr{ 14,true };
cout << tr.SN;
位字段通常用在低级编程中,一般来说可以用整型或者按位运算代替这种方式。
五 共用体
公用Union是一种数据格式,他能够存储不同的数据类型,但只能同时存储一种类型。(公用一段内存)常用于操作系统数据结构或硬件数据结构。共用体的长度为其最大成员的长度。
union ID
{
int id;
char cId[20];
};
ID id;
cout << sizeof(id) << endl;//20
//匿名共用体
struct Person
{
int age;
union
{
int id;
char cId[20];
};
};
Person p;
p.id;
p.cId;
小端字节序:低地址,低字节; 大 低地址高字节
long 0x123456;
若是小端字节序:则ox0001存的是56
大端字节序:则ox0001存的是12
六 枚举
C++enum提供了另一种创建符号常量的方法,这种方式可以代替const。
//创建枚举,默认第一个为0 ,之后递增,
enum Color
{
red = 0,
orange = 1,
yellow,
blue,
green=8,
qing, //则此处为9
purse = 8, //可以创建多个值相同的枚举量
zi
};
Color c;
//不进行强转的情况下,只能将定义枚举时的枚举量赋值给枚举类型的变量
c = qing;
//c = 20;//error
cout << c << endl; //9
//枚举是整型,可被提升为int ,但int 不能转为枚举
int co = blue; //ok co is 3;
//c = 3;//error;
co = (Color)3; //OK
co = blue + 1; //4
cout << co << endl;
枚举类型的取值范围
enum bits {one =1,two = 2,four = 4, eight = 8};
bits m = bits(6);//合法 6不是枚举值,但在枚举范围内
上限:最大的数 8 ,找到大于这个最大值的 最小2次幂。 若最大值为101 ,则比101大的是2^7 = 128 ,因此最大为127.
下限:若最小值不小于0,则下限为0;
若最小为负数,比如-6, -8,因此下限为-7.
C++11扩展了枚举,增加了作用域内枚举。
七 指针和自由存储空间
计算存储数据时必须提供3种属性:信息存储在何处,值是多少,存储的什么类型。
&地址运算符,获得变量的地址。
指针是一个变量,其存储的值是地址,不是值本身。
using namespace std;
int main()
{
int a = 10;
int* p = &a;
cout << "a address : " << p << endl;
cout << "a address : " << &a<< endl;
cout << "*p value : " << *p << endl;
cout << "p address : " << &p << endl;
}
输出:
a address : 0000002179AFF5A4
a address : 0000002179AFF5A4
*p value : 10
p address : 0000002179AFF5C8
面向对象
面向对象与面向过程的区别在于:oop强调的是在运行阶段(而不是编译阶段)进行决策。(比如数组)
指针的危险
C++中创建指针时,计算将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据内存。
int *a ;
a = 2222;//危险,2222的地址可能是其他的数据。一定要在对指针应用解除引用运算符前()将指针初始化为一个确定的适当的地址。
指针和数字
int *a ;
a = 0x1234; // 不行,要将数字值作为地址使用,应通过强制转换类型将数字转为适当的地址。
a = (int *)0x1234;
使用new分配内存,delete
指针的真正使用是,在运行阶段分配未命名的内存以存储其值。这种情况只能通过指针访问内存。C中库函数malloc(),C++中除了使用它,还可以new。
int *p = new int; *p = 100;
new分配的内存块通常与常规变量声明分配的内存块不同,变量在栈区,new分配的内存块在堆区。
只能使用delete来释放使用new 分配的内存。然而对空指针使用delete是合法的
使用new动态分配数组
int *pa = new int [10];
pa指向的为一个长度为10的数组的首个地址,即a[0];
int main()
{
int* pa = new int[3];
pa[0] = 1;
pa[1] = 2;
pa[2] = 5;
cout << "pa[0] " << pa[0] << endl;
cout << "pa[1] " << pa[1] << endl;
cout << "pa[2] " << pa[2] << endl;
pa = pa + 1;
cout << "pa[0] " << pa[0] << endl; //2
delete[] pa;
}
注意:使用new和delete应该遵循以下规则
- 不要使用delete来释放不是new分配的内存
- 不用用delete释放同一个内存两次
- new 【】 对应 delete 【】;
- 对空指针使用delete是安全的。
八 指针,数组和指针算术
指针变量+1,增加的量等于它指向的类型的字节数。C++将数组名解释位地址
int a[2];
a[1] // *(a+1)
数组的地址
short tell[10] = {0};
cout << tell << endl; //0038FE44
cout << &tell[0] << endl; //0038FE44
cout << &tell << endl; //0038FE44
cout << tell+1 << endl; //0038FE44 + 2
cout << &tell+1 << endl; //0038FE44 + 20
数组名被解释为其第一个元素的地址,而对数组名应用地址运算符得到的是整个数组的地址。
数字上说这两数完全相同,概念上说&tell[0](即tell)是一个2字节的内存块地址,&tell是一个20字节的内存块地址。
short (pa)[20 ] = &tell;
数组指针,指向一个有20个short的数组。若省略括号,则pa先与20结合,导致pa是个指针数组。pa的类型为short ()[20],由于pa被设置为&tell,因此*pa与tell等价。(*pas)【0】为tell数组的第一个元素。
指针和字符串
cout和多数C++表达式中,char数组名,char指针,以及用括号引起来的字符串常量都被解释为字符串第一个字符的地址。
int main()
{
char animal[20] = "dog";
const char* bird = "Fly";
cout << "animal : " << animal << " and bird : " << bird << endl;
cout << "input a animal\n";
cin >> animal;
char* pa = animal; //单纯的地址复制。两个指向同一块地址
cout << pa << endl; //输入字符串的地址
cout << "before use strcpy :\n";
cout << animal << " at : " << (int*)animal << endl; //打印字符串的地址,若不转换则打印的是字符串本身
cout << pa << " at : " << (int*)pa << endl;
pa = new char[strlen(animal) + 1];
strcpy_s(pa, strlen(animal) + 1, animal);
cout << "after use strcpy :\n";
cout << animal << " at : " << (int*)animal << endl;
cout << pa << " at : " << (int*)pa << endl;
delete[] pa;
}
输出:
animal : dog and bird : Fly
input a animal
cat
cat
before use strcpy :
cat at : 0035FA94
cat at : 0035FA94
after use strcpy :
cat at : 0035FA94
cat at : 005219C8
注意:
strcpy的缺点
char a[3];
strcpy(a,“hellowrold”);
这种情况下,函数将字符串中后面的部分复制到数组后面的内存中,可能会覆盖程序正在使用的内存。避免这种问题使用strncpy();但需要注意的是若内存已经用完,则不会增加空字符。因此
char a[3];
strncpy(a,“hellowrold”,2);
a[2] = ‘\0’;
这样最多将2个字符复制到数组中 ,若源字符小于2个字符,会自动加上’\0’。
new创建动态结构
struct Person
{
int id;
char name[20];
};
Person* p = new Person;
p->id = 9;
strcpy_s(p->name, 5, "wth");
cout << p->id << endl;
cout << (*p).id << endl;
自动存储 静态存储 动态存储
C++新增线程存储
- 自动存储 。 自动变量。存储在栈中,作用域为包含它的代码块
- 静态存储。 程序执行期间都存在的存储方式。使用变量成为静态有两种方式,一是在函数外定义它,另一种是在声明变量时使用关键字static。 static int a = 6;
- 动态存储:自由存储空间或堆,由于new和delete 使得自由存储区域不连续。在栈上自动添加和删除的机制使得其空间时连续的。
九 类型组合
数组 结构 指针
int main()
{
struct Person
{
int id;
};
Person p1, p2, p3;
p1.id = 11;
Person* p = &p2;
p->id = 12;
Person parray[3];
parray[0].id = 15; //(parray+1)->id =11; //数组名是一个指针可以
cout << parray->id << endl; //15
Person* aa[3] = { &p1,&p2, &p3 }; //指针数组
cout << aa[1]->id << endl; //12
Person** paa = aa;
//aa是一个数组的名称,它是第一个元素的首地址。
//但其第一个元素为指针,因此paa是一个指针,指向一个Person的指针。可以使用auto
//paa是一个指向结构体指针的指针,*paa是一个结构指针可以 (*(paa+1))->id
auto ppb = aa;
cout << (*paa)->id << endl; //11
cout << (*(ppb+1))->id << endl; //12
}
十 数组替代品
模板类vector 和array是数组的替代品
模板类vector
模板类类似于string类,也是一种动态数组,使用new 和delete管理内存
vector a; //初始化为0
vector a(10); //初始化为10个元素
vector des(4,6); //初始化4个元素,默认值6
vector vt(n_elem) //n_elem可以是整型常量也可以是整型变量
模板类array(C++11)(定长数组替代品)
vector类比数组强大,但付出的代价是效率较低。如果需要的是固定长度的数组,使用数组更佳。C++11新增array,与数组一样,
定长,位于栈区,因此其效率与数组相同,但更安全,方便。
array<typename, n_elem> arr;
n_elem 不能是变量
array<int,4>a = {1,2,3,4};
比较
int main()
{
int a1[4] = {1,2,3,4 };
vector<int> a2(4);
a2[0] = 5;
a2[1] = 6;
a2[2] = 7;
a2[3] = 8;
array<int, 4> a3 = { 6,7,9,8 };
array<int, 4> a4 = a3; //可以直接赋值
cout << "a1[2]" << a1[2] << " at " << &a1[2] << endl;
cout << "a2[2]" << a2[2] << " at " << &a2[2] << endl;
cout << "a3[2]" << a3[2] << " at " << &a3[2] << endl;
cout << "a4[2]" << a4[2] << " at " << &a4[2] << endl;
a1[-2] = 0; //转为 *(a1-2)= 0; 找到a1的首地址,前移两个int 存入0.即存到数组外面。
cout << "a1[-2]" << a1[2] << " at " << &a1[2] << endl;
cout << "a3[2]" << a3[2] << " at " << &a3[2] << endl;
cout << "a4[2]" << a4[2] << " at " << &a4[2] << endl;
}
输出:
a1[2]3 at 00000028B610F780
a2[2]7 at 000001BB01861298
a3[2]9 at 00000028B610F7F0
a4[2]9 at 00000028B610F820
a1[-2]3 at 00000028B610F780
a3[2]9 at 00000028B610F7F0
a4[2]9 at 00000028B610F820
三者都可以使用下标来访问,array和数组存储在栈种,vector存储在堆中。赋值array可以直接赋值。
a1[-2] = 0; //转为 *(a1-2)= 0; 找到a1的首地址,前移两个int 存入0.即存到数组外面。
可以使用a1.at(1);区别在于使用at,程序会在运行期间捕获非法的索引,而程序将默认中断。这种代价是程序的运行时间更长。