对象

对象、类、接口三者之间的联系https://blog.csdn.net/qq_41060065/article/details/90345254
父类修改---->子类也会被修改
派生类继承基类的接口和所有成员(其中私有成员也存在,只是被隐藏且不能被访问)

数据抽象

各种抽象数据类型
endl;表示一行结束并在行末加上换行符
双引号之间的正文:字符数组
cout<<"hello the world"
<<8<<"today"<<endl;
输出为hello the world8today
只有hello the world后加了\n之后才换行
\n 换行
\t 跳格
\反斜杠
\b 退格

隐藏实现

初始化与清除

1.对象是存放在栈中(栈中变量称为自动变量)或者静态存储区(内存固定块,程序开始执行以前分配)里,目的是为了:快速分配和释放(牺牲灵活性,必须知道对象的准确数量,生命期和类型)
存储和生命期可以在编写程序时(编译器决定)确定
2.创建对象在的区域动态(不需要知道对象的数量,生命周期和准确数据类型)
使用new生成新的对象,用delete释放内存
由于是动态管理(做出一般性假设),堆上分配存储需要的时间更长
存在C++第三方垃圾收集器用于销毁对象
细分为三种内存分配方式:
1.从静态存储区分配:内存在程序编译时已经分配好,内存在程序整个运行期间都存在:全局变量,静态变量
2.在栈上创建:执行函数时,函数内部局部变量的存储单元可以在栈上创建,函数执行结束时,会自动释放。
3.在堆上分配(动态内存分配):
程序运行的时候:
使用new,malloc申请任意多少的内存
使用free,delete释放内存
图片说明

函数重载与默认参数

重载函数的定义:派生类改变已经存在的基类函数的行为,即使用同一个接口函数,但是对它进行重新定义。

函数内部有形参和实参,两者区别如下:
形参:只有在被调用时才分配内存单元,在调用结束之后会释放所分配的单元。只在函数内部有效,函数调用结束返回主调函数之后不能再使用原来的形参变量
实参:函数调用之前必须有确定值(常量,变量,表达式,函数)
形参和实参的数据类型应该相同或者赋值兼容
实参对形参的数值传递是单向传递,只能由实参传递给形参

函数的分类
内部函数:对函数的调用范围只局限于本文件 static int f(int a,int b)
外部函数:再整个源文件中都有效 extern int f(int a ,int b)

变量的分类
局部变量:函数内部定义的变量,只在本函数范围内有效
全局变量:有效范围从定义变量的位置开始到该源文件结束
注意:尽可能少使用全局变量,原因如下:
1.全局变量再程序全部执行过程中都占用内存,函数未使用时也存在
2.降低函数可移植性
3.降低函数清晰性

常量

内联函数

内联成员函数:使用inline关键字将函数定义为内联函数。
对于成员函数而言,如果它是定义在类体中,即使没有使用inline关键字,该成员函数也被认定是内联成员函数。
内联函数的声明与定义:
声明内联函数使用关键字:inline
内联函数的好处与坏处:
好处在于编译器不会创建真正的函数,而只是将这个内联函数的所有代码拷贝到调用函数,这样程序在执行该函数时就不需要来回跳转,自然就提高了程序运行时的效率。
坏处就是使用内联函数在提高效率的同时,也会付出一些代价,比如说程序调用了内联函数一百次,那么就要将该函数中的代码复制一百次,无形中增大了程序的体积。
当然我们也有种折中的选择,假如函数很小的话,那么即使多次复制也不会增加多少体积,这时使用内联函数还是相当划算的。

#include<iostream>
using namespace std;
inline int Func();//声明一个内联函数,
int Func()//定义这个内联函数
{
return 1;
}
void main()
{
cout << Func();//调用内联函数
}
    在函数短小的情况下这种方法可以有效的提高速度,但是假如函数体很多代码并且需要重复调用该函数多次的话,那么不断的赋值该函数体的代码将会造成程序的增大。
    所以这个时候我们就需要将函数的声明和定义分开。 
    如果不想让某个成员函数修改成员变量的值,那么这个成员函数可以声明为const。 
#include<iostream>
using namespace std;
class A
{
public:
    void func(int x, int y){ i = x, j = y; }
    void print()const{ cout << "两数相乘为:" << i*j<<endl; }
private:
    int i;
    int j;
};
void main()
{
    A a;
    a.func(1, 2);
    a.print();
    A b;
    b.func(3, 4);
    b.print();
}

定义静态类成员

其他类成员都要通过对象来访问,不能通过类名直接访问。
静态类成员可以通过类名直接访问
定义静态数据成员时,在类体外部对静态数据成员进行初始化

class CBook{
public:
static unsigned int m_Price;
}

unsigned int CBook::m_Price=10;

int main(int argc, char* argv[]){
CBook book;
cout<<CBook::m_Price<<endl;
cout<<book.m_Price<<endl;
return 0
}

在一个类中,静态数据成员时被所有的类对象所共享的,类的静态数据成员只有一份。
如果某个对象修改了静态数据成员,其他对象的静态数据成员也将改变
静态数据成员注意要点:
1.静态数据成员可以是当前类的类型,其他数据成员只能是当前类的指针或者应用类型。
2.静态数据成员可以作为成员函数的默认函数;
类的静态成员函数只能访问类的静态数据成员,不能访问普通的数据成员;
静态成员函数不能使用const关键字
函数的实现代码处于类体之外,则在函数的实现部分不能再标识static关键字

隐藏的this指针

对于类的非静态成员,每一个对象都有自己的一份拷贝,即每个对象都有自己的数据成员,不过成员函数却是每个对象共享的。
意思就是成员函数是共享的,但是数据成员不一样,各自对应各自的对象
就是开隐藏的this指针来实现的
class CBook{
public:
int m_pages;
void outputpages(){
cout<<m_pages<<endl;
}
};
int main (int argc, char* argv[]){
CBook vbbook vcbook;
vbbook.m_pages=512;
vcbook.m_pages=570;
vbbook.outputpages();
vcbook.outputpages();
return 0;
}
其实等价于:
void outputpages(){
cout<<this->m_pages<<endl;
}
或者
void outputpages(CBook* this){
cout<<this->m_pages<<endl;
}</this-></this->

嵌套类

定义友元才能访问内部嵌套类的私有成员,而且调用需要 CList::CNode node, 其中node是对象名

局部类

定义再函数中的类,在函数之外是不能被访问的,因为局部类被封装在了函数的局部作用域中

友元

使用friend关键字可以让特定的函数或者别的类的所有成员对私有数据成员进行读写。
详细的部分还是要后续再进行补充。

命名空间

名字控制

引用和拷贝构造函数

引用

左值引用:++a,int a=0
int a;
int &ref_a=a;
a=100
则ref_a表示a的别名
引用有以下特点:
1.C++引用初始化后,不能再使用他去引用另一个对象,不能被重新约束。意思就是确定了作为A的别名,就不能再作为其他的别名使用
2.引用变量是被引用对象的别名,意思就是,你叫小王去做事和叫王二狗去做事是一样的
3.引用和指针变量的区别
(1)指针是一种数据类型,引用不是数据类型:
指针可以转化为它所指向变量的数据类型,以便赋值运算符两边的类型相匹配
引用和变量的类型必须相同,不可进行数据类型转换
(2)指针变量和引用变量都用来指向其他变量:指针变量语法复杂,引用变量的使用方法和普通变量相同
4.引用需要初始化,不然会报错

右值引用:a--
右值是临时变量
int get(){
int i=4;
return i;
}

int main(){
int &&k=get()+4
k++
cout<<"k的值"<<k<<endl;
}
输出为9

引用的功能

这里就要提到函数参数的传递方式:
函数传递的方式主要有两种:值传递、引用传递
两者的区别:
值传递:实参传递到调用函数中,调用函数中修改参数的值,但是在外面的实参的值是不会变的
引用传递:在调用函数中修改参数的值,外面的实参的值也随之改变
指针传递参数也属于一种值传递,传递的是指针变量的副本。可以使用指针的引用,达到在函数体内改变指针地址的目的。
右值引用指针暂时略过

构造函数(没有返回值)

构造函数的作用:建立函数时的初始化工作
默认构造函数、带参数的构造函数
复制构造函数

析构函数(没有返回值)

~Person
用于释放对象
注意事项:
1.一个类只能定义一个析构函数
2.析构函数是不能重载的
3.构造函数和析构函数不能使用return语句返回值,不用加上关键字void

调用环境:

1.自动变量的作用域是某个模块,当此模块被激活时,自动变量调用构造函数,当退出此模块时,调用析构函数
2.全局变量在进入main函数之前会调用构造函数,在程序终止时会调用析构函数
3.动态分配的对象在使用new关键字为对象分配内存时会调用构造函数;使用delete关键字删除对象时会调用析构函数。
4.临时变量时为支持计算,由编译器自动产生。临时变量的生存期开始和结尾会调用构造函数和析构函数。

运算符重载

运算符实际上是一个函数,重载运算符是重载函数
operator 类型名();
operator +()
注意:重载预算符各个元素之间是有顺序的

对于++,--这两种运算符

前置运算:
void operator++(){
++m_pages
}
后置运算:
void operator++(int){
++m_pages
}
在默认情况下,将一个整数赋值给一个对象是非法的,可以通过重载运算符将其变为合法的
void operator = (int page){
m_pages=page;
}

转换运算符double()

int i=10;
double d;
d=(double)i;
其实等同于d=double(i)

多重继承

一个子类从多个父类继承共有大的和受保护的成员------->多重继承
声明形式用“:”运算符,基类名标识符之间用","运算符分开
class cwaterbird: public cbird, public cfish
多重继承的构造顺序:
派生类在调用成员函数时,先在自身的作用域内寻找,如果找不到,会到基类中寻找;
当派生类继承的基类中有同名成员时,派生类中的构造顺序按照声明顺序进行调用
class ca:cb,ca
函数构造顺序是cb----->ca

动态对象创建

继承和组合

这一节介绍一下类和对象

类的声明和定义:

class 类名标志符
{
public:
数据成员的声明
函数成员的声明
private:
数据成员的声明
函数成员的声明
protected:
数据成员的声明
函数成员的声明
}

类的实现:

1.类的成员函数都定义在类体内
2.类的成员函数放在类体外,如果类成员定义在类体外,需要用到域运算符“::”,放在类体内和类体外的效果是一样的
C++一般是将函数声明放在头文件当中,将类成员函数的实现放在实现文件中

类的实现的两点说明:

1.类的数据成员需要初始化,成员函数要添加实现代码;类的数据成员不可以在类的声明中初始化
2.空类是C++中最简单的类

类成员

类的封装性
共有:public 对外可见,对内可见
私有:private 对外不可见,对内可见
保护:protected 对外不可见,对内可见,对派生类可见

继承和派生

https://blog.csdn.net/leowinbow/article/details/82380252
https://blog.csdn.net/fyf18845165207/article/details/82729085?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param
https://blog.csdn.net/hedafighter2011/article/details/7650311
继承之后的可访问性:
继承方式有三种:public,private,protected
1.共有型派生 public
2.私有型派生 private
3.保护型派生 protected

子类隐藏父类的成员函数
在子类中定义了一个和父类一样的成员函数,那么一个子类对象调用的是子类中的成员函数
如果用户想访问父类的outputname成员函数,需要显式使用父类名

多态性和虚函数

虚函数的目的是为了‘晚捆版’的灵活性,即给对象发送消息时,在程序运行时才去确定被调用的代码。主要是避免相同接口不知道使用哪个。

虚函数

为了解决子类和父类相同原型成员函数的函数调用问题--------->虚函数
虚函数允许在派生类中重新定义与基类同名的函数,可以通过基类指针引用来访问基类派生类中的同名函数

虚函数的概述:

在基类中用virtual声明成员函数为虚函数,在派生类中重新定义此函数,改变函数的功能。
在C++中,虚函数可以继承,当一个成员函数被声明成为虚函数的时候,器派生类中的同名函数都自动成为虚函数;
如果派生类没有覆盖基类的虚函数,则调用时调用基类的函数定义。
覆盖和重载的区别:重载是同一层次函数名相同;覆盖是在继承层中成员函数的函数与基函数内部的原型完全相同。

虚函数实现动态绑定

多态主要体现在虚函数上,只要有虚函数在,对象类型就会在程序运行时动态绑定。
动态绑定的实现方法:
定义一个指向基类对象的指针变量,并且使它指向同一类族中需要调用该函数的对象,通过该指针变量调用此虚函数。

#include<iostream>
using namespace std;
class CEmployee{
public:
       int m_ID;
       char m_name[128]
       char m_depart[128]

       CEmployee(){
              memset(m_name,0,128);
              memset(m_depart,0,128);
       }

       virtual void outputname(){
               cout<<"员工姓名:"<<m_name<<endl;
       }
};
class COperator:public CEmployee{
public:
       char m_password[128];

       void outputname(){
        cout<<"操作员姓名"<<m_name<<endl;
       }
};
int main(int argc, char* argv[]){
        CEmployee* pWorker= new COperator();
        strcpy(pWorker->m_name,"MR");
        pWorker->outputname();
        delete pWorker;
        return 0
}

虚函数有以下方面的限制:
(1)只有类的成员函数才能成为虚函数
(2)静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象
(3)内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的
(4)构造函数不能是虚函数,析构函数通常是虚函数

虚继承

省略

纯虚函数

包含有纯虚函数的类称为抽象类,一个抽象类至少具有一个纯虚函数
抽象类只能作为基类派生出的新子类,不能再程序中被实例化(不能说明抽象类的对象),但是可以使用指向抽象类的指针

纯虚函数是指被标明为不具体实现的虚成员函数,不具备函数的功能。
由于在基类中不能给虚函数一个有意义的定义,这时可以在基类中将它说明为纯虚函数,它的实现留给派生类去做。
纯虚函数不能被直接调用,仅起到提供一个与派生类相一致的接口作用

virtual 类型 函数名(参数列表)=0

纯虚函数不可以被继承
当基类是抽象类时,在派生函数中必须给出基类中纯虚函数的定义,或者在该类中再声明其为虚函数
只有在派生类中给出了基类中所有纯虚函数的实现,该派生类才不再成为抽象类

实现抽象类中的成员函数

抽象类通常用于作为其他类的父类,从抽象类派生的子类如果时抽象类,则子类必须实现父类中的所有纯虚函数

结构体

结构体是一种自定义数据类型。声明结构体时使用的关键字是struct,定义一种结构体的一般形式为:

struct 结构体名
{
成员表列
};
1.结构体的默认访问权限为public,而类中为private
2.结构体无法使用类模板
C++一般使用类比较多

多态性

多态性:指同样的消息被不同类型的对象接受时导致不同的行为。多态性通过联编实现。
联编:一个计算机程序自身彼此关联的过程
按照联编所进行的阶段不同,存在两种不同的联编方法:静态联编、动态联编
按照联编的时刻不同,存在两种类型的多态性:函数重载、虚函数

模板介绍

函数模板的问题,打算看另外一本书详细研究。

指针

一个整型变量占用4个字节,所以编译器会分配4个连续的个地址
读取的时候从第一个地址开始,读取4个字节数据放到CPU中,然后CPU在进行后续操作
我们就规定在一种类型后边加 * 号就来表示该类型的指针,也就是去存该类型的地址。https://zhuanlan.zhihu.com/p/97016439
而不同类型 double、int 、char 所需要的字节数是不一样的,所以只有首地址是不够的,我们还需要知道这个地址对应的类型占几个字节。
所以我们需要 double 指针类型去存 double 类型的地址,也需要 int 指针类型去存 int 类型的地址,还需要 char 指针类型去存 char 类型的地址。
为了方便,我们就规定在一种类型后边加 * 号就来表示该类型的指针,也就是去存该类型的地址。
所以 int a = 3,我们就可以 int* p = &a。
现在 p 就存了 a 的地址,同时也知道了当前存的是 int 类型的地址,也就是 4 个字节,我们就可以把 p 对应的地址中的数据正确的拿出来了。

指针的声明

int* p 声明一个整型指针
float* a,* b 声明两个浮点指针
##指针的赋值
int i=100
int* p=&i

指针的相关说明:

1.指针的变量名是p:
p=&i的意思是取变量i的地址赋值给指针变量p
p代表的是看看p格子里存储的编号所代表的格子里存的数是什么
接下如果有print("%d\n",p),输出的是地址值
注意,指针一定要赋值
2.指针变量不能直接赋值,即:
int
p
p=100
是错误的
3.不能将p当作变量使用
*p一定要获得地址才行
*
个人见解*:如果大家不好理解指针这个概念,可以理解为我要把地址存在一个变量中,它需要在前面加一个声明这个东西是个指针,它的用途是指向变量内存中存储的地址的函数。

指针运算符和地址运算符

前提是 int* p=&a
&p : p相当于变量a,则&p相当于a的地址
*&a : &a相当于取变量a的地址,则
&a相当于变量a
https://www.zhihu.com/question/24466000

两个指针的减法就是他们之间差了几个格子,其实就是定义的数据类型所占的字节数
两个指针之间的加法是没有意义的,
其实算地址的话,一个字节8位,整型int占4个字节就是32位;所有输入的数字都要转化成为二进制,之后往前面添加0,补齐32位。
指针的值一般都是16进制的,类似类似于0xa2cf23c3d

指针之间的运算就要设计大小端问题,这目前先搁置

C语言:
C语言中的数组下表从0开始吗?因为arr[n]的形式事实上等价于*(arr+n)。数组名其实就是数组的首地址,也就是数组的第一个格子的地址。作为数组中的第一个元素当然得加0。
关于数组我多说两句,数组就是一段连续的小格子。数组名代表的就是第一个小格所在编号。
因此数组名就是指针,只不过这个指针变量的值是无法改变的。
对于二维数组int [3][4] a来说,唯一的区别就是指针+1后移动过的格子数不同。

实际中的计算机可没有这么简单。这个纸带不一定是一条,有可能一条是内存,一条是硬盘上的虚拟内存。纸带还会被分成多个区域,像什么堆栈之类的,并不是每个地址都可以访问,纸带也不一定是连续的。但是你在写程序的时候操作系统帮你把内存抽象成了一条连续的纸带。很多时候我们并不关心具体的值是什么。此外有的时候这种格子编号,不一定都指的是内存中的小格子。也有可能是某个硬件设备之类的。

举个数组初始化的例子:
int d[100];
int * e;
e=&d[0]; //e保存了数组d的第一个数据的地址
for (int i=0; i<100; i++)
{
*e = 0; //把该地址中的数据赋值0
e++; //地址累加一次,也就是数组中下一个数据的地址
}
指针和其他变量一样,可以运算(一般是加减),也可以重新赋值

一维数组和指针

int a[10]
int* p
p=&a[0]
就是把a[0]元素的地址赋值给指针变量p,p指向a数组的第0号元素
所以就有p+i和a+i是a[i]的地址,(p+i)和(a+i)是p+i和a+i所指向的数组元素
(p--)相当于,先对p进行运算,再使p自减
(++p)相当于,先使p自加,再作运算
(--p)相当于,先使p自减,再作运算

二维数组和指针

int a[4][3]
int* p
p=a[0]
a代表二维数组的地址,通过指针运算符可以获取数组中的元素
1.a+n表示第n行的首地址
2.&a[0][0]看作数组0行0列的首地址(也是二维数组的首地址),&a[m][n]就是m行n列元素的地址。
3.&a[0]是第0行的首地址,当然&a[n]是第n行的首地址
4.a[0]+n表示第0行第n个元素的首地址
5.((a+n)+m)表示第n行,第m列元素
6.*(a[0]+m)表示第n行,第m列元素

字符串数组和指针

char* p;
p="helloworld";
等价于
char* p="helloworld";

函数与指针

一个函数在被编译时被分配给一个入口地址,这个函数的入口地址就称为函数的指针。
可以用一个指针变量指向函数,然后通过该指针变量调用此函数。
注意:返回值的类型是指针类型(地址),这样的函数被称为指针函数
int* a (int x,int y)
a=avg

result=(*a)(width,length)

定义函数如下:
int avg(int a, int b){
return (a+b)/2
}

指针与安全

这个结合上面的《初始化与清除》部分观看:
首先是定义一个变量----->变量值被放到内存中----->如果没有申请动态分配内存,就会被分配到栈中------->在栈中变量所属的内存大小是无法被改变的,产生和消亡与变量定义的位置和存储方式有关。
------------->申请使用动态分配内存,就会被分配到堆中------>内存的申请和销毁机制由编程者来操作
###动态内存相关

申请变量的堆内存=======申请自身指向堆
int* pl=NULL; 申请动态分配内存
pl= new int;
*p=111; 动态分配的内存存储的内容变成111的整型变量

申请变量的栈内存
int* p2;
int k; 栈中的变量
p2=&k; 分配栈内存
*p2=222; 分配内存后方可赋值

释放变量的堆内存
delete p 依照p销毁内存

内存安全
C++如何避免野指针和内存泄漏的问题
https://www.cnblogs.com/zhiranok/archive/2012/12/22/cpp_memleak.html
https://blog.csdn.net/tjj1998/article/details/79870184
https://www.zhihu.com/question/400093693
野指针的出现会造成程序崩溃 ----->如何分析呢----->1.Linux会生成coredump文件,可用gdb分析;2.Win下可以注册unexception获取调用堆栈,将错误信息写到文件中

文件流

C++系统中的IO标准类,都定义在
进行标准IO操作时使用iostream头文件,包含ios,iostream,istream,ostream
进行文件IO操作时使用fstream头文件,包含fstream,ifstream,ofstream和fstreambase
进行串IO操作时使用strstream头文件,包含strstream,istrstream,ostrstream,strstreambase,iostream

枚举常量

enum{skipws,left,right,insternal,dec,oct,hex,showbase,showpoint,uppercase,showpos,scientific,fixed,unitbuf}

流的输入、输出

#include"stdafx.h"
#include<iostream>
#include<strstream>
using namespace std;
int_tmain(int argc, _TCHAR argv[])*
{
char buff[]="12345678"
int i,j
istrstream s1(buff);
s1>>1;
istrstream s2(duff,3);
s2>>j;
cout<<i+j<<endl;
}

文件打开

绝对路径和相对路径

(1)<文件流类><文件流对象名>(<文件名>,<打开方式>)
ofstream outfile("test.txt",ios::out)
ofstream outfile("c:\test.txt",ios::out)
(2)<文件流对象名>.open(<文件名>,<打开方式>)
ifstream infile;
infile.open("test.txt",ios::out)
(3)检测一个文件是否打开成功:
void open(const char* filename, int mode, int prot=filebuf::openprot)
prot决定文件的访问方式,取值如下:
0:普通文件
1:只读文件
2:隐含文件
3:系统文件

打开文件的同时创建文件

文件读写

文件流分类:
1.声明一个输入类 ifstream ifile;
2.声明一个输出类 ofstream ofile;
3.声明一个输入,输出类 fstream iofile;

读取文本文件
fstream file("古诗.txt",ios::in);
写入
fstream file("古诗.txt",ios::out);

读取图片文件要通过二进制的方式读写文件
fstream file;
file.open("test.data",ios::binary|ios::in)
写入
fstream file;
file.open("test.data",ios::binary|ios::out)

注意:cout遇到结束符号\0就停止输出

文件的复制:

文件指针移动

在读写文件的过程中,用户可能不需要对整个文件进行读写,而是对指定位置的一段数据进行读写,通过移动文件指针来完成。

文件的错误与状态:

程序中检测TO流操作是否正常
用rdstate获得文件状态
fstream file("test.txt");
if(file.fail()){
cout<<file.rdstate<<endl;
}

文件的追加
ofstream ofile("test.txt",ios::app);

文件与流的关联和分离

删除文件

预处理

不带参数的宏定义

define 宏名 字符串

宏名定义之后,即可成为其他宏名定义中的一部分。
宏替换以串代替标识符
注意:
(1)如果在串中含有宏名,则不进行替换
(2)如果串长于一行,可以在该行末尾用一反斜杠""续行
(3)#define 命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到此源文件结束
(4)可以用#undef命令终止宏定义的作用域

带参数的宏定义

define 宏名(参数表) 字符串

注意:
(1)宏定义时参数要加括号
(2)宏扩展必须使用括号,来保护表达式中低优先级的操作符
(3)对带参数的宏的展开只是将语句中的宏名后面括号内的实参字符串代替#define命令中的形参
(4)在宏定义时,在宏名与带参数的括号之间不可加空格,否则会将空格以后的字符都作为替代字符串的一部分
(5)在带参数的宏定义中,形式参数不分配内存单元,因此不必作类型定义

宏定义的好处

(1)输入代码时可以省去许多输入操作,因为宏名要比宏体短
(2)因为宏只需要定义一次,可以多次使用,而定义的宏名往往有一定的含义,所以使用宏能增强程序的易读性和可靠性
(3)宏不会引起额外的开销,宏的代码只会在宏出现的地方展开,不会引起程序的跳转
(4)宏的参数对类型不敏感,因此不用过多考虑参数的类型问题

#include命令

使用#include命令可以将另一个源文件的全部内容包含进来,也就是将另外的文件包含到本文件之中。
使用#include命令将另一源文件嵌入带有#include的源文件时,被读入的源文件必须使用双引号或者尖括号括起来

include "studio.h"

include <studio.h>

双引号和尖括号的区别:
双引号:系统先在用户当前目录中寻找要包含的文件,若找不到,再到存放C++库函数头文件所在的目录中寻找要包含的文件。调用用户自己编写的文件的时候使用
尖括号:系统到存放C++库函数头文件所在的目录中寻找要包含的文件,调用库函数的时候使用。

条件编译

#pragma 命令

设置编译器状态,指示编译器完成一些特定的动作

#line 命令

#undef 命令

限制宏名的范围

#ifdef 命令 和 #ifndef 命令

类似id else语句,主要时判断 #ifdef后的宏替换名是否被定义

#if 命令

如果#if命令后的参数表达式为真,则编译#if到#endif之间的程序段,#endif用来表示#if段的结束,否则跳过这段程序。
#if 常数表达式
语句段
#endif

程序调试

断点调试