默认值参数
在函数声明或定义的时候,给定参数默认值,如果参数传递的时候不给该形参传值,则会按照默认值传参。
- 函数参数的默认值是在编译期生成指令的时候,直接生成入参指令,函数参数的默认值只能传递常量。
- 函数的默认值参数只能从右向左赋值,不能跳过。
- 函数的默认值参数在同一作用域中只能赋值一次,不能重复赋值。
- 因为函数参数的默认值是在编译期时期带入的,所以函数参数的默认值只在本文件中生效。
内联函数
- 在release版本中调用内联函数,该函数会在调用点展开(编译时期展开)。
- 在debug版本中调用内联函数和正常函数的调用方法一致。
- 由于内联函数在编译期展开,而编译期无法获取变量的值,递归函数的终止条件一定需要变量的值参与,所以递归函数不可能被处理为内联函数。
- inline函数只是对系统的建议,建议系统将其视为内联函数。
- inline函数在debug版本中生成的是局部符号(local),如果被处理为内联函数后在release版本不生成符号,直接在调用点展开。
符号
- 所有的数据都会生成符号
- 指令中只有函数名会处理为符号
- 全局符号(global)所有的文件只要引用声明就可以使用。
- 局部符号(local)只有本文件可见。
指针和数组名的区别
-
数组 数组名 常量 39323987
-
指针 变量
常量
在编译时期将常量的值直接写入到常量的使用点
常量的初始化必须使用常量,使用变量就会退化为常变量
const和指针
- const修饰的值不能成为左值,不能泄露常量的地址给非常量的指针
- const修饰的类型是离它最近的第一个成型的类型,其余是它修饰的内容
- 如果const修饰的内容不包括指针,则无法参与类型
引用
- 引用的底层是一个指针
- 在使用到引用的地方,编译期会自动替换成为底层指针的解引用
函数 | 是否可以展开 | 调试 | 是否有栈帧开辟 | 可见区域 | 是否生成符号 | 是否有类型安全校验 |
---|---|---|---|---|---|---|
宏函数 | 预编译时期在调用点展开 | 无法调试 | 无栈帧开辟 | 单文件可见 | 不生成符号 | 没有类型安全校验 |
static函数 | 不展开 | 可以调试 | 有栈帧开辟 | 单文件可见 | 不生成符号 | 有类型安全校验 |
内联函数 | debug版本不展开,release版本在调用点展开 | 可以调试 | debug版本有栈帧开辟,release版本没有栈帧开辟 | 单文件可见 | debug版本生成local符号,release版本不生成符号 | 有类型安全校验 |
普通函数 | 不展开 | 可以调试 | 有栈帧开辟 | 多文件可见 | 生成global符号 | 有类型安全校验 |
函数重载
函数名相同,参数列表不同。
bool compare(int a,int b);
bool compare(float a,int b);
- 函数重载是在编译时期决定究竟调用哪个函数。
- c语言函数生成符号依赖函数名
- c++生成函数符号依赖函数名与参数列表(返回值不影响)
class Person
{
public:
int _age;
int _sex;
char *_name;
Person()
{
_name=NULL;
cout<<"Person()"<<endl;
}
Person(int age)
{
cout<<"Person(int age)"<<endl;
_age=age;
_name=NULL;
}
Person(int age,int sex,const char*name)//const防止误赋值系统报错
{
cout<<"Person(int age,int sex,const char *name)"<<endl;
_age=age;
_sex=sex;
_name=new char[strlen(name)+1];
memset(_name,0,strlen(name)+1);
for(int i=0;i<strlen(name);++i);
{
_name[i]=name[i];
}
}
C与C++的互相调用
C++产生的函数符号(函数名+参数列表)
C语言产生的函数符号(函数名)
extern "C"//使用C语言的方式编译以下代码
- 不需要建立存储空间,通过使用extern关键字声明变量名而不定义它。
- extern int a 是变量的声明而不定义,其中a是可以在别的文件中再定义的
- 如果将C++的函数符号改为C的函数符号,需要改动C++的源文件error
- 添加自己实现的C++文件,写C++函数作为中间层去调用需要的C++函数,然后让自实现的C++函数产生C语言符号,之后进行C语言调用
面向对象
三大特性:封装、继承、多态。
this指针
- 指向本对象的指针
- 编译时期会自动被添加
1.普通成员方法的第一个参数,默认加上this指针
2.在普通成员方法内使用到普通成员的地方,加上this->
3.在调用普通成员方法的时候加上参数this指针Class_Name *const this
构造函数
当对象进行构造的时候默认调用的成员方法。
class Class_Name
{
public:
Class_Name();//构造函数
}
- 如果没有实现构造函数,系统会自动生成一个构造函数。
- 当自己实现了构造函数之后,系统就不会生成默认构造函数了。‘
- 默认构造函数是除了this指针以外没有参数的构造函数
析构函数
对象生存周期满了之后自动调用的成员方法。
- 析构函数如果没有自己实现,系统会默认生成一个。
- 不可重载,没有可见的参数。
- 生成的析构函数什么也不做,如果自己生成了,就不会再自动生成析构函数了。
~Person()
{
if(NULL!=_name)
{
cout<<_name<<endl;
}
cout<<"~Person()"<<endl;
if(NULL!=name)
{
delete[]_name;
}
_name=NULL;
}
拷贝构造函数
用一个已存在的对象给另一个正在生成的对象初始化时候自动调用的成员方法
- 如果没有自己实现,会自动生成一个浅拷贝的构造。
- 如果自己实现,就不会生成了。
- 注意:拷贝构造函数一定要传引用(防止死递归),拷贝构造要防止浅拷贝
等号运算符重载
- 当使用一个已存在的对象,给另一个已存在对象赋值的时候自动调用的成员方法
- 如果不自己实现,就会自动生成一个浅拷贝的等号运算符重载函数
- 如果自己实现,就不会生成了。
- 注意:防止自赋值,防止内存泄漏,防止浅拷贝
Person&operator=(const char &src)
{
cout<<"void operator=(const char &src)"<<endl;//防止自赋值
if(this==&src)
{
return *this;
}
delete[]_name;//防止内存泄漏
if(NULL!=src._name)//防止浅拷贝
{
_name=new char [strlen(src._name)+1];
memset(_name,0,strlen(src._name)+1);
for(int i=0;i<strlen(src._name);i++)
{
_name[i]=src._name[i];
}
}
else
{
_name=NULL;
}
return *this;
}
Person p1(31,1,"zhangsan");//构造函数
Person*p2=new Person();//构造函数
Person p3;//默认构造
Person p4=20;
//使用20生成临时对象
//使用临时对象拷贝构造p4
//析构临时对象
//如果有上述步骤,系统会自动直接优化成构造p4
类与类之间的关系
- 组合:一个类是一个类的一部分
- 嵌套:一个类在另一个类的内部实现,权限属性依旧生效,外界访问嵌套类需要用到外层类的作用域
成员对象:
- 构造顺序,先构造成员对象,再构造自身对象
- 析构顺序,先析构自身对象,再析构成员对象
- 如果成员对象没有默认的构造函数,就必须手动写在初始化列表
代理:
- 一个类的接口功能完全依赖于另一个类的接口功能
成员变量必须要放在成员方法的前面,否则成员方法编译时无法知道成员变量的存在。
类的编译顺序:
编译类名 编译成员名 编译嵌套类(类名、成员名) 编译成员方法体
权限
- class中默认权限是private
- public中默认权限是public
- 必须要对外界提供的,就放入public,其他的都放在private中。一般成员属性都放在private,如果外界需要使用就提供公有接口
int a;
Person p;
//初始化 在定义的时候给值,内存划分的时候给值
//赋值 在定义之后给值,内存划分完后给值
初始化列表
- 只有构造函数有初始化表
- 必须初始化的成员需要放在初始化表
- 在本对象构造之前需要完成动作,必须放在初始化列表
Person()
:_sex(1)
{
}
Person(const char *name,int age,int sex)
:_sex(sex)
{
}
Person(const Person&src)
:_sex(src._sex)
{
}
const
- const成员必须放在初始化列表
- 常对象只能调用常方法 ( 构造函数、析构函数,静态函数不影响)
- 常方法中只能调用常方法,静态函数不影响
需要写成常方法的成员
- 1.如果成员方法内不需要改动成员,并且没有对外暴露成员引用||指针,就可以直接写成常方法
- 2.如果成员方法内部不需要改动成员,但是会对外暴露成员引用||指针,就写两个成员方法(const方法和非const方法),形成重载
int &get_age()
{
return _age;
}
int get_age()const
{
return _age;
}
- 3.如果成员方法内需要改动成员,就写成普通方法
static静态成员变量
- 静态成员在一个类只有一份,无论构造多少对象,都只有一份
- 静态成员变量存储在数据段
- 必须在类外的.cpp文件中进行初始化,且只能初始化一次
- 静态成员变量的访问不依赖于对象,不依赖于this指针,使用类的作用域可以访问
int Person::_num=0;
静态成员变量的类外初始化,不需要static关键字
静态成员方法
- 静态成员方法没有this指针
- 静态成员方法的访问可以不依赖于this指针,使用类的作用域可以直接访问
- 静态成员方法内只能使用静态成员
运算符重载
给自定义类型写一个成员方法,让指定的运算符按照我们希望的逻辑进行运算
- 一元运算符 * ++ –
- 二元运算符 + - = && || <<
- 三元运算符 :?
容器
- string/vector 静态容器
- vftable 存储虚函数指针
int main()
{
vector<int>v1;
for(int i=0;i<10;i++)
{
v1.push_back();
}
return 0;
}//vi.max_size()最多能填充的数字数量