一、头文件 源文件
头文件:变量的声明、函数的声明、类的定义
源文件:对应变量的敌营、函数定义、类成员属性的定义、类成员函数定义
为什么包含头文件而不是源文件?包含源文件会有重定义错误
为什么可以跨文件调用?连接器会把编译好的文件连起来
#pragma once
//AA.h
extern int a; //声明变量
void fun(); //函数声明
class CTest; //类的声明
class CTest { //类的定义,注意:类的定义可以重复(在不同的源文件中),所以把类的定义放在头文件中,防止混淆
public:
int m_a;
static int m_b;
const int m_c;
int& m_d;
public:
CTest(); //声明,也可以放定义
~CTest();
static void funstatic();
void funconst()const;
virtual void funvirtual();
};
//AA.cpp
#include <iostream>
using namespace std;
int a = 1;
void fun() {
cout << a << endl;
}
#include "AA.h"
//类中的成员函数在类外定义,需要在函数名前+类名作用域
int CTest::m_b = 2; //在源文件中定义及初始化,不能放在头文件中定义,会有重定义错误
CTest::CTest():m_a(1) ,m_c(3),m_d(m_a){ //常量、引用、虚函数指针、父类的构造函数 必须在构造函数的初始化列表中初始化
cout << "CTest" << endl;
}
CTest::~CTest(){
cout << "~CTest" << endl;
}
void CTest::funstatic(){ //静态函数static关键字要去掉,修饰整个函数
cout << "funstatic" << endl;
}
void CTest::funconst() const{ //常函数const关键字要保留,修饰this指针类型
cout << "funconst" << endl;
}
void CTest::funvirtual(){ //虚函数virtual关键字描述函数整体,要去掉
cout << "funvirtual" << endl;
}
//main.cpp
#include "AA.h"
#include <iostream>
using namespace std;
int main() {
fun();
CTest t;
cout << t.m_a << endl;
cout << CTest::m_b << endl;
cout << t.m_c << endl;
cout << t.m_d << endl;
CTest::funstatic();
t.funconst();
t.funvirtual();
}
头文件与源文件的差异:
头文件:单独的头文件不参与编译
源文件:每一个源文件都是自上而下独立编译
二、头文件重复包含
//A.h
//#pragma once
class A {
public:
int m_a;
A(int a);
};
//A.cpp
#include "A.h"
A::A(int a) :m_a(a){}
//B.h
//#pragma once
#include "A.h"
class B {
public:
A m_a1;
B() :m_a1(1) {}
};
//C.h
//#pragma once
#include "A.h"
class C {
public:
A m_a2;
C() :m_a2(2) {}
};
//main.cpp
#include <iostream>
using namespace std;
#include "B.h"
#include "C.h"
int main() {
B b;
cout << b.m_a1.m_a << endl;
C c;
cout << c.m_a2.m_a << endl;
}
ERROR:C2011 A::class 类型重定义
解决去重的方法:
1、#pragma once
告诉编译器,当前这个头文件在其他的源文件中,只需要包含一次
2、宏的逻辑判断
#pragma once
#ifndef __A_H__ //如果没有定义 __A_H__ 宏,可以往下走
#define __A_H__ //定义宏
class A {
public:
int m_a;
A(int a);
};
#endif // !__A_H__
对比:
1、效率不同:#gragma once 和编译器直接沟通,编译程序效率高。通过宏逻辑判断,处理大量头文件时,效率略低
2、操作难易:#gragma once 程序自带,简单。基于逻辑宏的判断,若 宏名字 重复,会导致程序问题
基于逻辑宏的判断优势: 可以做到手动局部控制部分代码是否包含
三、编译器-运行期
3.1 编译期确定 / 运行期确定
#include <iostream>
using namespace std;
int main() {
//编译期确定
#if __cplusplus
#define A 1
#else
#define A 2
#endif
int a = A;
//运行期确定
cin >> a;
if (a > 10)
cout << ">10" << endl;
else
cout << "<10" << endl;
}
3.2 编译期错误 / 运行期错误
//在编译期产生的错误
int len = 10;
//int arr[len]; //ERROR:变量len的值不可以做常量
//在运行期产生的错误
int* p = new int[len];
p[len + 1] = 1; //ERROR:数组越界
3.3 类有关的问题
char buffer[1024] = { 0 };
class A {
public:
virtual void fun() {
cout << "A" << endl;
}
};
class B :public A{
private: //访问修饰夫编译期的概念(限制作用)
void fun() {
cout << "B" << endl;
}
void fun2() {
cout << "B::fun2" << endl;
}
public:
void Getfun2() {
printf("%p\n", &B::fun2); //输出地址到控制台,不用cout因为解析不正确
sprintf(buffer, "%d\n", &B::fun2);
}
};
int main() {
A* pa = new B;
pa->fun(); //B
//在类外直接调用类成员函数
B b;
b.Getfun2();
cout << buffer << endl;
//to_string();
int a = atoi(buffer);
void(*p)() = (void(*)())a;
(*p)(); //B::fun2
}
运行期多态,访问修饰符限制在编译期,所以可以在类外调用B(private)
原因在于在运行期时,实际调用的是地址
3.4 宏-内联函数
3.4.1 宏#define
3.4.1.1 宏的特殊用法
\ :连接当前行和下一行
注意: \ 后面不能有任何字符。最后一行一般不加 \ ,以防影响后续代码
#define A 10
#define B \
for (int i=0;i<A;i++){\
cout << i << " ";\
}\
cout<<endl;
()宏 可以带参数,参数的作用也是替换。可以等效为函数
#define C(N) \
for (int i=0;i<N;i++){\
cout << i << " ";\
}\
cout<<endl;
#define Mul(a,b) a*b
int Mull(int a, int b) {
return a * b;
}
宏的参数没有校验的功能(类型的安全检查)
宏的参数只是单纯的替换,不会自动的计算和表达式的求解
cout << Mul(2, 3 + 4) << endl; //10
cout << Mull(2, 3 + 4) << endl; //14
# :将宏参数转化为字符串,相当于加上了“ ”
#define D(N) #N
void fun(char c) {
cout << "fun(char)" <<c<< endl;
}
void fun(const char* c) {
cout << "fun(char*)"<<c << endl;
}
void fun(int c) {
cout << "fun(int)" <<c<< endl;
}
fun(D(1)); //1
fun(D("ABC")); //"ABC"
#@ :将参数转化为字符,相当于加上‘ ’
##:拼接
3.4.1.2 限制宏的范围
#undef 取消宏定义
#undef A
int A = 3;
函数名与宏重名
G(1); //默认使用 宏
(G)(2); //用括号将函数名括起来,使用的是函数
3.4.2 内联函数inline
inline 建议性关键字: 建议编译器将其当成内联函数使用,而非必须。(取决于编译器)
宏 普通函数 内联函数三者区别:
1、宏发生在预处理期,普通函数和内联函数都在编译期
2、宏和内联函数效率高、普通函数效率慢
3、内联函数和宏本质是复制粘贴,会导致占用内存大(空间换时间)
注意:递归函数 虚函数 一定不能为内联函数
类、结构中在的类内部声明并定义的函数 默认为内联函数
#include <iostream>
using namespace std;
//预处理期
#define Mul(a,b) ((a)*(b))
int Mull(int a, int b) { return a * b; }
//编译期
// 短小精悍 ,代码量比较少,逻辑简单的函数适合为内联函数, 如果函数代码量比较多,大量的复制替换,导致程序占用空间增大
inline int MUL(int a, int b) { return a * b; }
int main() {
Mull(1, 2);
MUL(1, 2 + 3); //1*5
}
3.5 重载运算符
函数名:operator + 需要重载的运算符
参数:与操作符的规则保持一致(双目运算符->2个参数)
顺序:与使用场景一致
返回类型:一般是有的,目的是和后续的操作符继续操作
3.5.1 一般运算符
#include <iostream>
using namespace std;
class CTest {
public:
int m_a;
CTest():m_a(0){}
int operator=(/* CTest* const this , */int a) { //类内有隐藏的this指针,第一个参数不用写
m_a = a;
return m_a;
}
int operator+(int a) { //再类内重载操作符函数,参数顺序固定,第一个参数是隐藏的this指针,只能匹配对象
return m_a + a;
}
};
int main() {
CTest t;
t = 10;
cout << t.m_a << endl;
*(int*)&t = 20; //通过指针
cout << t.m_a << endl;
t.operator=(30);
cout << t.m_a << endl;
t = t + 40;
cout << t.m_a << endl;
}
重载操作符的含义:本质上是一个函数,告诉编译器当遇到这个符号 能够匹配 的时候,调用这个函数来完成重载操作符的功能。是对原有操作符功能的扩展、补充
int operator++() { //左++
return ++m_a;
}
int operator++(int) { //右++比左++多一个int参数,这个参数只是为了区分,无其他意义
return m_a++;
}
在类外重载
int operator+(int a, CTest& t) {
return a + t.m_a;
}
cout << 10 + t << endl; //通过类外重载实现该操作
int operator++() { //类内左++
return ++m_a;
}
int operator++(CTest& t) { //类外
return ++t.m_a;
}
//cout << ++t << endl; //ERROR:有歧义
t.operator++(); //类内左++
t.operator++(0); //类内右++
::operator++(t); //全局左++
发现有歧义
3.5.2 重载输入输出操作符
要在类外重载
ostream& operator << (ostream& os, CTest& t) {
os << t.m_a;
return os;
}
istream& operator >> (istream& is, CTest& t) {
is >> t.m_a;
return is;
}
同一个操作符,在类内重载有隐含的this指针参数,类外需要多加一个参数(类对象的引用),
类内函数参数顺序相对来说固定,在类外重载时,顺序可调 ,在类外重载操作符,参数至少要包含一个自定义类型(类、结构体)
注意: 同一个操作符 类内 和类外 如果同时存在,可能会导致使用上的歧义
重载操作符函数,参数不能有默认值,否则违背了其使用规则
对于同一个操作符来说,参数的数量不同,可能会代表不同的含义
必须在类内重载的操作符: = [] 、-> ,()
不能改变原有操作符的优先级 和结合性
不能创建新的操作符
int operator*(int a) { //乘法
return m_a * a;
}
int operator*(CTest& t) { //间接引用
return t.m_a;
}
注意:*作为乘号是双目运算符,*作为间接寻址运算符是单目运算符。而int* p时是作为一类型(整型指针)
3.5.3 对象类型转换
函数名: operator 要重载的类型,无参数,无返回类型(类似于构造-析构,并不是返回void)
返回的数据 要和 重载的类型保持一致
#include <iostream>
using namespace std;
class CTest {
public:
int m_a;
CTest() :m_a(1) {}
operator int() { //重载类型和返回类型得保持一致
return m_a;
}
//对于 重载操作符 和重载类型,同时满足调用条件,优先匹配重载操作符
int operator+(int a) {
return m_a + a;
}
};
int main() {
CTest t;
int a = 0;
a = t; //a=t.m_a
cout << a << endl;
cout << t + a << endl; //重载运算符
cout << a + t << endl; //重载类型
cout << t.operator int() << endl;
}
3.5.4 迭代器
typedef struct node {
int data;
node* next;
node(int data) {
this->data = data;
next = nullptr;
}
}Node,*PNode;
class CIterator {
public:
PNode m_pTemp;
CIterator(PNode p):m_pTemp(p){}
bool operator!=(PNode p) {
return m_pTemp != p;
}
int operator *() {
return m_pTemp->data;
}
PNode operator++() {
m_pTemp = m_pTemp->next;
return m_pTemp;
}
PNode operator++(int) {
PNode tmp;
tmp = m_pTemp;
m_pTemp = m_pTemp->next;
return tmp;
}
};
class CLinkedList {
private:
PNode header;
PNode ender;
int len;
public:
//void showList() {
// PNode p = header;
// while (p) {
// cout << p->data << ' ';
// p = p->next;
// }
// cout << endl;
//}
void showList() {
CIterator ite = header;
while (ite!=nullptr) {
cout << *ite << ' ';
ite++;
}
cout << endl;
}
};
3.5.5 list STL链表
3.5.5.1 添加(push)、弹出(pop)、遍历
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lst; //定义一个空链表
lst.push_back(2);
lst.push_back(3);
lst.push_front(1);
lst.push_front(0);
//定义迭代器对象,指向头
list<int>::iterator ite = lst.begin();
while (ite != lst.end()) { //不等于尾节点
cout << *ite << " ";
ite++;
}
cout << endl;
lst.pop_back();
lst.pop_front();
ite = lst.begin();
while (ite != lst.end()) {
cout << *ite << " ";
ite++;
}
cout << endl;
}
3.5.5.2 插入、删除
ite = ++lst.begin();
list<int>::iterator iteinsert = lst.insert(ite, 10);
cout << *iteinsert << endl;
for (int v : lst) {
cout << v << " ";
}
cout << endl;
ite = lst.begin();
//返回的是删除节点的下一节点
list<int>::iterator iteerase = lst.erase(ite++);
cout << *iteerase << ' ' << *ite << endl;
for (int v : lst) {
cout << v << " ";
}
cout << endl;
ite = ++lst.begin();
iteerase = lst.erase(ite++);
if (iteerase != lst.end()) {
cout << *iteerase << endl;
cout << *ite << endl;
}
else {
cout << "ERROR" << endl;
}
3.5.5.3 头尾节点取值
cout << *lst.begin() << ' ' << lst.front() << endl; //front() 返回头节点里的值
cout << *--lst.end() << ' ' << lst.back() << endl; //back() 返回尾节点的值
cout << lst.size() << endl; //获取链表的长度,有效节点的数量
lst.clear(); //清空链表
cout << lst.empty() << endl; //返回bool类型
3.6 代码片段
3.7 拷贝构造
3.7.1 转换构造
#include <iostream>
using namespace std;
class CTest {
public:
int m_a;
int m_b;
//转换构造函数 ,explicit 修饰构造函数,禁止构造函数发生隐式转化
/*explicit*/ CTest(int a) :m_a(a) {}
CTest(int a,int b) :m_a(a),m_b(b) {}
};
int main() {
CTest t(10);
cout << t.m_a << endl;
CTest t2 = 20; //初始化,可以做隐式转换
cout << t2.m_a << endl;
t2 = { 2,3 };
cout << t2.m_b << endl;
}
3.7.2 拷贝构造
#include <iostream>
using namespace std;
class CTest {
public:
int m_a;
int* m_p;
CTest(int a):m_a(a),m_p(new int(++a)){}
/*
拷贝构造函数 参数:const + 当前类对象的引用
函数体代码:形参对象中的成员依次给this对象中的成员做初始化
一个对象给另一个对象做初始化
空类中,编译器会默认提供拷贝构造函数,一旦我们手动重构他,编译器就不会提供默认的了
浅拷贝问题:多个对象的指针成员指向了同一个申请的卡空间,会导致同一个内存空间被回收了多次,使程序出现异常
默认的拷贝构造函数,是一个浅拷贝
深拷贝,手动重构解决浅拷贝问题。对于指针成员,需要单独申请一个属于自己的空间
避免浅拷贝问题发生,
*/
CTest(const CTest &t):m_a(t.m_a),m_p(t.m_p) { //引用传递
if (t.m_p)
m_p = new int(*t.m_p);
}
~CTest() {
if (m_p)
delete m_p;
m_p = nullptr;
}
};
void fun(CTest& t) { //避免值传递,推荐使用引用、指针
cout << "fun" << *t.m_p << endl;
}
int main() {
CTest t1(1);
CTest t2(t1);
cout << t2.m_a<<*t2.m_p << endl;
fun(t2);
}
3.7.3 operator=
#include <iostream>
using namespace std;
/*
空类中 默认存在的函数
1.默认无参构造
2.析构函数
3.拷贝构造函数
4.默认的operator=
*/
class CTest {
public:
int m_a;
int* m_p;
explicit CTest(int a):m_a(a),m_p(new int(a)){}
~CTest() {
if (m_p)
delete m_p;
m_p = nullptr;
}
//int const* operator=(int* const p)[]
/*
默认提供的函数 函数名:operator= 参数:const 当前类对象的引用,返回类型:当前类对象的引用
函数体代码:形参对象中 依次 给this中的成员做初始化
默认的operator=是个浅拷贝
*/
//CTest& operator=(const CTest& t) {
// if (this!=&t) { //如果不是自己,则赋值
// this->m_a=t.m_a;
// if (t.m_p) {
// this->m_p ? *this->m_p = *t.m_p : (this->m_p = new int(*t.m_p), 0);
// }
// else {
// if (this->m_p)
// delete this->m_p;
// this->m_p = nullptr;
// }
// }
// return *this;
//}
};
int main() {
/*
CTest t1(1);
int a = 0;
//t1=&a; //赋值,有对应匹配的函数
CTest t2(2);
t1 = t2;
cout << t2.m_a << endl;
cout << *t2.m_p << endl;
t1 = t1;
*/
CTest t(1);
t = CTest(2);
t = 3; //禁止隐式转换后,该写法不支持
return 0;
}