目录
5.在类中显示定义了构造函数,编译器就不会自动生成,反之亦然。
7.编译器生成默认的构造函数会对自定类型成员调用的它的默认成员函数。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
5.编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
1.内联函数
内联函数用inline关键字修饰,编译的时候编译器会在调用内联函数的地方直接展开,没有额外的栈帧开销,可以提升程序的运行效率。
内联函数是用空间换时间的做法,编译器会在调用内联函数的地方直接展开可能会让文件变大,但是增加了运行效率。
对于规模较大的函数不建议使用内联函数。
内联函数不建议声明和定义分离,因为内联函数没有函数地址,链接是会找不到函数。
2.aoto关键字
2.1 aoto关键字简介
使用aoto关键字修饰变量,会作为新的类型指示符来指示编译器,编译器会在编译时自动推导出变量的类型。
比如:
注意点:在使用auto关键字定义变量时必须要对变量初始化,auto不是一种类型的声明,只是一个类型声明时的占位符,编译器在编译的时候会替换成对应类型。
2.2 auto使用时的细节
1.auto和auto*
用auto声明指针的时候auto和auto*没什么区别,声明引用时要加上&
2.一行定义变量
用auto在一行定义多个变量时,这些变量的类型必须相同。
2.3 不能用auto关键字的场景
1. auto不能作为函数参数
2. auto不能声明数组
3.范围for循环
3.1 语法
范围for循环的参数部分由“ : ”分割为两部分。1.是范围内用来迭代的变量 2.是表示迭代的范围。
3.2 范围for循环的使用条件
范围for循环的迭代范围必须是确定的,以下就是范围不缺定导致编译器报错。
4.指针空值nullptr
C/C++编程时声明指针变量的时候会给一个合适的值,否则可能出现无法预料的错误。
但是NULL实际上是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
C++把NULL定义成0了或者是空类型指针void*
程序本来是要调用fun(int*)函数,但是NULL被定义成0。
所以C++就引入了nullptr来表示指针空值
5.类
5.1类的引入
C语言的结构体中只能定义变量,在C++中结构体可以定义变量也可以定义函数。
C++中以栈为例子:
#include<iostream>
#include<assert.h>
using namespace std;
typedef int STDataType;
struct Stack
{
void STInit(size_t capacity)
{
a = NULL;
capacity = 0;
Top = 0;
}
bool STEmpty()
{
return Top == 0;
}
void STPush(const STDataType& x)
{
if (Top == capacity)
{
int newcapacity = capacity == 0 ? 4 : capacity * 2;
STDataType* tmp = (STDataType*)realloc(a, sizeof(STDataType) * newcapacity);
if (NULL == tmp)
{
perror("realloc fail");
return;
}
a = tmp;
capacity = newcapacity;
}
a[Top] = x;
Top++;
}
void STDestroy()
{
free(a);
a = NULL;
capacity = 0;
Top = 0;
}
STDataType* a;
int Top;
int capacity;
};
int main()
{
Stack st;
st.STInit(4);
st.STPush(1);
st.STDestroy();
return 0;
}
在C++中更喜欢用class代替struct
5.2类的定义
类由class关键字定义,classname是类的名字,{}内是类的主体,括号后有一个分号
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
在类里面定义的变量称为类的属性或者成员变量,类里面定义的函数称为类的方法或者成员函数。
定义类有两种方式
1.声明和定义都在类里面:
class student
{
public:
//成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
void ShowAge()
{
cout << age << endl;
}
public:
int age;
};
2.声明和定义分离
//.h头文件中
class student
{
public:
void ShowAge();
public:
int age;
};
//.cpp文件中
//成员函数名前需要加类名::
void student::ShowAge()
{
cout << age << endl;
}
5.3类的访问限定符
类的访问限定符有public(公有)、private(私有)、protected(保护)
1.被public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)
5.4类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
class student
{
public:
void ShowAge();
public:
int age;
};
void student::ShowAge()
{
cout << age << endl;
}
5.6类的实例化
我们定义的类其实并没有占用存储空间,只是一个声明,想要使用这个类就要将类实例化
6. this指针
先写一个类age
class age
{
public:
void ShowAge()
{
cout << age << endl;
}
public:
int age;
};
int main()
{
age a1;
age a2;
a1.age = 10;
a2.age = 20;
a1.ShowAge();
a2.ShowAge();
return 0;
}
age类中有一个成员函数ShowAge,函数体中没有关于不同对象的区分,那当a1调用ShowAge函 数时,该函数是如何知道应该设置a1对象,而不是设置a2对象呢?
C++为了解决这个问题引入了this指针::C++编译器给每个“非静态的成员函数“增加了一个隐藏 的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编 译器自动完成。
6.1 this指针的特性
1.this指针的类型:类类型* const,所以不能给this指针赋值。
2. 只能在“成员函数”的内部使用
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。
4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传 递,不需要用户传递
编译器会自动传一个隐含的this指针
就像下图那样,this指针不能由用户传递
7. 类的默认6个成员函数
比如一个空类
class age{};
这个类里面并不是什么都没有,编译器会自动生成以下6个默认成员函数
1.构造函数
2.析构函数
3.拷贝构造
4.赋值重载
5.普通对象取地址重载
6.const对象取地址重载
8.构造函数
8.1概念
struct Stack
{
void STInit(size_t capacity)
{
a = NULL;
capacity = 0;
Top = 0;
}
bool STEmpty()
{
return Top == 0;
}
void STPush(const STDataType& x)
{
if (Top == capacity)
{
int newcapacity = capacity == 0 ? 4 : capacity * 2;
STDataType* tmp = (STDataType*)realloc(a, sizeof(STDataType) * newcapacity);
if (NULL == tmp)
{
perror("realloc fail");
return;
}
a = tmp;
capacity = newcapacity;
}
a[Top] = x;
Top++;
}
void STDestroy()
{
free(a);
a = NULL;
capacity = 0;
Top = 0;
}
STDataType* a;
int Top;
int capacity;
};
对于栈这个类来说每次实例化完都要去调用init()取初始化,这样做太麻烦了而且有时候会忘记初始化,所以就要用到构造函数。
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
8.2 特性
构造函数是成员函数,它的任务是初始化对象。
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
struct Stack
{
public:
Stack()
{
_a = nullptr;
_Top = _capacity = 0;
}
Stack(size_t capacity)
{
_a = nullptr;
_Top = 0;
_capacity = capacity;
}
private:
STDataType* _a;
int _Top;
int _capacity;
};
5.在类中显示定义了构造函数,编译器就不会自动生成,反之亦然。
struct Stack
{
public:
//Stack()
//{
// _a = nullptr;
// _Top = _capacity = 0;
//}
//Stack(size_t capacity)
//{
// _a = nullptr;
// _Top = 0;
// _capacity = capacity;
//}
//编译器自动生成构造函数
private:
STDataType* _a;
int _Top;
int _capacity;
};
7.编译器生成默认的构造函数会对自定类型成员调用的它的默认成员函数。
注意:C++11往后内置类型成员变量在类中声明时可以给默认值。
9.析构函数
9.1概念
析构函数的功能和构造函数是相反的,析构函数会在对象销毁时自动调用,完成对象中资源的清理工作。
9.2特性
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
struct Stack
{
public:
Stack()
{
_a = nullptr;
_size = _capacity = 0;
}
Stack(size_t capacity)
{
_a = nullptr;
_size = 0;
_capacity = capacity;
}
bool STEmpty()
{
return _size == 0;
}
void STPush(const STDataType& x)
{
if (_size == _capacity)
{
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
STDataType* tmp = (STDataType*)realloc(_a, sizeof(STDataType) * newcapacity);
if (NULL == tmp)
{
perror("realloc fail");
return;
}
_a = tmp;
_capacity = newcapacity;
}
_a[_size] = x;
_size++;
}
~Stack()
{
if (_a)
{
free(_a);
_a = NULL;
_capacity = 0;
_size = 0;
}
}
private:
STDataType* _a;
int _size;
size_t _capacity;
};
int main()
{
Stack st;
st.STPush(1);
st.STPush(2);
return 0;
}