数组的存储:数组元素逻辑上连续,物理地址上也连续。对于二维数组可以将每一行看一维数组的一个元素,元素个数为行数。

数组的声明:数据类型 标识符[][].....;

数组初始化
1.初始化数组中元素个数不的多于[]的个数。
2.若[]内未声明个数,则默认为初始化元素的个数。
3.若只对数组的起始部分元素初始化,则必须要指明[]内的数。
4.只有定义数组的时候才能进行初始化,之后不能用=对数组进行赋值,但是可以用数组下标对每个元素进行赋值。
5.对于多维数组,若给出全部元素,则第一维下表可以不说明。对于二维数组可以用{}将每行元素括起来,方便分辨。
6.声明为常量的数组,必须给定初值。

注:对于静态生存期的数组,每个未赋初值的元素会自动赋为0,而动态生存期的数组,未赋初值的元素的初值不确定。

数组作为函数参数
1.数组元素作为参数同该类型的变量作为参数。
2.数组名作为参数,传递的是数组的首地址。实参数组元素个数不少于形参数组个数,由于传递的是地址,所以若被调函数的形参数组发生变化,则实参数组也会跟着变化。

注:将数组作为参数时,可以不用指定数组的第一维大小。

对象数组
1.数组元素为对象。
2.声明:自定义类名 数组名[大小];(字符串类数组:string str[10]😉
3.用数组元素访问公有成员:数组名[下标].成员名。
4.对象的初始化需要用到构造函数,同样对象数组的初始化也需要用到:自定义类名 数组名[大小]={构造函数(初值1),构造函数(初值2)......};若未指定对象初始值,则会调用缺省构造函数或默认构造函数初始化对象。
注:1.若数组元素对象的初值要求相同或不初始化,则需要定义默认构造函数。若每个对象要求的初值不同,则需要定义含参构造函数。2.数组创建需要调用对象的构造函数,删除需要调用析构函数。

指针
作用:1.组织表示数据结构。2.动态内存分配。

内存数据的访问方式
1.变量名:对于静态生存期的变量(全局变量),在程序运行前就被分配了内存,而动态生存期变量,在声明时分配,变量名就代表内存空间名。
2.地址:当变量名不方便或者无变量名时,只能通过地址来访问,不方便体现在传递指针/引用比值传递更快,当使用动态内存分配时,new关键字只能返回分配内存空间的首地址。

指针变量
1.指针变量用于存放变量的地址。
2.声明:数据类型 标识符。在声明中,表示声明了一个指针变量,数据类型指的是指针指向对象的数据类型,称为指针的类型。(地址的类型为无符号)

和&运算符
1.在声明语句中:
代表声明标识符为一个指针,例如int p中声明p时一个int型指针。&代表声明一个引用,例如int &p=b中声明p为b的引用,即为b的别称。
2.在执行语句中:
代表取内容运算符,例如* p表示取以p为地址的内存中存放的内容。&代表取地址运算符,例如& p表示取变量p的地址。

指针的赋值
1.指针的声明只能确定指针的类型,此时指针内会有脏数据可能会访问到重要的数据导致系统故障,所以在使用指针前必须先进行初始化或者赋0(NULL)。声明:存储类型 数据类型 指针名=地址;地址可以是已声明且类型和指针相同的对象的地址或者是另一个同类型的指针。
2.对于数组,数组名表示数组的首地址,且属于指针常量,所以不能让其成为左值进行加减访问数组元素,只能将数组名赋值给另一个指针ptr,通过
(ptr+n)来访问数组元素。
3.指向常量的指针:即不能通过指针改变对象的值,但是指针自身的值可以改变。声明:const 数据类型 *指针;
4.指针类型的常量(指针常量):指针自身的值为常量,无法更改。声明:数据类型 *const 指针;
5.void类型指针可以存储任何类型的指针,声明:void *p;void指针在不确定赋值指针所指对象的类型时使用。
6.若想要用不同类型的指针变量进行赋值,则需要进行强制类型转换(被赋值对象的类型 *)。

注:不同类型的指针之间进行赋值需要进行强制类型转换。格式:数据类型 *p=(数据类型 *)b;

指针运算
1.指针的整数加减:指针的整数加减与指针类型密切相关,如指针p和整数n,p+n表示p指针指向的变量后第n个变量的地址,且p+n的值和变量的数据类型有关,每一个整数代表一个数据类型大小的地址增减。(一块内存空间为1B大小)
2.(p+n)可写作p[n],(p-n)可写作p[-n]。
3.由于数组的内存空间连续,所以指针算数运算主要用于数组,可以实现随机存取。
4.指针的关系运算:只能作用于相同数据类型的指针。

用指针处理数组元素
1.可以通过指针来访问数组元素,例如声明一数组int array[5]。其中array=&array[0],*(array+3)=array[3]。
2.将数组作为函数的形参,等价于传入数组的首地址。参数列表的等价写法:void f(int p[]),void f(int p[3]),void f(int *p);
3.访问数组元素的方式:1.数组下标。2.数组名(首地址)+整数。3.将数组首地址赋给指针变量,通过指针变量访问。
4.指针计算容易越界,所以引入begin和end函数,函数将数组名作为参数,返回元素地址。1.begin(a)返回数组a首元素的地址。2.end(a)返回数组a尾元素下一个元素的地址。俩个函数定义在iterator头文件中。

指针数组
1.每一个数组元素都是指针变量,且每个元素都是同一类型的指针。
2.声明:数据类型 数组名[];数组名代表数组的首地址,同时也是二级指针。
3.指针数组必须先初始化,才能引用,因为指针只声明不初始化当被误用后会导致错误。
4.二维数组与指针数组的关系(int p[3]与int array[3][3])
1.对于指针数组来说
(p[i]+j)等价于p[i][j]。通过p[i]访问指针数组中第i个指针,即第i+1行一维数组的首地址,然后p[i]+j代表指向该一维数组的第j+1个元素的指针。
2.对于二维数组array[3][3]来说,可以将二维数组看成含有三个元素的一维数组,每个元素为一行一维数组。array[i]代表第i+1行的一维数组,也为该一维数组的数组名,表示该一维数组的首地址,为一级指针。所以
array[0]表示array[0][0],(array[0]+1)表示array[0][1]。
3.俩者的区别在于:p[i]是从指针数组内获取,而array[i]是通过数组名array(二级指针)来获取,即
(array+i)表示为array[i]。
4.俩者虽然存在差异,但是对元素的访问相同,即可以把二维数组当成指针数组访问,也可以将指针数组当成二维数组访问。((array+i)+j)<->array[i][j]

用指针作为函数参数
1.为了减少值传递带来了大量数据拷贝开销,可以只传递指针,对于数组来说,传递的是数组首地址。
2.指针传递和引用传递一样的效果:1.效率高。2.形参改变,实参也会改变。除此之外可以通过指向函数的指针传递函数代码的首地址。
3.若函数中不需要修改形参的值,可以将其声明为指向常量的指针,目的和常引用一样,除了能传入普通对象的地址,也能够传入常对象的地址,该方法比不改变实参的值传递更快。

指针型函数
1.普通函数只能返回一个变量或者对象,而指针型函数返回的是指针,从而可以返回大量的数据,例如数组。
2.声明:数据类型 函数名(参数表){函数体};有俩种声明方式:1.int func(){return array}返回的是数组首地址。2.int (func())[10]{return &array}返回的是首地址的地址,即二级指针。
3.int (
func())[10]解析:func()表示数组首地址的地址。func()表示数组首地址即数组名。 (func())[10]表示一个大小为10的数组。
1.可以使用类名别名的方法对该声明化简:using arr=int[10];从而上述表达式可进一步化简为arr func();若知道函数返回的二级指针指向哪个数组(int a[10]),可以用decltype关键字声明返回类型:decltype(a) func()。表示func()返回一个二级指针指向含有10个整数的数组。
2.c++11中可以用尾置返回类型对该声明化简:auto func() ->int(
)[10];
4.对返回数组指针的函数调用:int (
p)[10]=func();

指向函数的指针
1.函数名表示函数代码的首地址,而指向函数的指针同样指向函数代码的首地址。所以既可以用函数名调用函数,函数指针也可以。
2.声明:数据类型 (函数指针名)(参数表)。可以用typedef对该声明化简。对于int (func)(double);可以用typedef int (*func)(double);从而可以用func来声明指针,对于func c;表示声明一个具有该类型,名为c的函数指针。
3.typedef的使用方法:用别名代替变量名,在前面加上typedef,则后面可以用别名来声明变量。
4.函数指针在使用前也需要进行初始化,定义:函数指针名=函数名;要求赋值的函数必须已经声明,且和函数指针具有相同的返回类型和参数列表。
5.利用函数指针调用函数:函数指针名(参数列表);

对象指针
对象的特点
1.和变量,函数一样,既可以通过名字,也可以通过地址来访问对象。
2.对象所占的存储空间只用于存放数据成员,而不存放函数成员。
3.对象指针的声明:类名 对象指针名;
4.对象成员的访问:1.对象指针名->成员名.2.(
对象指针名).成员名。
5.对象指针在使用前也需要初始化,为其赋予一个已声明对象的地址。通过对象指针可以访问对象的公有成员。
6.当一个类使用了前向声明,且未定义时,在声明后面不能立马创建对象,因为对象需要从类中继承成员,而类的定义不完全。但是可以声明一个指向对象的指针,就算后续再定义类也不影响该指针的使用。

this指针
1.this指针用于指向当前调用该成员函数的对象。且this指针作为成员函数的一个隐藏参数传入其中,在成员函数想要调用对象的数据成员,可以通过this指针来间接调用。
2.this是一个指针常量。对于常成员函数来说,this为指向常量的指针。*this指代调用该成员函数的对象。
3.若在成员函数中声明了与类中同名标识符,对标识符的直接引用表示成员函数中的标识符,若想调用类的同名标识符,则需要通过this指针。

指向类的非静态成员的指针
1.使用指针变量存放类的成员,通过指针,来访问对象的成员。
2.声明:1.指向数据成员的指针:类型说明符 类名::指针名;2.指向函数成员的指针:类型说明符 (类名::指针名)(参数表);
3.赋值:1.数据成员指针:指针名=&类名::数据成员名;2.成员函数指针:指针名=&类名::函数成员名;(若指针想要被常成员函数赋值,需要在声明时写出const关键字:类型说明符 (类名::指针名)(参数表)const;)
4.对类成员取地址时,也需要遵循访问权限,即在类外对私有成员不能取地址。
5.对类的数据成员取地址获得的其实是数据成员相对于类的地址偏移,因为类在实例化之前不会给数据成员具体地址。只有在声明对象后才会有具体的内存空间,此时将对象的起始地址+地址偏移就可以获得对象数据成员的真正地址,所以即使有指向数据成员的指针,也不能脱离对象访问数据成员。对指针指向的数据成员的访问格式:1.对象名.
类成员指针名。2.对象指针名->类成员指针名。
6.虽然成员函数不同于数据成员,无论对象是否创建,成员函数的地址都为真正地址,但是由于成员函数需要传入隐藏参数this指针,所以也不能离开对象直接用指针访问。对指针指向的函数成员访问格式:1.(对象名.
类成员指针名)(参数表)。2.(对象指针名->*类成员指针名)(参数表)

指向类的静态成员的指针
1.因为类的静态成员属于类,而不属于对象。所以可以通过普通指针访问静态成员,而不需要对象。赋值语句同非静态成员,需要加上类名限定。

注:指针赋值操作注意匹配。

动态内存分配
1.静态数组需要在声明时确定数组的大小,后续大小无法更改,应用起来十分不方便,此时可以使用动态内存根据运行过程申请适量的内存。
2.申请动态内存语句:new 数据类型(初始化参数列表);若内存申请成功,便返回一个指向内存首地址的指针。
1.对于基本数据类型,若不想分配内存后设置初值,则可以省去括号。
2.若保留括号,且不输入任何值,则用0对该对象初始化。
3.若创建的某一个类的对象,则需要根据初始化列表调用相应的构造函数。1.若存在用户定义的默认构造函数,则new T和new T()都会调用默认构造函数。2.若用户未定义默认构造函数,使用new T会调用系统生成的构造函数,使用new T()不仅会调用系统生成的构造函数,还会为对象的成员赋值0,若对象中存在内嵌对象,且内嵌对象也未定义默认构造函数,则还会为内嵌对象的成员赋值0。
3.运算符delete用于释放new申请的内存空间,格式:delete 指针名;若被删除的是对象,则会调用对象的析构函数。
注:每用一次new申请的空间必须要用一次delete来释放。使用new申请的空间,系统无法自动回收。
4.使用new运算符可以创建动态数组,声明:new 类型名 [数组长度];[]后可以带(),也可以不带()。
1.对于基本数据类型来说,动态数组不像静态数组会自动初始化,此时带上(),可以为动态数组用0初始化。注:()内不能带任何值。
2.对于对象来说,不带()初始化效果同new T,带()初始化效果同new T()。
3.二维动态数组申请:int **ptr=new int arr[m];for(int i=0;i<m;i++){p[i]=new int[n];}
5.可以用delete删除new建立的数组,声明:delete[]指针名;
注:对于动态数组的创建和删除可以将这一系列操作封装成一个动态数组类,例如创建动态数组对象时调用构造函数申请动态内存空间,删除动态数组对象时可以调用析构函数使用delete释放空间,同时也可以在成员函数中使用assert语句检查数组下标是否超过界限。
6.若自定义动态数组类,则对于动态数组中元素的访问需要通过动态数组类的对象调用成员函数来返回数组元素,若想实现直接用[]访问,需要将[]运算符重载。
7. assert语句:1.assert定义在头文件cassert中。2.assert(表达式),当表达式为false时,程序会终止,并且报告错误。3.assert只在调试模式下生效。
8.使用new声明二维数组返回的是二级指针(首地址的地址),声明三维数组返回的是三级指针(首地址的地址的地址)。对于三维数组的访问方式:
(((p+i)+j)+k) <->p[i][j][k]。

用vector创建数组对象
1.无论是静态数组还是new创建的动态数组,都难以检测下标越界的错误。而自己封装的动态数组类只能创建一种类型的动态数组。C++标准库提供了被封装的动态数组类模板vector,通过该模板省去了多余的重复操作。
2.声明:vector<元素类型>数组对象名(数组长度);
3.与new创建的动态数组不同,vector定义的数组对象的所有元素都会被初始化。1.对于基本数据类型,则所有元素默认初始化初值为0。2.如果数组元素为类的对象,则会调用类的默认构造函数初始化。3.初值可以自己指定,但是只能为所有元素指定相同初值。形式:vector<元素类型>数组对象名(数组长度,元素初值);
4.对vector数组元素的访问:数组对象名[下标表达式]
5.vector定义的数组对象可以调用成员函数size(),用于返回数组的大小。
注:vector数组对象名不表示数组的首地址,而表示的是一个数组对象。而可以通过数组对象名[下标表达式]访问数组元素是因为vector类中重载了[]运算符进行了封装。

深层复制与浅层复制
1.浅层复制:当类中未自定义拷贝构造函数时,系统会自动生成隐藏的拷贝构造函数,会将旧对象的数据成员赋给新对象的数据成员,对于普通变量,拷贝会创建新的副本。而指针变量,拷贝不会创建新的副本,只会出现俩个指针变量指向同一块内存空间的情况,例如想要拷贝数组,但实际只是将数组首地址赋值给了另一个指针变量。
2.浅层复制有一个很大弊病,就是对指针的复制,会导致俩个对象共用一块内存空间,但是程序结束时会调用析构函数对这俩个对象进行释放会导致同一块内存被释放俩次,导致运行错误。
3.深层复制:自定义拷贝构造函数,创建新变量接受指针所指对象的赋值。对于数组的复制需要创建一个新数组用于接收旧数组的值。
4.使用深层复制会对指针所指的变量进行复制,而不仅仅对指针进行复制,实现了真正的拷贝。

字符串
在c++中有俩种方式存放字符串,一种是字符型数组,另一种是string类。

字符数组存储和处理字符串
1.字符串常量在内存中的存放方式为一个字符占一字节,并且在末尾需要存放一个’\0’。
2.一个字符串常量可以表示为一个字符数组的首地址。
3.字符串常量声明:const char*p=”hello”;
1.由于指针指向的对象为常量,所以需要用到const关键字。
2.其中p用于接收字符串常量的首地址,对p进行输出可以输出整个字符串hello。
4.字符串变量声明:char array[8]={‘h’,’e’,’l’,’l’,’o’,’\0’};char array[8]=”hello”;char array[]=”hello”;
1.由于是字符串变量,可以通过数组下标改写字符串。
2.数组大小应该大于等于字符串长度+1(‘\0’)。
3.输出字符数组名也会输出整个字符串。
5. 使用字符数组进行初始化比较方便,但是无法使用cstring头文件中的处理函数,而且当字符串长度不确定时,还需要用new申请动态数组,使用完后还要delete释放。

string类
1.string类可以使用一系列字符串操作,使用起来更加方便。使用string类需要头文件string。
2.string类构造函数:1.string():默认构造函数,建立一个长度为0的串。2.string(const string&):复制构造函数。3.string(const char *s):用指针s所指向的字符串常量初始化string类的对象。
注:可以不用含参构造函数,来对string类对象进行初始化,由于string类重载了赋值运算符,所以可以用字符串常量对string对象初始化:string str=”hello”;
3.由于string类对大多数操作符进行了重载,所以可以直接用这些操作符对字符串进行操作。
4.输入字符串常量中若出现”,则需要在其前使用转义运算符\。
5.若使用键盘输入字符串,空格键会作为分隔符,导致输入中断。若希望遇到空格也不中断,直到行末结束,则需要用到getline函数。
6.getline(cin,str,’|’)表示从键盘输入字符串,遇到指定间隔符则中断,将已输入的字符串赋给string类对象,若不指定间隔符则可以实现整行全部输入。
7.字符串操作:
1.str.substr(p,n):从字符串p处截取长度为n的字符串。
2.str.find(str1):在字符串str中找到str1的位置并返回。(strchr(s1,字符):返回一个指针,指向字符在字符串中首次出现的位置。) 3.str.replace(str.find(str1),str1.length,str2) :在字符串str中找到str1的位置,并用字符串str2代替str1。原型:replace(pos,length,str):从pos起长度为length的字符串用str代替。
4.str.erase(str.find(str1),str1.length):找到str中str1的位置,并删除str1。
5.strcat(s1,s2)将字符串s2连接到s1的末尾。(用+代替)
6.strlen(s1)返回字符数组的长度,s1为数组类型。
7.strcmp(s1,s2):相同则返回0,若s1<s2,则返回值小于0。如果s1>s2则返回值大于0。