C++入门笔记(1)

一、基本数据类型

Bool

||或

&&且

!非

字面值

二、变量(对象)

变量提供一个具名的、可供程序操作的存储空间。变量由其数据类型决定所占空间的大小和布局方式、该空间能存储的值的范围、变量能参与的运算

初始化

声明和定义

为了支持分离式编译,C++语言将声明和定义区分开来。声明(declaration)使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义(definition)负责创建与名字关联的实体

(声明一个变量时不能初始化)

extern int i; //声明i

作用域

基本的引用和指针

可以引用指针(指针是一个对象),也可以定义指向一个指针的指针,但指针不能指向引用

命令的使用

sizeof()             获取占用字节大小    需注意:不同数据类型使用的字节大小不同,编译时就执行

可以通过sizeof(array)/sizeof(array数据类型)来获得数组长度

需注意 构建的字符串填充char数组占用个数为字节个数+1   换行符

strlen() 是一个函数,需要引入头文件<string>, 在运行时执行,获取输入长度的大小遇到NULL中止

char a[10]="you";

int b=sizeof(a);    //b=10

int c=strlen(a);    //c=3

name.length() 帮助获取到string中的有效字符个数
strlen(name)帮助检索char字符数组中的有效字符个数

sizeof(name)
char name[]={'o', 'k', '\0'}; 创建字符串
char name[]={'o', 'k'}; 创建字符数组
char name[]="ok";创建c语言字符串填充数组

cin只读到空格前的数 空格后的数会留到读取下一次输入
cin.getline(name,20);只能接受字符数组,读取完整的行内容

用strlen()前要#include <string>

用power()前要#include <math>


string也有空格断输入的问题
getline(cin,name); 成员函数
string不用关心长度

一维数组
int data[]{0,1};
二维数组
int data[][3]={ {0,1,2},{5,6,7}};
读写数组
data[0][3]=2
数组名称里越贴近名称的维度越高

遍历只针对容器

数组如需在循环里输出指定位,需要在编译时就指定数组大小,不能靠运行时输入确定大小


if(){
}
else if(){
}
else{
}

for(A, B, C)
{D}

For在第一次循环时执行A,判断条件B是否成立,B成立执行D, D完毕后执行C
 

Break跳出当前操作逻辑循环并终止循环,如果是嵌套循环,只跳出一层
Continue跳出当前操作逻辑循环并进入下次循环,continue只会跳出一层 只能在循环里用
Return函数中才能用,当前程序逻辑无需继续执行,直接移交执行权给函数

%取余

Do while

do
{循环体
}while(条件)

语法糖
简化了循环的操作流程,可以遍历数组和数组结构的数据体
应用性越强,结构复杂度越高,降低了执行效率

int DATA[]=一个数组
For(auto 名字:DATA)
{
cout<<名字<<endl;
}

三目运算符(三元运算符)
只针对两个值进行筛选
只能拿来筛选值
可用if替代,但不能替代if语句
条件?结果1:结果2
int C=A>B?A:B;
如果条件为真,返回结果1,假返回结果2

位运算
二进制在程序中编码中很难展示 框架级设计常见,产品开发中不常见
位运算主要是针对数据以二进制的方式进行运算
高效,节省内存
进行位运算时,如果要做推断校验,需先把数据转换为二进制,再进行运算
& 按位与 a&b 相同位的两个数字都为1,有一为0则为0
| 按位或 a|b 相同位数据,有一为1则为1
^ 按位异或 a^b 相同位数据,不同为1,相同为0
~ 按位取反 ~a 为1变0,为0变1
<< 左移 a<<b 将数据位向左移动,新进位用0填补,左移相当于乘以2的移动数的次方
>> 右移 a>>b 正数,将数据位向左移动,缺失位用0填补; 负数,将数据位向右移动,缺失位用1填补  正数右移相当于除以2的移动数的次方

按位运算符的优先级
1.~
2.<<,>>
3.&
4.^
5.|
6.&=,^=,|=,<<=,>>=


应用
1.快速乘除2 左移右移


2.加密解密一个数字 按位异或


3.交换AB两个变量值

A=A^B; B=A^B; A=A^B   (B=A+B-B)


4.获取二进制最后一位

按位与 A&0x1


5.快速判断奇偶数(二进制最后一位为0为偶数) 取余2速度很慢


6.位运算中的取末尾3或4位 

末尾三位A&7,四位A&0xf


7.获取二进制N位上的数
B=A>>(N-1)&0x1


8.把二进制的第N位变为0或1
看连招释放 超时置为0,一个int 32个布尔值 可以控制多个开关
B=1<<(N-1)|A改为1
B=~(1<<(N-1))&A改为0(1二进制为0001)
 

构建枚举

enum EColor

{Blue,

Red,

White,

};

使用

EColor TColor=Ecolor::Blue;

Switch给定的变量与常量进行比较,也可以switch枚举

所以switch可以转成IF,但不是所有IF都能转成switch

switch比较的是值

Switch(Ecolor)

{case Blue:

break;

case Red:

break;

case White:

brake;

default:

break;

}

函数

用来处理一系列逻辑运算和操作的整体

void 无类型,常在程序中定义返回函数类型,参数类型,指针类型等

结构体

扩展数据类型,用来更加清晰地描述事物的特性

struct结构体名称{

成员属性

};

结构体的初始化

在创建结构体对象的时候,如果给他默认赋值,

就不能使用花括号赋值     

struct a

{

int Id;

};

a lizi{1}

a lizi.Id=1

结构体的大小

数据对齐,结构体中计算数据大小时,将向最大数据类型看齐,无法对齐就构建一个最大的数据类型内存单元,成员属性排列按从大到小就行

共用体

是一种数据类型,能够存储不同的数据类型,将他们放在同一块内存中,但是只能同时使用其中的一种类型。共用体只能存储基本数据类型,如整型,浮点型,布尔型

在同一时间共用体只有一个数据有效

应用场景:解决硬件平台内存吃紧问题,共用体可以灵活的应对不同的数据类型需求,根据需求进行调整

共用体的大小:最大数据类型的大小

共用体结构

union name

{

};

共用体使用

直接用name声明变量即可

注意:如果将共用体内的变量进行赋值,后面再向其他成员变量赋值时,将会覆盖之前成员变量的值

结构体中可以放行为(函数)

结构体中的函数与普通函数编写一样,但用结构体模板里的函数时必须要有实例对象

OOP面向对象 object oriented programming

面向对象的目的主要是扩展数据类型,添加自定义类型数据

面向对象好处

易于理解(符合人类思维方式)

扩展容易(继承可以更大限度地将内容进行传承)

结构划分清晰(一类只针对一个事物处理逻辑)

对象 指具体的事物,还可以指抽象的规则、计划、事件

对象有什么  具备描述自身的状体属性,并且具备自我行为,可以管理和修改状态属性

属性就是我们定义的变量,行为就是函数体

如何构建对象  了解对象行为,概括并构建对象属性,属性可以声明成变量,行为可声明成函数

用来抽象描述对象属性行为的模板

类本质是一种数据类型,需要构建实例

类的规范:

类的声明:以数据成员的方式描述数据部分,以成员函数的方法描述公有接口

类方法定义:描述如何实现成员函数

类声明提供了类的规划蓝图,方法定义进行了细节的实现
 

类的语法

class 类名

{

成员数据

};

注意,构建类时后面需要添加分号。分号前面可以直接构建类对象(结构体 枚举)  (函数定义后面没有分号)

类里的东西的定义

对象类型 类名::东西名

{

};

类的实例化:

类名 变量名;

结构体的成员属性没有保护,类的成员属性受保护

访问修饰符(结构体也可以用)

类中创建的对象或是函数均默认为不可访问,我们如果想要其他地方可以访问需要添加访问修饰符,这体现了OOP编程封装的特性

Public:公有访问,修饰的成员属性或函数,子类和本类所有均可以访问,类的实例也可以访问

Protected:受保护,修饰的成员属性或函数,只允许子类及本类访问,类的实例无法访问

Private:私有,修饰的成员属性或函数,只允许本类内进行访问,子类无法访问,类实例无法访问

没有访问修饰符的类中的成员属性或函数,都是private

加一个public:

                    int Age;

访问权限修饰符的主要目的是为了封装的特性

构造函数(帮助类的成员属性初始化)

一种特殊的类的成员函数,没有返回类型,构造函数的名称必须与类名相同,对象被创建时会调用构造函数,我们可以显式定义构造函数,并重载更多的构造函数。如果不显示编写构造函数,系统将提供默认的无参构造函数

如果显示重写构造函数,并标注了构造函数的访问关键字非public,则此类无法被创建出对象,如果重写构造函数带参数,则创建时只能使用已有的构造函数进行创建

构造函数:主要用来在创建对象时完成成员变量初始化操作,创建对象时会默认调用构造函数,所以无需主动调用构造函数

构造函数的三个作用:

创建的对象建立一个标识符

为对象数据成员开辟内存空间

完成对象数据成员的初始化

可以通过在构造函数的定义里的作用域前面加:后面加成员属性进行初始化

重载

当两个函数的名字相同时,只要形参(类型、个数中有一个)必须不同(返回类型不同不可以),也就是说同一个函数表现不同的运算功能,我们称这种操作为函数重载

析构函数(析构函数没有参数)

在对象被释放时调用,在函数名前加~符号

(指针对象进行释放时用)

指针释放时(内存泄漏 自身指针释放成功,但里面还有没被释放)

允许在一个构造函数中编写多个构造函数,但必须是重载

父类所有的构造函数都带参,则子类必须写构造函数,并且需要明确给予父类构造函数调用,只需调用任意一个即可

构造函数的使用例子

class animal   //抽象类

{

private:

    string name;

    int age;

    string color;

public:

    animal(string name="",int age=0,string color=""):name(name),age(age),color(color)

    {}                                       //构造函数

    virtual void speak()=0;     //虚函数

}

类的继承

class cat : public animal

{

public:

    cat(string name="",int age=0,string color=""):animal(name,age,color)

    {}

    

    virtual void speak() override

    {

    cout<<""miaomiaomiao"<<endl;

    }

private:

protected:

};

内存空间

栈区

由编译器自动分配并释放,存放的是函数的参数值,局部变量等,方法调用的实参也是保存在栈区的。栈是系统数据结构,对应线程/进程是唯一的。优点快速高效,缺点是有限制,数据不灵活

堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储空闲内存空间的,自然是不连续的,而链表的遍历方向是由低地址向高地址的。Windows下,栈的大小一般小于2GB

特点:堆的大小受限于计算机系统中有效的虚拟内存,所以堆获得的空间比较灵活,也比较大,但速度相对慢一些,也容易产生内存泄漏问题

在C++中,构建自定义对象(类对象)应优先考虑使用堆空间。堆空间需要主动申请,借助关键字new进行申请,当不使用空间时需要及时释放

全局/静态存储

全局变量和静态变量时放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放

申请了就一直存在没法释放它,程序不结束一直存在,程序结束才释放

常量存储区

存放常量,不允许修改(通过非正当手段也可以修改)。随着程序的结束而消亡

找的速度快

代码区

存放代码(如函数),不允许修改(类似常量存储区),但可以执行(不同于常量存储区)

对于栈来讲,是由编译器自动管理,速度快,空间有限,无需手动控制

对于堆来讲,申请和释放工作由程序员控制,速度相对慢一些,空间大,容易产生memory leak(内存泄漏)

内存泄漏

在程序运行期间,程序员主动进行堆中的内存申请。这些内存程序不进行管理的,由申请的人进行管理,而我们在使用期间,对于不使用的内存忽略了释放,那么这些内存将会无法被回收,重复利用,导致了内存越来越少,出现了泄露

new(在堆中使用内存空间)

构造函数本质是无法被直接调用的,但可以通过和new关键字结合使用,用来在堆中申请空间存储对象,并将堆中的地址进行返回

new运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。返回地址,我们将地址存放在指针中。通过操作指针即可操作堆空间中的数据

指针

指针是一种复合数据类型,可以用来指向堆中的数据空间地址,根据构建指针的数据类型确认空间中的数据大小,指针被分配在栈中。指针本身的释放不需要我们管理,但指针指向的空间,需要我们管理,合理释放

指针本身具有双重操作

第一 指针本身有值存储了空间地址

第二 指针地址指向的空间又是一份数据

指针主要用来存储地址,这是指针的主要用途。地址中存放的就是数据

枚举也可以被构建成为指针,但是指针无法操控枚举的枚举项(不要使用)

指针的值存在了栈里,指向堆里的内存空间

int *p=new int(5665);(在堆中开辟了一个int大小的空间,向空间中存入5665这个数,并将空间的首地址进行返回,存到了p指针上)

指针的长度是32位,4个字节(编译平台)

& 按位与 取地址符 引用

&只要在变量名前,在赋值运算符后就是取地址符

*跟在指针前就是解地址符

this指针

this是一个特殊的关键字,旨在帮助我们在对象内部获得自身的引用。this不是对象的属性,不会影响对象的大小,this的作用域在类内,外部无法访问操作,只能在成员函数内使用,代表当前操作对象的指针,由编译器构造的一个隐形的操作变量

delete(释放堆中空间)(只是说这块堆里的地址可以被再利用了,并没有改变里面的值)

不要对已经释放的对象再进行操作,否则会崩溃

使用delete关键字进行指针的空间释放,需要将指针指向NULL(0)(nullptr),防止误操作。主动调用delete关键字释放自定义数据指针时,析构函数将被调用

空指针

在构建指针时,我们需要注意,如果指针不需要立刻被使用,我们可以将指针暂时指向空指针,以防止误操作

指针在使用前需要通过if来检查指针的有效性,这可以帮助编写更稳定的代码

空指针宏,用来帮助引导指针指向无效值,NULL的值为0

空指针的操作是非法的,将造成程序致命错误

野指针

指针本身存储了一个内存地址值,但是地址指向的空间已经被标记为删除回收或是访问受限

产生原因

1.构建指针时没有给予指针进行初始化(系统随机塞一个值)

2.指针释放时没有将指针指向空

野指针无法通过IF判断是否有效,所以是最头痛的问题

指针的意义

面向对象编程和面向过程编程区别在于,面向对象编程是在运行阶段进行决策,这样可以更加灵活地操控逻辑与数据进行组合,在运行阶段使用new关键字进行内存创建,使用delete进行内存释放,使得程序更加的灵活

指针的操作,指针本身存储的值是地址,而我们在大部分时间里都是在操作指针指向的空间中的值。我们在写程序时不应该忘记指针本身也是存储了值内容

重写 重载 重定义

重写:主要产生在子类和父类之间,指子类对于父类中的虚函数进行了重新的声明和定义,此过程需要保证子类的函数原型和父类的保持一致,并有关键字override标记

1.重写出现在继承关系中

2.重写父类的虚函数,如果虚函数是私有的,或是受保护的,也可以进行重写

3.重写虚函数后,可以更改函数的访问域

重载:在同一一个类内部,针对函数而言,在编写函数时出现函数名相同,参数不同(参数类型,顺序,个数) , 返回类型可以相同或是不同的两个函数,我们称之为重载

1.重载发生在同一个类中

2.不同的返回类型,函数名相同参数列表相同,不属于重载

重定义:发生在子类和父类之间,指子类中重新声明定义了和父类中完全相同的函数,而父类中的函数并不是虚函数,重定义在同一类内部是禁止的

面向对象的六个基本原则

单一职责原则:一个类只负责一项职责。 降低代码复杂度,增加可读性可维护性,只有在逻辑足够简单、类中的方法足够少时才可以在代码级别上违反单一职责原则。职责被分解为很多细粒度的职责, 程序已经写好的情况下,分解类开销大(分解意味着零散,加载变的复杂,阅读也变得复杂) , 修改类虽然违反单一职责原则,但是是个不错的选择

里氏替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能。这项原则并不是要避免多态,而是要求子类在继承父类的时候,不能与父类已经定好的契约冲突,也就是不要重定义父类已经实现的方法

LSP原则是:只要父类出现的地方子类就可以出现,而且替换成子类还不产生任何错误或异常

子类重载父类方法,方法的前置条件要比父类更宽松

子类实现父类的抽象方法时,方法的后置条件要比父类更严格

依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。它的核心是面向接口编程

依赖的关系的实现:接口传递、构造方法传递、setter方法传递

接口隔离原则:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上

接口尽量小,但是要有限度。

为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。

迪米特法则:一个对象应该对其他对象保持最少的理解

类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也就越大。迪米特法则就是为了实现低耦合

陌生的类最好不要作为局部变量的方式出现在类的内部

常量const

const修饰指针时

1.在*左侧

const int *p1=new int(0);

指针指向的地址内的对象为常量,指针本身可以修改新的地址

2.在*右侧

int const *p2=new int(0);

指针本身为常量,不可以指向其他地址,指向的地址内的对象可以被修改

3.都有

如果放在引用类型前,则限定了引用变量不可以修改原对象数据(保护实参不被形参修改)

int a=10;

const int&b=a;

命名空间

以重描述逻辑分组的机制,可以将某些标准在逻辑上属于同一个操作的生命放在同一个命名空间中,以解决名字冲突,并提高了声明的可控性,降低了命名空间污染。命名空间的主要用途是构建操作域

命名空间主要用来声明变量,函数,类,实现定义

减少使用using namespc

由于编码过程中,C++类非常的庞大,我们不可能熟悉类库的过程中操作或是覆盖了已有的函数或是对象,导致不可预知的bug,在一定程度上增加了名称冲突的可能性

using namspc std;

using std::cin;

全局命名空间,普通命名空间,嵌套命名空间,内联命名空间,未命名命名空间

1.全局命名空间

最外层的命名空间,在全局作用域中定义的名字(即在所有类,函数,及命名空间之外定义的名字),存在于文件域的最外层,也称全局域

可以使用::进行引用,由于全局作用空间是隐式的,所以没有名字,也可以省略双冒号

2.普通命名空间

通过使用关键字namespace进行构建,使用using namespace进行引入。操作时如果引入空间则可以不带空间名操作空间成员。如果没有引入,则需要包含空间名使用空间成员

extern关键字

声明全局变量

3.嵌套命名空间

在一个已存在的命名空间中再次添加命名空间,这是被允许的

调用时要从最外层向内去关联内容(加::)

4.内联命名空间

借助关键字inline(放在namespace之前)可以构建内联命名空间。主要目的是可以在调用空间内容时,省略空间名称,直接进行关联调用

内联命名空间最主要是剥离了空间的复杂层次,解决了空间污染,并且还隐藏了过深的嵌套关系

cout<<ue4::p<<endl;

内联空间注意

1.任何命名空间均可以内联,但是不建议将inline放到全局命名空间上,这将导致命名空间意义丢失,造成空间命名污染

2.内联空间中的内容,禁止和外层空间内容相同,这将导致调用的二义性

3.内联命名空间中,可以继续存放内联命名空间,调用时省略空间名称

4.内联命名空间中可以存放普通命名空间,调用时只需要省略内联空间名称,即可完成调用

5.内联命名空间只是降低了命名空间中的层级调用关系,并没有解决大问题

6.内联命名空间命名需要与上层空间命名不重叠

5.未命名命名空间

直接通过namespace构建的空间,并且没有指定空间名称,未命名空间仅在特定的文件内部有效,起作用范围不会跨越多个文件。如果未命名空间在全局作用域中,则容易产生二义性,如果嵌套在其他空间中,则可以通过外层空间名访问

未命名空间内可以继续存放命名空间

static关键字

构建静态操作函数或是静态变量的关键字,主要目的是将内容静态化。静态内容无法被主动释放,只有随着程序的结束而结束

(全局变量无法被释放,常量也无法被释放)

static可以修饰变量(常量)也可以修饰函数

面向过程的static

将全局变量之前加入static,则构建出了静态全局变量,静态全局变量构建在静态内存区域。静态全局变量只作用于当前cpp文件中。如果静态全局在头文件中声明(定义也需要在头文件中进行),则引入该头文件的cpp均具有一个静态变量(引入头文件的cpp,每个都有并且互相不干扰)

静态全局变量特点;

1.该变量在静态数据区分配内存

2.没有被初始化的静态全局变量会被程序自动初始化为0

3.静态全局变量在声明的cpp文件内可见,在文件外不可见

4.不同cpp中允许有相同名称静态全局函数

静态全局变量因为不是一个所以不会重定义,只会在声明的那个文件域里

静态全局常量

所有常量都需要声明后初始化,静态全局常量也必须要这么做

不同cpp允许有相同静态全局常量

静态局部变量

特点

1.该变量在静态存储区域分配内存

2.当程序执行到该处时被初始化,即以后的函数调用不再进行初始化

3.静态局部变量一般在声明处初始化,如果没有显式初始化,则被程序自动初始化为0

4.它始终驻留在静态内存区域,直到程序执行结束

5.静态局部变量的作用域为局部作用域

6.在不同的cpp中可以命名相同的静态局部变量

静态全局函数

出现在:头文件,空间,CPP中

注意

1.如生命在头文件中,定义必须写在头文件中。使用需要引入头文件

2.声明在cpp中,允许在不同的cpp中存在命名相同的静态全局函数(文件域不同,所以不会冲突)

3.静态全局函数只能操作全局类型数据,如全局变量,静态全局函数,全局函数,静态全局变量,不能操作级别低于自己的内容

面向对象的static

静态全局变量

在类内部定义静态变量称为类的静态成员变量,受访问修饰符约束。禁止在类声明的地方初始化,禁止在初始化参数列表中初始化,需要在CPP文件中初始化(只一次即可 ,如果声明在头文件中需要引入头文件),不初始化无法使用

静态成员变量特点:

1.在类中只有一份,不会随着对象创建而增加

2.不能在初始化参数列表中初始化(没有实例)

3.使用前必须初始化

4.禁止在类声明的地方初始化

5.存储在静态内存空间中

6.受访问修饰符限定

静态成员常量

在类内部修饰静态成员常量,则需要声明时进行赋值初始化,也可以在外部进行初始化(在cpp中初始化),cpp初始化需要加const关键字,如声明在头文件中,需要引入头文件

静态成员常量特点

1.在类中只有一份,不会随着对象创建而增加

2.不能再初始化参数列表中初始化

3.声明完成后可以一同初始化,也可以在外部进行初始化,引进未初始化使用

4.受访问修饰符限定

静态成员函数

普通成员函数前加入关键字static即为静态成员函数,调用参考静态成员变量

特点:

1.可以在头文件中直接定义,也可以在cpp中定义,static 返回类型 类名::函数名(参数列表)

2.只存在一份函数体,并且不属于任何类的实例对象,没有this指针(指向实例自身,但没有实例)

3.只能操作类的静态成员变量,无法操作类的成员变量

4.访问受访问修饰符限定

单例模式

单例模式是一种常用的软件设计模式,通过单例模式可以保证在系统中,应用该模式的类只有一个实例

懒汉模式:唯一的类实例只有在第一次被使用时才构建

饿汉模式:唯一的类实例在类装载时构建

A想获得C里的数据,通过中间变量B的单例就可以实现A和C的交互

设计要点

目的:将一个类变为对外,只有一个实例入口

注意点:

1.某个类只能有一个实例(通过类里私有的静态指针来创建类实例(饿汉模式,类装载时构建实例),通过公有的静态局部函数里的静态指针来创建类实例(懒汉模式,第一次使用时构建实例))

2.某个类实例必须由自身创建(外部创建将导致实例不唯一,无法约束外部行为)(private构造函数)

3.这个实例由某个类负责向整个系统提供(从类中调用出实例)

数组与指针

传统数组(不能动态扩容,定长,无法主动获得大小)但高效稳定(因为是一段连续地址空间)

动态数组

运算符优先级

第一级别

[]数组下标    ()圆括号    .对象成员选择    →指针成员选择

第二级别

-负号运算符    ++自增    --自减    *取值    &取地址    !逻辑非    ~按位取反    sizeof长度

动态数组

目的是为了构建数组,普通数组在构建时需要在编码阶段确定数组大小,动态数组可以将构建数组大小的任务预留到运行阶段,但是一旦数组大小确定将无法进行修改

例子

int size;

cin>>size;

int* array = new int[size];

输出数组等于一个地址,可以拿指针去接

指针等于数组第0位元素值,指针+1等于数组第1位元素值

柔性数组(不常用)

存放在结构体中最后一个成员数组,并且数组本身并没有进行大小标记,则叫柔性数组

柔性数组只能放在结构体最末端,且构建时不能描述大小

malloc动态内存申请关键字

和new类似,但new的大小不固定,malloc大小固定且只返回一个地址,没有数据类型

free(专门配合malloc)

box* b=(box*)malloc(sizeof(box)+sizeof(int)*5);   //5为柔性数组元素个数,(box*)为类型

指针数组

存放指针的数组,数组内存放的数据类型是指针变量,数组大小由指针数量决定

int* data[5]{ nullptr };

data[0]=new int(5);

delete data[0];

动态指针数组

int size;

cin>>size;

int** data=new int*[size];

data[0]=new int(6);

delete data[0];

delete[] data;

数组指针

指向数组的指针,首先这是一个指针,它指向一个数组。大小是一个指针的大小,无法使用数组指针确定数组的大小,因为它只是一个指针

    int data[2]{12,34};//数组本身有一个地址,数组的值为第一个元素的地址

    int(*pdata)[2] = &data;//创建一个指向里面有2个元素的那个数组的地址的指针

    //这个指针指向数组的地址,解引用这个指针为数组第一个元素的地址

    cout<<*(*pdata+1)<<endl;

    cout<<(*pdata)[1]<<endl;

数组本身就是一个指针,可以进行基本的加减法运算,加减的操作相当于是在做指针位置移动操作,移动大小与数组的数据类型有关

数组进行函数参数传递时,传递的是指针值(首元素的地址),所以数组无法使用for语法糖方式进行遍历

可变参数函数

在函数编写形参时,我们不确定参数的个数,需要调用者根据自己的需求填入参数,这种构造函数形参的形式,称为可变参数

可变参数分为参数相同的可变参数和参数不同的可变参数

参数类型相同的可变参数

函数中必须提供一个参数,并且可变参数符号在函数末尾。调用时,填入的可变参数类型必须和可变参数符号之前的参数类型相同

可变参数符号:...

函数内用一个指针等于第一个值的地址,这样之后就可以通过偏移指针来获取函数内部可变参数值

void funtest(int num,...);

void funtest(int num,...)

{

    int *p=&num;

    cout<<p[0]<<endl;    //1

    cout<<p[1]<<endl;     //2

    cout<<*(p+2)<<endl;  //3

    cout<<p[3]<<endl;     //4

    cout<<p[4]<<endl;    //5

    cout<<p[5]<<endl;   //无意义

}

intmain(){funtest(1,2,3,4,5);}

预编译(优先于编译前)

在预处理阶段帮助编译器进行第一次代码分析,扫描整个程序工程源代码,对其进行初步的转换,产生新的源代码,提供给程序使用,这就是预编译要做的事。预编译是文本操作,无法检查语法或逻辑。(编译器在检查语法结构,运行时态才能检查逻辑)

预编译是通过不同的指令来完成工作的

#

标注了预编译的起始点,并且#是该行的第一个字符,#符号后是指令关键字

#include

引入头文件,在指令出展开被包含的文件,方便我们进行编码中的生命操作

#define

定义了一个标识符及一个串,标识符为该宏的名称,源程序在预编译阶段会将程序中出现的所有宏用其定义的串进行替换,称为宏替换

结构

#define 宏名 字符串

#define 宏名(参数表) 宏体   //()和宏名之间不能有空格

例子

#define SUM(A,B) (A+B)

#undef

取消一个已定义的宏

宏一般使用大写字母进行声明,宏属于纯粹的文本替换,不会进行逻辑检查,所以在使用过程中需要万分注意,尤其是运算符优先级,可能导致奇怪的问题

宏操作技巧

1.加\换行

2.#输出    结果将使用"输出"

#define SIGN(X) #X    //输出结果“X”

3.借助##可以拼接

#define MES(A,B) A##B   //拼接结果AB

4.使用#@可以构建单引号操作

一个char字节

宏用来做什么

1.定义数据"常量"(实际是变量),但是不如使用const关键字声明的常量,因为宏本身不参与语法检查

#define NUM 10

2.进行简单的逻辑代码替换,方便我们复用代码,简化代码编写量

3.用来构建简单的逻辑函数。目的降低函数调用开销。但是会带来代码的膨胀,阅读性下降

4.简化复杂的数据类型编写,比如指针

5.批量生成逻辑代码

问题

宏本身不是逻辑单元,是文本操作,不参与运行时态

#if              #elif               #else                 #endif

逻辑条件指令,可以根据给出的条件对程序代码有选择性地进行筛选组合,方便构建不同操作逻辑的工程,例如针对平台代码

注意:

1.处理逻辑中,所有的条件参考内容,需要使用预处理指令构建的参考数据(例如宏)

2.#endif指令标记了逻辑的结束

3.#if标记了逻辑的开始

4.此逻辑不会出现在程序运行阶段

5.表达式支持逻辑关系运算符,逻辑运算符(或且非)

#ifdef         #ifndef      #endif

用来检查某些宏是否被定义,一般用来防止宏或是头文件重复引入(新版本C++使用了#pragma once)

常用来检查宏是否重复

例子:

#ifndef 宏名

#define 宏

#endif

__LINE__显示当前指令所在行数

__FILE__显示当前指令所在文件位置

#error

一般用来做逻辑预处理时提供错误指示,当编译时提供错误信息

#if DEBUG <=1

#error 代码错误

#endif

#pragma commond

该指令用来设定编译器的状态或是指示编译器完成一些特定的动作,它有许多不同的参数

1.#pragma once

保证头文件被包含一次,可以有效防止重复包含头文件问题,在头文件中,可能会出现相互引入,加入此关键字可以有效解决相互引入时多次被引入问题

2.#pragma message

可以在编译阶段打印信息到输出控制台,方便做编译逻辑检查。格式如下

#pragma message("信息")

多继承

多继承是C++在面向对象中使用的一种手段,即一个子类可以有多个父类,并且继承多个父类的特性

我们将多继承看作是继承的扩展,父类和每个子类之间依旧可以看作是单继承,但子类和父类之间就不一定是单继承

例子

class C : public A, public B{};

构建对象时,先执行父类的构造函数,执行顺序按继承顺序执行

析构函数执行先执行子类析构,再按继承顺序反向执行父类析构

继承关系中的指针转换

多继承中需要注意指针对齐方式,对象模型的构建由继承层次关系决定,但是作为指针进行转换时,如果转换到第二继承位类型,则指针地址会做偏移操作,偏移长度为继承位第一位基类的长度,关系以此类推。如果转换为第一继承位的类型时,指针不发生任何偏移

内联函数(高频调用中才使用)

在程序执行过程中,函数的调用时存在时间开销的(参数压栈,查询函数),为了降低函数调用的开销,加入了内联函数

内联函数的目的是为了提高程序的执行效率,用关键字inline进行函数修饰,在调用时把程序代码展开,减少函数调用时的开销。使用内联函数后,编译器可以通过上下文相关的优化技术对结果代码进行深度优化

构建内联函数

1.函数声明前加上关键字inline

2.函数定义前加上关键字inline

3.类内成员函数

满足一条就可

一般建议内联函数的声明和定义在一起,过于复杂的函数编译器会自动修改内联函数为普通函数,定义在类内的函数自动成为内联函数

问题

内联函数本身在做代码拷贝,容易造成代码膨胀,如果函数体内的逻辑操作时间比函数调用时间长,则不建议使用内联函数

不宜使用内联函数的情况

1.函数体内的代码较长,使用内联函数将导致代码膨胀过大

2.函数体内出现循环,或是其他复杂的控制结构,那么执行函数体内代码的将比函数调用的开销大得多,因此内联的意义不大

3.深度调用函数不建议使用内联,例如递归函数

4.对内联函数不能进行异常接口声明

内联函数VS宏

1.宏本身也可以做到提高代码执行效率(编译阶段操作不占用执行内存),我们可以借助宏编写精简函数

2.宏的缺点在于预处理器在拷贝宏代码时会产生边界效应,例如运算符优先级问题,展开无逻辑问题等

3.宏本身不是代码,无法参与逻辑调试(无法逻辑断点)

4.宏本身无法操作类私有成员(宏不能访问类成员)

5.宏参数不进行类型检查

6.内联函数增加了类型安全检查,可以操作类成员

友元

友元出现在C++中,在类存在后,旨在用于将类内数据进行共享

友元:非类的成员函数,可以访问类的所有成员函数或是成员变量(不受访问修饰符约束),这种操作方式称为友元操作

友元分为友元类和友元函数(全局友元函数,成员友元函数)

前向声明

声明一个类,并不定义它,可以在其他的地方去定义类的结构,这种声明方式我们称之为前向声明

class 类名;

注意:

前向声明在声明之后,定义之前,该类是不完整类型,无法直接操作使用,不完整类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数

友元函数

关键字friend用于声明友元函数,友元函数可分为全局友元和成员友元。设计目的相同,只要声明为友元函数,则在函数内均可访问目标类的任何成员

全局友元函数

注意:由于友元函数无法获取类实例的this指针(友元函数不是类的成员函数),所以如果需要操作类数据则需要通过参数传入类的引用或是指针。如无参数,自行构建类对象也可以用于访问任何数据

某类的一个成员函数是另一个类的友元函数

如果函数需要另一个类作为参数,则需要使用前向声明进行参数构建。如果不需要参数,则可以留空

注意:

友元不属于类的成员函数,所以不受访问修饰符的约束(可放到任意位置)

友元函数是否添加参数

1.要访问非static成员时,需要对象做参数(引用或是指针)

2.访问static成员或是全局变量,则不需要对象做参数

3.如果使用的是全局对象,则不需要对象做参数

友元类

A类中包含友元类B,则A类可以访问到B类的所有数据,但B类不可以

如果想B类也可以访问到A类的所有数据,那么则需要在B类中声明A友元类

友元总结

友元本身破坏了面向对象的封装特性(可以访问私有内容)。但是友元又只是小范围的开放数据,共享数据。只要使用得当就可以帮我们解决大问题。例如在不开放数组的成员情况下,又可以和某一函数或是类共享数据,这就是友元的优点。在运算符重载中(两个无关的数据进行运算),友元起到了至关重要的作用

除了运算符重载,如无意外,尽量不要使用友元     破坏封装特性会降低程序稳定性

猜你喜欢

转载自blog.csdn.net/hoppingg/article/details/124795670
今日推荐