数组
// 声明数组的格式: typeName arrayName[arraySize];
// arraySize不能是变量
short months[12];
// 声明并初始化数组
int yams[3] = {1, 3, 10};
// 数组列表初始化只能在声明数组的语句中进行
// 数组赋值给另一个数组是不允许的
int yams1[3] = {1, 2, 3};
int yams2[3];
yams2[3] = {1, 2, 3}; // 不允许
yams2 = yams1; // 不允许
// 数组初始化不完全时其他未初始化的部分被初始化为默认值
int yams[5] = {1, 2}; // 数组被初始化为{1, 2, 0, 0, 0}
sizeof
int yams[3] = {1, 3, 10};
cout << sizeof yams << endl; // 12,说明yams数组占12个字节
cout << sizeof yams[1] << endl; // 4,说明yams[1]占4个字节
char name1[15];
cin >> name1; // 假设输出了java,四个字母
cout << sizeof name1 << endl; // 15, 说明name1数组占15个字节
char数组
数组名存储的是数组中第一个元素所在的地址
double wege[3] = {1, 2, 3}; // 则wege == &wege[0]
C风格字符串
// C风格字符串以'\0'结尾,它的ASCII码是0
char list1[]{'H', 'e', 'l', 'l', 'o', ',', 'w', 'o', 'r', 'l', 'd', '!'};
char list2[]{'H', 'e', 'l', 'l', 'o', ',', 'w', 'o', 'r', 'l', 'd', '!', '\0'};
// list1,list2都是char数组,但只有list2是一个C风格字符串
cout打印char数组的时候遇到空字符(也就是’\0’)后停止打印,所以如果cout<<list1; 可能会多打印其他东西
字符串常量(也叫字符串字面值)
char list1[11] = "Mr. Cheeps";
// 字符串常量在结尾处隐式的包含空字符,所以不用显示的表示,但空字符仍然占据一个字节,所以list1有11字节
// 所以,"S"和'S'不同,前者其实是两个字符组成的字符串。
// 实际上,"S"表示一个指向这个字符串的地址
字符串拼接
// C++中空格,回车,制表都被看做是一个空白
cout << "Hello, " "world" << endl;
cout << "Hello, "
"world" << endl;
cout << "Hello, world" << endl;
// 这三句话作用相同
// 字符串拼接时,前一个字符串末尾的'\0'会被后一个字符串的首个字符覆盖。
数组赋值给另一个数组是不允许的,所以对char数组进行操作有专门的的函数
strcpy() ,strncpy(),strcat() ,strlen() 和 strcmp()( 函数(位于头文件cstring里)
char name1[20] = "hello";
char name2[20];
string str="sir";
strcpy(name2, name1); // strcpy(value2, value1)的作用是把数组value1里的内容赋值 给value2
strcat(name2, name1); // strcat(value2, value1)的作用是把数组value1里的内容追加 到value2后边
strncpy(name2, name1, 19); // 最后一个参数的意思是限制最多拷贝19个字符给name2,然后加空字 符。或者name1不足19个,那就把name1拷贝给name2,然后加空字符。
strcmp(name1, "hello") // 如果值相同,返回0,如果第一个参数按字母顺序在第二个参数之 后,则>0,如果第一个参数在第二个参数之前,则<0
cout << name1 == "hello" << endl; // 结果是0,因为比较的是地址而不是值
cout << sizeof name1 << endl; // 20
cout << strlen(name1) << endl; // 5,不包含空字符。
cout << str.size() << endl; // 3,这是获取string对象的长度的函数
// strlen() 和 sizeof 不同,前者返回存储的字符数(不包含最后 的'\0'空字符),后者返回占据的字节数
getline() 和 get() 函数 ---- 面向行输入char数组
// 一次只能输入一个单词问题: char数组是以空字符串作为结尾的,但是用户输入的时候是不会输入空字符的,因为'\0'这个字符无法从键盘输入。所以cin把空白(空格,制表符,回车)作为一个字符串的结尾。
// getline() 和 get() 函数都是istream类提供的类成员函数
**getline()**函数:读取一行输入直到换行符出现,读取并丢弃换行符
char name[20];
cin.getline(name, 20); // 系统 接受到19个字符 或者 遇到换行符 的时候会停止读取。
**get()**函数,读取一行输入直到换行符出现,不读取换行符,而是将它留在输入流中,cin也不会读换行符
char name[20];
cin.get(name, 20); // 系统遇到换行符就停止读取,所以如果只使用get()函数,系统始终不能跳过换行符
cin.get(); // get()函数的变体,读取下一个字符,哪怕是换行符。
cin.get(name, 20).get(); // 作用和前两句加起来相同,读取字符串到换行符并丢弃换行符。
之所以可以出现cin.get(name, 20).get(); ,是因为cin.get(name, 20)返回一个cin对象,它仍可以调用istream类的成员函数get()
get() 相对 getline() 的优势是当出现输入问题时可以通过cin.get()获取下一个字符,从而得知输入流间断是由于达到数组长度上限还是遇到了换行符。
string类(在头文件string中,位于命名空间std里)
C++推出了string类存储字符串,string类不是字符数组,而是单独的把字符串也作为了一个数据类型。
string比char数组更方便,更安全。
例如,不能把一个数组赋值给另一个数组,但可以把一个string对象赋值给另一个string对象
string s1 = "hello";
string s2;
s2 = s1; // 允许把一个string对象赋值给另一个string对象
s2 = "world"; // 允许把C风格字符串赋值给string对象
s2 += s1; // 允许string对象和其他string对象或者C风格字符串拼接。
cout << s2.size() << endl; // 作用类似于char数组中的strlen()函数,获取字符串长度
// 并且可以看出,string类型变量是可以改变的。
cstring头文件是之前没有发明string类型的时候,对char数组进行操作的类
string头文件是发明了string类型之后,对string对象进行操作的类
// 根据 strlen(name1) 获取char数组name1的长度
// 根据 s1.size() 获取string对象s1的长度
可以看出,C函数通过参数指定操作对象,而C++函数通过对象名和句点运算符指定操作对象
getline()函数 ----面向行输入string对象
char name1[20];
cin.getline(name1, 20); // 获取一行的输入并赋值给char数组name1,在遇到换行符或者已经从 输入流获取19个字符后将停止从输入流获取数据。
string str1;
getline(cin, str1); // 获取一行的输入并赋值给string对象str,在遇到换行符之后停止从 输入流获取数据(因为string对象没有大小限制)
原始(raw)字符串
cout << R"("jeni" is a name of a wugui.\n)" << endl;
// 输出"jeni" is a name of a wugui.\n
此处的界定符是 “( 和 )” ,允许在情况需要时自动以界定符,可以在引号和括号中间添加任意数量的基本字符来自定义界定符,比如可以设置界定符为 “±/( 和 )±/”
结构struct
数组只能存同类型的数据,但结构可以存储多种类型的数据。结构还可以组成结构数组。
// 结构定义相当于生成了一个新类型
struct structName
{
typeName1 name1; // 每个列表项都是一条声明语句,被称为结构成员
typeName2 name2;
typeName3 name3;
...
};
// 创建这种类型的变量。
structName structname1;
structName structname2 = {value1, value2, value3...};
// 结构类型变量可以被作为参数,返回值,可以用一个结构变量给另一个同类型结构赋值
structName structname3 = structname2;
// 可以直接在定义结构的同时创建一个该结构类型的变量
struct structName
{
typeName1 name1;
typeName2 name2;
typeName3 name3;
...
} structname1, structname2; // 在定义结构structName的同时创造了两个结构变量structname1, 2
// 有时一个可能不需要专门定义一个结构,只是需要这个结构的一个变量,可以直接声明没有名称的结构类型
struct
{
typeName1 name1;
typeName2 name2;
...
} structname1; // 定义一个结构变量structname1,它具有name1,name2等成员
成员运算符. (通过结构变量的变量名访问成员)
name.structname1;
结构数组
// 可以创建元素为结构的数组
structName structname1[size];
// 创建的时候可以进行初始化
structName structname2[size]
{
{value1, value2, ...},
{value1, value2, ...}
};
structname2; // 代表刚刚创建的结构数组
structname2[index]; // 访问结构数组的某个结构元素
structname2[index].name1; // 访问结构数组的某个结构元素的某个结构变量
//
结构可以在函数外部声明以及内部声明,如果在函数外部定义结构,该结构定义语句之后的所有函数都可以使用此结构。
C++允许结构有成员函数
共用体union
// 共用体可以存储很多数据类型,但一次只能存其中的一个
union unionName
{
typeName1 name1;
typeName2 name2;
typeName3 name3;
...
};
// 这些typeName对于union来说是它的形态之一,也就是说一个unionName型变量可能是一个typeName1类型的值,也可能是一个typeName2类型的值
// 一个共用体的应用:商品的id可能是数字也可能是字符串
struct widget
{
char brand[20];
int type;
union id
{
long id_num;
char id_char[20];
} id_val;
};
...
widget prize;
if (prize.type == 1)
cin >> prize.id_val.id_num;
else
cin >> prize.id_val.id_char;
// 共用体也可以是匿名的
struct widget
{
char brand[20];
int type;
union
{
long id_num;
char id_char[20];
};
};
...
widget prize;
if (prize.type == 1)
cin >> prize.id_num;
else
cin >> prize.id_char;
共用体的作用通常是节省内存
枚举enum
一种代替const创建符号常量的方式
// 以下这句话相当于定义了一个新类型enumName,这个类型的值可以是name1,name2...,没有赋值以外的其他运算
enum enumName = {name1, name2, ...};
// 此时 enumName 是一个枚举,name1, name2...称为枚举量
// 默认情况下,枚举量的值从0,1,2...递增,即在上边的语句里name1默认是0,name2默认是1...。
// 枚举量的值可以设置
enum enumName = {name1, name2 = value, name3...};
// 此时name1 = 0, name2 = value, name3 = value+1, 之后递增
// enumName 的取值范围是 2^n ~ 2^m,其中2^n是小于name1的最大数,2^m是大于最后一个枚举值的最小数
// 定义和初始化一个枚举变量
enumName enumname1 = name1;
// 枚举值是整形,所以可以把枚举值赋给int之类,但不允许把其他类型值直接赋值给枚举变量
int a = enumname1 + 1; // 允许
enumName enumname2 = a - 1; // 不允许
enumName enumname3 = enumName(a - 1); // 允许
enumName enumname3 = enumname1 + enumname2; // 不允许,因为枚举类型enumName没有定义+运算,所以这里默认会把enumname1 + enumname2转成int(enumname1) + int(enumname2),int型值不允许赋值给枚举变量
指针
指针类型的变量存的内容是值的地址,而不是值本身。通过**地址运算符&**获得一个变量的地址
面对对象编程和面对过程编程的区别:OOP强调在运行阶段做决策
在编译阶段做决策:提前做好计划,无论发生什么都按照计划进行。在编译时给数组分配内存被称为静态联编。
在运行阶段做决策:根据当前情形进行决策,更灵活,比如可以在运行阶段决定数组的长度避免空间浪费。在运行时给数组分配内存被称为动态联编。动态联编的好处是可以在运行时确定数组的长度。动态联编创建的数组称为动态数组。动态数组用new关键字创建。
int counts;
int* pcounts;
pcounts = &counts;
// &valueName 表示获取一个值的地址
// typeName* pname; 表示声明一个指向一个typeName类型值的指针变量,指针变量通常用p做前缀名
// pname 是一个指针, *pname 表示这个指针指向的值,pname的类型是typeName*,*pname的类型是typeName
// 上边的例子中,&counts = pcounts, counts = *pcounts
同时声明多个指针变量时容易出现的问题:
int* a, b; // 这里其实声明了a是一个int指针型变量,b是一个int型变量
int *a, *b; // 这其实才是声明两个int指针型变量 a 和 b
和数组一样,数组不是一个数据类型,int数组,char数组这种才算“一个数据类型”。
指针同样,指针不是一个数据类型,int指针,char指针这种才算“一个数据类型”。
可以用typedef关键字把int*声明成一种类型
typedef int* pint;
pint a, b; // 这样,a和b都会被定义为int*变量
使用指针可能会导致危险:
int* a;
*a = 23; // 表面看没有问题,实际上指针a没有进行初始化,所以它可能是任意值,它可能 指向程序代码,此时修改该指针处的值可能会导致不小心把程序代码修改了
通常来说修改指针指向内容的值之前,必须保证指针指向的位置是已知的。否则可能会导致问题。
指针在计算机中也是二进制存储,表现形式则是十六进制,但不能用十六进制数字给它赋值。
将指针变量加一后,其增加的值相当于增加了指向的类型占用的字节数
两个指针相减获得的也是其指向的类型变量的个数差
int a[2] = {4, 3};
int* pa = a; // 此时pa指向数组a的第一个元素
pa++; // 此时pa指向数组a的第二个元素,pa实际的值+sizeof int也就是加4
// *(pa + 1) = pa[1];
int *pa2 = a[9];
cout << pa2 - pa; // 得到的结果是a[9] - a[1] = 8
new关键字
一般来说变量名就是给一个内存命名,值就是这个内存里保存的值。指针允许不给一个内存命名就能直接访问到这个内存里的值。这种未命名的内存只能通过指针进行访问。C++中用new分配未命名内存。
int* a = new int; // new会找到一个存int型变量的内存,把地址返回赋值给a
new分配的内存块和常规变量声明分配的内存块不同,常规变量的值(比方说int型变量,int*型变量)都存在栈里,而new从堆(也叫自由存储区)里分配内存。
用new声明的内存,在使用完之后需要用delete释放
delete的原理其实是删除new创建的内存,也就是说删除指针变量指向的内存,但不是删除指针变量
int* a = new int;
...
delete a;
new和delete必须成对使用,否则可能发生内存泄漏(堆里的内存空间耗尽)
另外,delete只能用于未命名的内存,如果一个内存有名字,不能用delete
int a = 10;
int* pa = &a;
delete pa; // 这是不允许的
总之 new 和 delete 一定是成对出现的。
用new关键字创建动态数组:
int* pname = new int[10]; // new关键字返回的是一个地址,int[10]告诉它应该找一个多大块的地址
delete [] pname;
箭头成员运算符->(通过地址访问成员)
用new关键字创建一个未命名的结构:
inflatable pa = new inflatable;
// 这里的inflatable是某个结构的名字,此时pa是一个inflatable类型的指针
当一个结构类型变量访问它的成员时,使用成员运算符 ** ".**"
inflatable a;
a.name = "hello"; // 结构类型变量的变量名使用成员运算符访问成员
但问题是,用new关键字创建的结构未命名,C++设计了箭头成员运算符 “->”,可以允许那些只知道地址不知道名字的结构用它来访问成员
inflatable pa = new inflatable;
pa->name = "hello"; // 结构地址使用箭头成员运算符访问成员
指针和数组的不同
- 指针类型的变量可以修改,但数组名是常量
int a[2] = {4, 3};
int* pa = a;
pa++; // 正确的,指针变量值可修改
a++; // 错误的,数组名是个常量
- sizeof 指针变量,得到的是指针变量的长度,sizeof 数组名,得到的是整个数组占据的长度
int a[2] = {4, 3};
int* pa = a;
cout << sizeof a << endl; // 8,因为一个int型变量占4字节,a数组定义为有两个int变量的数组
cout << sizeof pa << endl; // 4,因为存储一个int指针型变量要4字节
打印字符地址
cout遇到其他类型地址时会打印地址值,但遇到字符地址(char数组名,char指针,字符串常量)时就会打印地址指向的字符直到遇到空字符’\0’
所以如果想打印字符地址值,就把它强制转换成别的类型地址
char a[3] = {"hel"};
cout << a << endl; // hel,因为cout遇到字符地址时会自动打印地址指向的字符串
cout << (int*)a << endl; // 一个十六位进制数,因为cout遇到int型地址时会打印地址值
C++管理数据内存的方式
自动存储
函数中的常规变量使用自动存储空间,被称为自动变量,就是局部变量。作用域就是它所在的代码块,保存在栈中
静态存储
整个程序执行期间都存在的存储方式
两种方法定义静态存储的变量:1. 在函数外定义变量 2. 声明变量时使用关键字static
动态存储
C++中自由存储空间或堆,和存储 自由变量 和 静态变量 的内存是分开的
动态存储的变量的生命无关函数或者程序,而是在new到delete期间都可用
数组、vector类、array类
头文件vector里有vector类,vector包含在命名空间std中
vector和string类似,string是动态的char数组,vector是动态数组,内置有new和delete
#include <vector>
using namespace std;
int main()
{
vector<int> vi;
vector<double> vd(n); // vector是动态数组,长度可变,所以初始可以不用声明长度
// 在声明静态数组的时候要求声明的长度必须是常量,但是 声明vector数组的长度可以用常量也可以用变量
return 0;
}
vector的优势是自由,缺点是慢,因为长度固定的数组比不固定的效率更高
头文件array里有array类,array包含在命名空间std中
array是静态的,长度固定
#include <array>
using namespace std;
int main()
{
array<int, 5> ai; // 声明一个长度为5的int数组ai
array<double, 4> ad = {1.2, 3.3, 5.9, 0.2}
// 声明array数组的长度只能用常量
return 0;
}
array相对于数组的优势是可以用赋值运算符 “=” 在array对象间赋值,而且array对象比数组更安全
vector 和 array 其实也可能发生越界访问错误,但可以选择避免
vector<int> a(2);
array<int, 2> b;
a[-1] = 10;
b[200] = 1; // 代码不会报错
a.at(1) = 1; // vector 和 array 类都有 at() 函数,在不确定是否越界时 可以使用at(),避免越界访问