c++基本语法
(一)、基本概念
-
面向对象:其实质是从对象的角度出发
-
面向过程:时间维度
面向对象:事物维度
-
什么是面向对象?
- 我们就所有对象分为一个个的类
- 类是定义了对象的属性和操作
- 对象至少属于某一个类
- 类与类之间可以继承等关系
- 对象可以包含对象,告诉对象做什么(而不是how to do?)
-
计算机学科与自然学科的不同?
- 自然学科:先有对象,再有类
- 计算机学科:现有类,再有对象(人为的学科)
-
松耦合:可替换
-
封装(interface)
好处:
- 便于交流(交流外部)
- 保护内部(隐藏内部)
实例:将数据用private修饰,用getter,setter修饰
-
当一个类没有归到特定的
namespace
时,它将归属于一个default namespace
-
::
:解析符(可以看做是一个域的限制)如:
cat::say()
表示cat中的say函数 -
抽象(abstract)
- 从某一个层次去看
- 忽略细节
-
最近原则:本地变量的优先级高于全局变量
-
字段(field):类中的成员变量
-
c++复杂的体现
- 三种内存模型
- 堆栈(局部)
- 堆(动态)
- 全局变量空间
- 三种内存访问方式
- 直接
- 指针
- 引用
- 三种内存模型
-
所有oop语言都是默认动态绑定的,只有c++是静态绑定的:为了效率
-
相对关系的名词
- 声明(declaration)和定义(definition)
- 初始化(initialization)和赋值(assignment)
-
一个类下应该自己重写的函数
- 构造函数
- virtual析构函数
- 拷贝构造函数
-
全局变量初始化
- 对于同一个文件来说是有序的
- 对于不同文件来说是无序的
-
assert:如果一个指令是错误的,则退出
-
在计算机中set为置1,reset为置0
(二)、编译过程
源文件
为了使中间文件变得简单,这里不用iostream头文件
-
main.cpp
#include "TicketMachine.h" //在main函数中以双引号引用 int main() { TicketMachine ticketMachine; ticketMachine.showBalance(); return 0; }
-
TicketMachine.h
// // Created by Arno_vc on 2022/3/1. // #ifndef TICKETMACHINE_TICKETMACHINE_H #define TICKETMACHINE_TICKETMACHINE_H class TicketMachine { private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); }; #endif //TICKETMACHINE_TICKETMACHINE_H
-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" void TicketMachine::showBalance() { //不需要重新构建一个类 } TicketMachine::TicketMachine() { }
尝试编译并生成中间文件
使用
g++ main.cpp -saves-temp
保存中间文件
-
预编译
对应文件:TicketMachine.ii
# 1 "TicketMachine.cpp" # 1 "<built-in>" # 1 "<command-line>" # 1 "TicketMachine.cpp" # 1 "TicketMachine.h" 1 # 10 "TicketMachine.h" class TicketMachine { private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); }; # 6 "TicketMachine.cpp" 2 using namespace std; void TicketMachine::showBalance() { } TicketMachine::TicketMachine() { }
程序:预处理器
作用:将TicketMachine.h文件插入TicketMakchine.cpp文件
-
编译
对应文件:TicketMachine.o
.file "TicketMachine.cpp" .text .align 2 .globl _ZN13TicketMachine11showBalanceEv .def _ZN13TicketMachine11showBalanceEv; .scl 2; .type 32; .endef .seh_proc _ZN13TicketMachine11showBalanceEv _ZN13TicketMachine11showBalanceEv: .LFB0: pushq %rbp .seh_pushreg %rbp movq %rsp, %rbp .seh_setframe %rbp, 0 .seh_endprologue movq %rcx, 16(%rbp) nop popq %rbp ret .seh_endproc .align 2 .globl _ZN13TicketMachineC2Ev .def _ZN13TicketMachineC2Ev; .scl 2; .type 32; .endef .seh_proc _ZN13TicketMachineC2Ev _ZN13TicketMachineC2Ev: .LFB2: pushq %rbp .seh_pushreg %rbp movq %rsp, %rbp .seh_setframe %rbp, 0 .seh_endprologue movq %rcx, 16(%rbp) movq 16(%rbp), %rax movl $0, (%rax) nop popq %rbp ret .seh_endproc .globl _ZN13TicketMachineC1Ev .def _ZN13TicketMachineC1Ev; .scl 2; .type 32; .endef .set _ZN13TicketMachineC1Ev,_ZN13TicketMachineC2Ev .ident "GCC: (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0"
软件:编译器
作用:将c++代码编译为更底层的汇编语句文件
注:
- 编译是以一个.cpp文件为单位进行编译的
-
汇编
对应文件:TicketMachine.o(二进制)
软件:汇编器
作用:翻译为最底层的机器码
-
链接
对应文件:main.exe(windows下)
软件:链接器(ld)
作用:将多个单元文件(.o)与库文件整合为可执行文件
注:对于头文件的处理(.h)尽在预编译阶段,但是与之对应的.cpp文件却可以是在链接阶段.换句话说,我们不是根据.h文件与对应的.cpp文件没有必然的联系
示例:
-
a.h
void f(int i,int j=0);
-
a.cpp
#include "a.h" #include <iostream> using namespace std; void f(int i,int j){ cout << i << " " << j << endl; }
-
main.cpp
void f(int i,int j=10); int main(){ f(10); for(;;); return 0; }
-
结果:10 10
-
结论:
- 尽管main.cpp中没有a.h,最后通过链接(
g++ -o test main.cpp a.cpp
)使用a.cpp中的f()
定义 - 单元a中尽管用的是a的头文件,最后使用的却是main中的f声明(输出结果为10 10而不是10 0),说明传入的默认参数是在编译时确定的,而不是运行时(具体说来,函数还是只需要两个参数,而这两个参数在编译时确定了。不是说多了两个函数,否则结果应该是0 10)
- 尽管main.cpp中没有a.h,最后通过链接(
-
(三)、定义与声明
-
申明(declaration)与定义(definition)
-
c++所有声明都会添加一个下划线:如
global
经过编译后变成了_global
-
声明的几种方式
- extern变量
- 函数声明
- 类与结构体的声明
-
extern关键字:表明该变量在其它模块(相当于在当前模块留下了一个"洞")
-
TicketMachine.h
// // Created by Arno_vc on 2022/3/1. // #ifndef TICKETMACHINE_TICKETMACHINE_H #define TICKETMACHINE_TICKETMACHINE_H extern int temp; //对外声明 class TicketMachine { //类声明 private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); }; #endif //TICKETMACHINE_TICKETMACHINE_H
-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" #include <iostream> //在cpp下加额外需要用的头文件 using namespace std; //需要加标准命名空间 int temp; //定义 void TicketMachine::showBalance() { //不需要重新构建一个类 cout << balance << endl; cout << "temp:" << temp << endl; } TicketMachine::TicketMachine() { }
为什么不能在.h文件放普遍变量定义(如
int temp=9
)?.h文件本身就是放声明的例外:
const int temp=9
,因为常量本身就是全局的 -
(四)、oop三大特性
封装
继承
- 目的:实现软件重用(还包括其它方式,如对象组合)
多态(Polymorphism)
-
基本实现
- 上型
- 动态绑定:在运行时确定调用哪个函数(搭配virtual关键字使用)
-
c++有重载但没有覆写(即子类的函数会使父类的同名参数无效化) => c++可以通过virtual实现覆写
-
上型:父类属性是子类属性的子集,故子类对象可以作为父类对象调用
内存空间:父类属性在前,子类属性在后,子类与父类的公共函数分开存放
-
强制转换和造型
- 强制转换:改变了内存数据
- 造型:改变看待一个对象的角度(从子类的角度=>从父类的角度)
(五)、命名空间
-
类全名:
std::ostream
即std命名空间下的类
二、类与头文件
(一)、基本格式
-
新建一个TicketMachine类
-
.h文件
// // Created by Arno_vc on 2022/3/1. // #ifndef TICKETMACHINE_TICKETMACHINE_H //.h文件不需要#include自己 #define TICKETMACHINE_TICKETMACHINE_H class TicketMachine { private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); }; #endif //TICKETMACHINE_TICKETMACHINE_H
-
.cpp文件
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" #include <iostream> //在cpp下加额外需要用的头文件 using namespace std; //需要加标准命名空间 void TicketMachine::showBalance() { //不需要重新构建一个类,直接定义 cout << balance << endl; } TicketMachine::TicketMachine() { }
-
main文件
#include <iostream> #include "TicketMachine.h" //在main函数中以双引号引用 int main() { TicketMachine ticketMachine; ticketMachine.showBalance(); return 0; }
-
-
头文件形式
#incldue "xx.h"
:在当前目录找头文件#include <xx.h>
:在系统指定目录找头文件#include <xx>
:same as#include <xx.h>
-
#ifndf
与#define
#ifndf XXX
:如果没有XXX#define XXX
:那么定义XXX,知道后面的#endif
为止
尝试修改:
-
在.h文件添加
#define
-
.h文件
// // Created by Arno_vc on 2022/3/1. // #define TICKETMACHINE_TICKETMACHINE_H #ifndef TICKETMACHINE_TICKETMACHINE_H #define TICKETMACHINE_TICKETMACHINE_H extern int temp; class TicketMachine { private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); }; #endif //TICKETMACHINE_TICKETMACHINE_H
-
.cpp文件
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" //#include <iostream> //在cpp下加额外需要用的头文件 using namespace std; //需要加标准命名空间 int temp; void TicketMachine::showBalance() { //不需要重新构建一个类 cout << balance << endl; cout << "temp:" << temp << endl; } TicketMachine::TicketMachine() { }
-
生成.ii
# 1 "TicketMachine.cpp" # 1 "<built-in>" # 1 "<command-line>" # 1 "TicketMachine.cpp" # 1 "TicketMachine.h" 1 # 6 "TicketMachine.cpp" 2 using namespace std; int temp; void TicketMachine::showBalance() { cout << balance << endl; cout << "temp:" << temp << endl; } TicketMachine::TicketMachine() { }
-
注:可以看到没有类定义
-
-
在.cpp文件添加
#define
-
.h文件
// // Created by Arno_vc on 2022/3/1. // #ifndef TICKETMACHINE_TICKETMACHINE_H #define TICKETMACHINE_TICKETMACHINE_H extern int temp; class TicketMachine { private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); }; #endif //TICKETMACHINE_TICKETMACHINE_H
-
.cpp文件
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" //#include <iostream> //在cpp下加额外需要用的头文件 using namespace std; //需要加标准命名空间 int temp; void TicketMachine::showBalance() { //不需要重新构建一个类 cout << balance << endl; cout << "temp:" << temp << endl; } TicketMachine::TicketMachine() { }
-
生成ii
# 1 "TicketMachine.cpp" # 1 "<built-in>" # 1 "<command-line>" # 1 "TicketMachine.cpp" # 1 "TicketMachine.h" 1 # 7 "TicketMachine.cpp" 2 using namespace std; int temp; void TicketMachine::showBalance() { cout << balance << endl; cout << "temp:" << temp << endl; } TicketMachine::TicketMachine() { }
-
注:可以看到没有类定义
-
-
总结:由于.cpp文件的预编译是自上而下读取的,那么相同头文件时,就不会再读取,避免了出现重复定义等错误(需要注意到.cpp文件间的相互引用可以是比较复杂的关系)
例:main.cpp引用了TicketMachine2.h和TicketMachine2.h,而TicketMachine2.h中实际也引用了TicketMachine.h
-
main.cpp
#include <iostream> #include "TicketMachine.h" //在main函数中以双引号引用 #include "TicketMachine2.h" int main() { TicketMachine ticketMachine; ticketMachine.showBalance(); return 0; }
-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #ifndef TICKETMACHINE_TICKETMACHINE_H #define TICKETMACHINE_TICKETMACHINE_H extern int temp; class TicketMachine { private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); }; #endif //TICKETMACHINE_TICKETMACHINE_H
-
TicketMachine2.cpp
// // Created by Arno_vc on 2022/3/2. // #ifndef TICKETMACHINE_TICKETMACHINE2_H #define TICKETMACHINE_TICKETMACHINE2_H #include "TicketMachine.h" class TicketMachine2 { }; #endif //TICKETMACHINE_TICKETMACHINE2_H
结果:可以正常运行
对照(删去
#ifndef
):编译失败,且在main.ii文件下可以看到TicketMachine的重复引入main.ii
# 1 "main.cpp" # 1 "<built-in>" # 1 "<command-line>" # 1 "main.cpp" # 1 "TicketMachine.h" 1 extern int temp; class TicketMachine { private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); }; # 3 "main.cpp" 2 # 1 "TicketMachine2.h" 1 # 1 "TicketMachine.h" 1 extern int temp; class TicketMachine { private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); }; # 9 "TicketMachine2.h" 2 class TicketMachine2 { }; # 4 "main.cpp" 2 int main() { TicketMachine ticketMachine; ticketMachine.showBalance(); return 0; }
-
(二)、类中的字段与函数
-
类中的字段也是声明的一部分(声明表示有这么个东西,定义表示这个东西在哪里)
-
字段是属于对象的,函数是属于类的
类的函数调用的参数就是类本身
-
main.cpp
#include <iostream> #include "TicketMachine.h" //在main函数中以双引号引用 using namespace std; int main() { TicketMachine ticketMachine; printf("&ticketMachine.balance:%p\n",&(ticketMachine.balance)); ticketMachine.showBalance(); return 0; }
-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" #include <stdio.h> #include <iostream> //在cpp下加额外需要用的头文件 using namespace std; //需要加标准命名空间 void TicketMachine::showBalance() { //不需要重新构建一个类 cout << balance << endl; printf("&balance:%p\n",&balance); //%p输出内存地址 } TicketMachine::TicketMachine() { }
-
运行结果
&ticketMachine.balance:0000006c383ff748 0 &balance:0000006c383ff748
深入理解:每个类的函数都有this这个本地变量,作为指针指向调用函数的对象
转换成c代码思考:
f(&ticketMachine)
-
-
this:类的成员变量,作为指针指向调用函数的对象
(三)、构造与析构函数
-
构造函数的特点
- 构造函数的函数名就是类名
- 构造函数无返回值
-
对象的初始化
- 空间是在进入
{...}
后开始分配的 - 调用构造函数是在执行到相应语句
- 空间是在进入
-
default constructor与auto default constructor
- default constructor:没有参数的构造函数
- auto default constructor:有编译器自动生成的没有参数的构造函数
-
析构函数
格式:
~ClassName(){}
使用的两种情况:
- 被动释放:生命周期结束(以{}分隔)
- 主动释放:delete关键字
三、动态内存分配
-
new
本质;运算符(返回值为地址)
作用:
- 分配内存
- 调用构造函数
-
delete
作用:
- 调用析构函数
- 收回内存
-
对应的堆中的内存分配
-
delete []className
:调用一组析构(与new className[]对应)-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" #include <stdio.h> #include <iostream> //在cpp下加额外需要用的头文件 using namespace std; //需要加标准命名空间 void TicketMachine::showBalance() { //不需要重新构建一个类 cout << balance << endl; printf("&balance:%p\n",&balance); //%p输出内存地址 } TicketMachine::TicketMachine() { } void TicketMachine::setBalance(int balance) { this->balance = balance; //使用this关键字 } TicketMachine::~TicketMachine() { cout << "balance:" << balance << endl; }
-
main.cpp
#include <iostream> #include "TicketMachine.h" //在main函数中以双引号引用 using namespace std; int main() { TicketMachine *ticketMachine = new TicketMachine[10]; //注意这里变量类型为指针 for (int i = 0; i < 10; ++i) { ticketMachine[i].setBalance(i); } delete [] ticketMachine; return 0; }
-
结果
balance:9 balance:8 balance:7 balance:6 balance:5 balance:4 balance:3 balance:2 balance:1 balance:0
逆序释放空间
-
四、访问属性
-
常用修饰符
- private
- public
- protected:自身及子孙可以访问
-
private等修饰符实际上只在编译时刻起作用
-
main.cpp
#include <iostream> #include "TicketMachine.h" //在main函数中以双引号引用 #include "TicketMachine2.h" using namespace std; int main() { TicketMachine *ticketMachine = new TicketMachine(); //注意这里变量类型为指针 TicketMachine *ticketMachine1 = new TicketMachine(); ticketMachine1->setBalance(10); cout << ticketMachine->getBalance(ticketMachine1); return 0; }
-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" #include "TicketMachine2.h" #include <stdio.h> #include <iostream> //在cpp下加额外需要用的头文件 using namespace std; //需要加标准命名空间 void TicketMachine::showBalance() { //不需要重新构建一个类 cout << balance << endl; printf("&balance:%p\n",&balance); //%p输出内存地址 } TicketMachine::TicketMachine() { } void TicketMachine::setBalance(int balance) { this->balance = balance; //使用this关键字 } int TicketMachine::getBalance(TicketMachine *ticketMachine) { return ticketMachine->balance; } int TicketMachine::getBalance2(TicketMachine2 *ticketMachine2) { return ticketMachine2->balance; //无法用一个对象直接访问另一个对象的私有变量,编译都通不过 } TicketMachine::~TicketMachine() { cout << "balance:" << balance << endl; }
-
结论
- 在运行时,一个对象可以读取另一个同类对象的private属性(因为oop的访问修饰符特性只在编译时体现)
- 在运行时,一个对象不可读取另一个非同类对象的private属性(在编译时就无法通过)
-
-
friends(友元):友元函数可以访问类的private属性
-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #ifndef TICKETMACHINE_TICKETMACHINE_H #define TICKETMACHINE_TICKETMACHINE_H #include "TicketMachine2.h" class TicketMachine { private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); void setBalance(int balance); int getBalance(TicketMachine *ticketMachine); int getBalance2(TicketMachine2 *ticketMachine2); friend void printBalance(TicketMachine ticketMachine); //友元函数 ~TicketMachine(); }; #endif //TICKETMACHINE_TICKETMACHINE_H
-
main.cpp
#include <iostream> #include "TicketMachine.h" //在main函数中以双引号引用 #include "TicketMachine2.h" using namespace std; void printBalance(TicketMachine ticketMachine){ cout << "printBalance:" << ticketMachine.balance << endl; //是友元函数,可以直接访问private属性 } /* void printBalance2(TicketMachine ticketMachine){ cout << "printBalance2:" << ticketMachine.balance << endl; //不是友元函数,无法直接访问private属性 } */ int main() { TicketMachine *ticketMachine = new TicketMachine(); //注意这里变量类型为指针 ticketMachine->setBalance(10); printBalance(*ticketMachine); //printBalance2(*ticketMachine); return 0; }
-
结论正确
-
-
class与struct的区别:
- class的属性默认修饰符为private
- struct的属性默认修饰符为public
测试:
-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #ifndef TICKETMACHINE_TICKETMACHINE_H #define TICKETMACHINE_TICKETMACHINE_H #include "TicketMachine2.h" class TicketMachine { int temp; //无访问修饰符 private: const int PRICE=0; int balance; int total; public: TicketMachine(); void showPrompt(); void getMoney(); void printTicket(); void showBalance(); void printError(); void setBalance(int balance); int getBalance(TicketMachine *ticketMachine); int getBalance2(TicketMachine2 *ticketMachine2); ~TicketMachine(); }; #endif //TICKETMACHINE_TICKETMACHINE_H
-
main.cpp
#include <iostream> #include "TicketMachine.h" //在main函数中以双引号引用 #include "TicketMachine2.h" using namespace std; struct Node{ int a; }; int main() { TicketMachine *ticketMachine = new TicketMachine(); //注意这里变量类型为指针 ticketMachine->setBalance(10); printBalance(*ticketMachine); //printBalance2(*ticketMachine); cout << ticketMachine.temp << endl; //私有默认属性,无法直接访问 struct Node b; cout << b.a << endl; //公有默认属性,可以直接访问 return 0; }
五、初始化
(一)、初始化参数列表
-
使用初始化参数列表
-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" #include <stdio.h> #include <iostream> //在cpp下加额外需要用的头文件 using namespace std; //需要加标准命名空间 TicketMachine::TicketMachine(int balance, string name):balance(balance), account(name){ //使用初始化参数表,可以直接调用Account(string name) } TicketMachine::TicketMachine(int balance,int total,string name){ //使用构造函数初始化,对应的Account必须要有default Account,否早accout无法完成初始化 this->balance = balance; this->total = total; this->account.setName(name); } void TicketMachine::setBalance(int balance) { this->balance = balance; //使用this关键字 } int TicketMachine::getBalance() { return this->balance; } void TicketMachine::show(){ cout << "balance:" << balance << " total:" << total << " name:" << account.getName() << endl; }; TicketMachine::~TicketMachine() { }
-
Acount.cpp
// // Created by Arno_vc on 2022/3/3. // #include "Account.h" #include <string> Account::Account() {} //用于构造函数初始化 Account::Account(string name):name(name) {} //用于参数初始化列表初始化 string Account::getName() { return this->name; } void Account::setName(string name) { this->name = name; }
-
main.cpp
#include <iostream> #include "TicketMachine.h" //在main函数中以双引号引用 using namespace std; int main() { TicketMachine *ticketMachine = new TicketMachine(1,"test"); TicketMachine *ticketMachine1 = new TicketMachine(1,2,"test"); ticketMachine->show(); //使用参数化列表 ticketMachine1->show(); //使用构造函数 return 0; }
-
结果
balance:1 total:512 name:test balance:1 total:2 name:test
-
结论
- 参数化列表:初始化
- 构造函数:初始 => 赋值
-
-
任何类里面的成员变量都应该initialized里面做初始化,而不应该在构造函数中做初始化(否则需要default constructor)
使用构造函数初始化有时需要default constructor
六、对象组合与继承
对象组合和继承都是实现软件重用的方式
(一)、对象组合
- 两种使用方式
- 包含
- 引用(指针)
(二)、继承
-
格式
class parent: public child{ //修饰符可以换 }
-
protected:父类留给子类的接口用于访问private
-
子类与父类的构造与析构先后
- 父类先构造,子类后构造
- 子类先析构,父类后析构
代码:
#include <iostream> #include <stdio.h> #include "TicketMachine.h" //在main函数中以双引号引用 using namespace std; class A{ public: A(){ cout << " A creator" << endl; } ~A(){ cout << " A delete" << endl; } }; class B : public A{ public: B(){ cout << " B creator" << endl; } ~B(){ cout << " B delete" << endl; } }; int main() { B b; return 0; }
运行结果:
A creator B creator B delete A delete
七、函数
(一)、函数重载
-
c++的类中只有重载(overload)没有覆写(overwrite)
-
当子类有父类的同名函数,父类的同名参数(包括重载的)将全部被屏蔽
代码
#include <iostream> #include <stdio.h> #include "TicketMachine.h" //在main函数中以双引号引用 using namespace std; class A{ public: A(){ cout << " A creator" << endl; } void print(){ cout << "A print" << endl; } void print(int i){ cout << "A print(i)" << endl; } ~A(){ cout << " A delete" << endl; } }; class B : public A{ public: B(){ cout << " B creator" << endl; } void print(){ cout << "B print" << endl; } ~B(){ cout << " B delete" << endl; } }; int main() { A a; B b; a.print(); a.print(1); b.print(); b.print(1); //错误 return 0; }
(二)、函数默认参数
-
默认参数值:只有右边的参数可以省略掉
格式:
-
TIcketMachine.h
// // Created by Arno_vc on 2022/3/1. // #ifndef TICKETMACHINE_TICKETMACHINE_H #define TICKETMACHINE_TICKETMACHINE_H #include "Account.h" class TicketMachine { private: int balance; int total; public: TicketMachine(); void setBalance(int balance,int total = 9); //default argument尽在.h文件添加 int getBalance(); void show(); ~TicketMachine(); }; #endif //TICKETMACHINE_TICKETMACHINE_H
-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" #include <stdio.h> #include <iostream> using namespace std; TicketMachine::TicketMachine() {} void TicketMachine::setBalance(int balance,int total) { this->balance = balance; this->total = total; } int TicketMachine::getBalance() { return this->balance; } void TicketMachine::show(){ cout << "balance:" << balance << " total:" << total << endl; }; TicketMachine::~TicketMachine() { }
-
main.cpp
#include <iostream> #include <stdio.h> #include "TicketMachine.h" using namespace std; void f(int i,int j=10){}; int main() { TicketMachine ticketMachine; ticketMachine.setBalance(1); ticketMachine.show(); return 0; }
-
-
default argument是在编译时起作用,而不是运行时
-
a.h
void f(int i,int j=0);
-
a.cpp
#include "a.h" #include <iostream> using namespace std; void f(int i,int j){ cout << i << " " << j << endl; }
-
main.cpp
void f(int i,int j=10); int main(){ f(10); for(;;); return 0; }
-
结果:10 10
-
说明:
- 若默认参数是多了重载函数:则这些函数的第二个参数必然为0,因为main.cpp与a.cpp仅在链接步骤出现关联,而链接步骤只做了链接.
- 默认参数显然只能在编译时补上:由上显然只是一个函数然后适时补上了参数,都运行了还怎么补,那显然只能在编译时补(具体函数调用过程见下)
-
-
少用default value,以免阅读障碍等问题
(三)、内联函数
-
作用:在编译时将对应的函数嵌入在源代码中(汇编代码阶段,以空间换时间)
-
优点
- 自身优点:避免了原函数需要的大量堆栈操作
- 相较于宏定义:编译器无法对宏定义做类型检查,但可以对内联函数做检查
-
缺点:当函数代码过长或者使用了递归,编译器不会使用inline
-
调用格式:与default arguments不同,内联函数inline只需要在.h文件
-
使用内联
-
main.cpp
#include "a.h" int main(){ A a; a.f(1,2); for(;;); return 0; }
-
a.h
class A{ public: inline int f(int i,int j=0); ////inline function definition }; int inline A::f(int i,int j){ //inline function definition return i+j; }
-
观察 汇编文件main.s,共49行
-
-
不使用内联
-
main.cpp
#include "a.h" int main(){ A a; a.f(1,2); for(;;); return 0; }
-
a.h
class A{ public: int f(int i,int j=0); ////inline function definition };
-
a.cpp
#include "a.h" int A::f(int i,int j){ //inline function definition return i+j; }
-
观察汇编文件main.s,a.s,共27+24行(对于循环的调用应该效果会更明显吧)
-
-
-
类声明中的函数都是inline的,但通常更喜欢将需要inline的函数定义放在类外
八、const关键字
-
extern const int bufsize
- 外部有一个变量bufsize,这个bufsize是不是const无所谓
- 在我这里不能修改
-
几种常见使用错误
-
const对应的变量为什么无法用来初始化数组
#include <iostream> using namespace std; int main(){ int x; cin >> x; const int y; int arr[y]; return 0; }
原因:函数的局部变量包括数组是存放在堆栈中的,在编译时由编译器分配对应的内存空间。当y是变量时,显然编译器无法分配数组需要的栈空间报错。
-
const变量的地址无法赋值给普通指针
#include <iostream> using namespace std; int main(){ int const x=3; int *p; *p=&x; return 0; }
原因:x认为不能修改,但p认为可以修改,两者地位平等,存在逻辑冲突
-
-
指针用法
char * const q = "abc"
:指针无法修改const char *p = "abc"
:无法通过改指针修改内容
注:const在*后,对象是const;const在*后,指针是const
-
变量存放的三个区域:
- 全局变量:全局数据区(其中常量在代码段中)
- 本地变量:堆栈(由编译器分配)
- 动态分配的内存:堆
-
const char *s = "Hello world!"
和char s[] = "Hello world!"
-
main.cpp
#include <iostream> #include <stdio.h> //#include "TicketMachine.h" using namespace std; int main() { const char* s1 = "Hello world!"; //指向代码段 char s2[] = "Hello world!"; //指向堆栈 printf("main:%p\n",main); printf("s1 :%p\n",s1); printf("s2 :%p\n",s2); return 0; }
-
结果
main:00007ff64e541731 s1 :00007ff64e54b001 s2 :000000c3957ff94b
-
结论
- 前者仅仅是将指针指向代码段(无法修改)
- 后者是在堆栈中分配了内存空间并赋值
-
-
函数参数为什么要传const指针
原因:
- 传入对象要在对栈中开辟一个临时的数据空间,并放入数据
- 传入指针可能会被修改,不安全\
结论:
- 传入const指针既能方便传入对象,又能保证不被修改
-
const对象:对象中的值都是无法修改的 => 在函数后加const保证对象中的函数不会修改成员变量(声明和定义后都要加const,实际上表示this要加const)
-
TIcketMachine.h
// // Created by Arno_vc on 2022/3/1. // #ifndef TICKETMACHINE_TICKETMACHINE_H #define TICKETMACHINE_TICKETMACHINE_H #include "Account.h" void f(int i,int j=0); class TicketMachine { private: int balance; int total; public: TicketMachine(); int getBalance() const; //对函数声明进行const void show(); ~TicketMachine(); }; #endif //TICKETMACHINE_TICKETMACHINE_H
-
TicketMachine.cpp
// // Created by Arno_vc on 2022/3/1. // #include "TicketMachine.h" #include <stdio.h> #include <iostream> //在cpp下加额外需要用的头文件 using namespace std; //需要加标准命名空间 //void f(int i,int j){ // cout << i << " " << j << endl; //} TicketMachine::TicketMachine() {} int TicketMachine::getBalance() const { //对函数定义加const return this->balance; } void TicketMachine::show(){ cout << "balance:" << balance << " total:" << total << endl; }; TicketMachine::~TicketMachine() { }
-
main.cpp
#include <iostream> #include <stdio.h> #include "TicketMachine.h" using namespace std; int main() { const TicketMachine ticketMachine; //const对象 cout << ticketMachine.getBalance() << endl; //可以运行 ticketMachine.show(); //由于我们在声明ticketMachine时是const, 而show()不是const函数(编译器认为它不一定是安全的),故无法调用 return 0; }
-
-
const构成重载
void f(); //A* this void f() const; //const A* this
九、引用
-
基本定义:引用可以看做是别名
本质:用
const *
实现 -
与const
const int& z=x; //z的应用对象本身不能改变的,这里const表示不能通过z修改x(类比*)
-
与*
int *& p;
:一个int *
类型的引用int &* p
:非法
-
使用:函数参数
void f(int& x); //注意这个是函数声明,以往见到的多是调用中加&符号
-
无法做函数重载
-
没有reference的reference
int x; int &a=x; int &b=a; //error
十、virtual关键字
-
使用virtual关键字的类第一个int字节不再是普通的属性,而是指针,指向vtable(虚拟函数表)
-
main.cpp
#include <iostream> #include <stdio.h> using namespace std; class A{ public: int i; public: A():i(10){}; virtual void f(){ cout << "A" << endl; } }; typedef long long ll; int main() { //int 4,long 4,long long 8 A a; A b; ll *p = (ll*) &a; cout << *(ll*)p << " i:" << *(int *)(++p) << endl; //8个字节*8=64位地址 p = (ll *) &b; b.i=20; cout << *(ll*)p << " i:" << *(int *)(++p) << endl; ll *temp = (ll*)&b; cout << *(ll*)(*temp) << endl; //vtable地址 return 0; }
-
结果
140695808005440 i:10 140695808005440 i:20 140695807996512
-
结论:在使用virtual情况下,同一个类的首地址指向vtable(虚拟函数表)
-
-
子类与父类的vtable位置不同
-
main.cpp
#include <iostream> #include <stdio.h> using namespace std; class A{ public: int i; public: A():i(10){}; virtual void f(){ cout << "A" << endl; } }; class B:public A{ public: virtual void f(){ cout << "B" << endl; } }; typedef long long ll; int main() { //int 4,long 4,long long 8 A a; B b; ll *p = (ll*) &a; cout << *(ll*)p << " i:" << *(int *)(++p) << endl; //8个字节*8=64位地址 p = (ll *) &b; b.i=20; cout << *(ll*)p << " i:" << *(int *)(++p) << endl; return 0; }
-
结果
140696722494832 i:10 140696722494864 i:20
-
结论:父子类的vtable地址不同(哪怕父类没有覆写过虚函数)
-
-
类的赋值与指针的赋值
-
main.cpp
#include <iostream> #include <stdio.h> using namespace std; class A{ public: int i; public: A():i(10){}; virtual void f(){ cout << "A" << endl; } }; class B:public A{ public: virtual void f(){ cout << "B" << endl; } }; typedef long long ll; int main() { //int 4,long 4,long long 8 B b; A a1 = b; //赋值 a1.f(); A *a2 = &b; //指针 a2->f(); return 0; }
-
结果
A B
-
结论:不言自明
-
-
如果一个类里面有一个析构函数,那么析构函数也应该是virtual的
-
main.c
#include <iostream> #include <stdio.h> using namespace std; class A{ public: int i; public: A():i(10){}; virtual void f(){ cout << "A" << endl; } ~A(){ cout << "delete A" << endl; } }; class B:public A{ public: virtual void f(){ cout << "B" << endl; } ~B(){ cout << "delete B" << endl; } }; typedef long long ll; int main() { //int 4,long 4,long long 8 A *a = (A *)new B; delete a; return 0; }
-
结果
delete A
-
结论:由于普通函数的静态链接导致这里调用的A类的析构函数(而不是本身的B类)
-
main.c修改为virtual函数
#include <iostream> #include <stdio.h> using namespace std; class A{ public: int i; public: A():i(10){}; virtual void f(){ cout << "A" << endl; } virtual ~A(){ cout << "delete A" << endl; } }; class B:public A{ public: virtual void f(){ cout << "B" << endl; } virtual ~B(){ cout << "delete B" << endl; } }; typedef long long ll; int main() { //int 4,long 4,long long 8 A *a = (A *)new B; delete a; return 0; }
-
结果
delete B delete A
-
结论:成功自下而上调用析构函数
-
-
通过
FatherClass::func()
调用父类函数-
main.c
#include <iostream> #include <stdio.h> using namespace std; class A{ public: int i; public: A():i(10){}; virtual void f(){ cout << "A" << endl; } virtual ~A(){ cout << "delete A" << endl; } }; class B:public A{ public: virtual void f(){ A::f(); } virtual ~B(){ cout << "delete B" << endl; } }; typedef long long ll; int main() { A *a = (A *)new B; a->f(); return 0; }
-
结果
A
-
结论:子类用父类民调用父类函数
-
-
通过virtual实现override
-
main.cpp
#include <iostream> #include <stdio.h> using namespace std; class A{ public: int i; public: A():i(10){}; virtual void f(){ cout << "A" << endl; } virtual void f(int para){ cout << "A " << para << endl; } virtual ~A(){ cout << "delete A" << endl; } }; class B:public A{ public: virtual void f(){ //必须全部用virtual cout << "B" << endl; } virtual void f(int para){ //必须全部用virtual cout << "B " << para << endl; } virtual ~B(){ cout << "delete B" << endl; } }; typedef long long ll; int main() { A *a = (A *)new B; a->f(); return 0; }
-
结果
B
-
十一、拷贝构造
-
特点
- 没有调用普通构造函数(与普通的new比较)
- 发生的不是字节到字节的拷贝,而是member到member的拷贝
-
基本格式:
T::T(const T&); //由c++给出,可以自己覆盖
-
函数参数
-
main.cpp
#include <iostream> #include <stdio.h> using namespace std; int objectNum=0; class A{ public: A(){ objectNum++; cout << "objectNum: " << objectNum << endl; } ~A(){ objectNum--; cout << "objectNum: " << objectNum << endl; } }; A f(A a){ cout << "in function, objectNum:" << objectNum << endl; return a; } int main() { A a; cout << "before function" << endl; A b = f(a); cout << "after function" << endl; return 0; }
-
结果
objectNum: 1 before function in function, objectNum:1 objectNum: 0 objectNum: -1 after function objectNum: -2
-
结论:f中的参数和返回值所使用的都没有走构造函数,而是通过其它方式(拷贝构造)
-
-
基本使用
-
main.cpp
#include <iostream> #include <stdio.h> using namespace std; int objectNum=0; class A{ public: A(int temp){ objectNum++; cout << "objectNum: " << objectNum << endl; } ~A(){ objectNum--; printf("%p ",this); cout << "objectNum: " << objectNum << endl; } }; int main() { A a(1); A b = 2; return 0; }
-
结果
objectNum: 1 objectNum: 2 000000d4e11ff84e objectNum: 1 000000d4e11ff84f objectNum: 0
-
结论:
A a = 1; //等效与A a(2)
-
-
覆写
-
main.cpp
#include <iostream> #include <stdio.h> using namespace std; int objectNum=0; class A{ public: A(){ objectNum++; cout << "objectNum: " << objectNum << endl; } A(const A &b){ objectNum++; //A *temp = &b; printf("&b :%p this:%p\n",&b,this); //被拷贝的对象地址,原对象地址 cout << "objectNum: " << objectNum << endl; } ~A(){ objectNum--; printf("%p ",this); cout << "objectNum: " << objectNum << endl; } }; A f(A a){ printf("para a :%p\n",&a); //参数a cout << "in function, objectNum:" << objectNum << endl; return a; } int main() { A a; printf("initial a:%p\n",&a); //初始a cout << "before function" << endl; A b = f(a); //但是f(a)也会是相同的效果 printf("b:%p\n",&b); return 0; }
-
结果
objectNum: 1 initial a:00000052915ff86e before function &b :00000052915ff86e this:00000052915ff86f // a=>para a objectNum: 2 para a :00000052915ff86f in function, objectNum:2 &b :00000052915ff86f this:00000052915ff86d // para a => b objectNum: 3 00000052915ff86f objectNum: 2 b:00000052915ff86d 00000052915ff86d objectNum: 1 00000052915ff86e objectNum: 0
-
问题:f(a)是一个拷贝构造,那么A b=f(a)为什么也只有一个拷贝构造?
-
main.cpp
#include <iostream> #include <stdio.h> using namespace std; class A{ public: A(){ } A(const A &b){ printf("&b :%p this:%p\n",&b,this); //被拷贝的对象地址,原对象地址 } }; A f(A a){ return a; } int main() { A a; f(a); cout << "--------------" << endl; A c = f(a); printf("c:%p",&c); return 0; }
-
结果
&b :0000007e19fff90c this:0000007e19fff90e &b :0000007e19fff90e this:0000007e19fff90d -------------- &b :0000007e19fff90c this:0000007e19fff90f &b :0000007e19fff90f this:0000007e19fff90b c:0000007e19fff90b
-
结论:根据翁凯老师的讲解,这里编译器做了个优化(即要不要return回一个临时对象)
-
原形式
f(){ A b; return b; } Person @temp = f(a); A c = @temp;
-
编译器优化后的形式
f(){ A b; return b; } A c = b; //优化了一次拷贝构造
-
main.cpp验证
#include <iostream> #include <stdio.h> using namespace std; class A{ public: A(int t){ } void show(){ cout << "show" << endl; } A(const A &b){ printf("&b :%p this:%p\n",&b,this); //被拷贝的对象地址,原对象地址 } }; A f(int t){ A a(t); //非拷贝构造 printf("a:%p\n",&a); return a; } int main() { A c = f(2); //没有发生拷贝构造 printf("c:%p",&c); return 0; }
-
结果
a:00000043cabff98f c:00000043cabff98f
-
结论:没有使用拷贝构造(被优化了)
-
-
-
调用拷贝构造的几种情况
- 对象作为函数的参数传入
- 用一个对象初始化另一个对象
-
拷贝构造对指针属性的影响:将指针指向了同一个位置 => 然而许多时候我们并不希望这样
- 注意到c++中的string有拷贝构造,而c中的
char *
没有,因此更推荐用string - 一般情况下建议自己写拷贝构造
- 注意到c++中的string有拷贝构造,而c中的
-
禁止拷贝构造:将拷贝构造函数private
-
拷贝构造与初始化的不同
- 拷贝构造:调用拷贝构造函数
- 初始化:调用赋值运算符
十二、static关键字
-
static在c中的使用
- 本地变量(hidden):将变量(函数)限制在当前文件使用
- 持久化存储(persistent):而不是在某个函数调用后就收回
-
static在c++中的使用
- 修饰类:存储是全局的(链接时分配),但初始化依然是在函数内部,只初始化一次
- 修饰属性:属于类的静态成员属性和静态成员函数(这里表现的是其persistent特性,他的hidden特性由private替代了)
-
类中的static属性的初始化
-
需要在.cpp中初始化
-
main.cpp
#include <iostream> #include <stdio.h> using namespace std; class A{ public: static int i; //.h:声明 }; int A::i; //.cpp:定义,不能在前面添加static,因为static属性可以在.cpp外文件访问 int main() { cout << A::i << endl; return 0; }
-
结果
0
-
-
不能通过初始化参数列表初始化
-
在内部通过this访问
-
在外部通过
ClassName::field
访问
-
-
静态成员函数调用静态参数
十三、运算符重载
-
限制
- 只能对已有运算符重载
- 重载必须对一个类或枚举类型
- 重载必须保持原来的参数个数与优先级
-
通过operator关键字重载
-
运算符重载类型
-
全局函数:完整写出所有参数
-
成员函数:使用reciver作为实际调用运算符重载函数的对象(在全局的重载函数中是第一个参数)
以
a + b
为例,实际可以写作a.operator+(b)
,故reciver是左边的a应该选择作为成员函数的情况:
- 单目运算符
- 常见的如:
=,(),->,->*
-
-
基本概念
-
return value
- 修改当前对象
- 返回新对象
- 新对象可以被修改
- 新对象不能被修改
-
常见操作符原型
-
+-*/%^&|~
const T operatorX(const T& l,const T& r) const;
-
! && || < <= == >= >
bool operatorX(const T& l,const T& r) const;
-
[]
T& T::operator[](int index);
-
++ --
const Integer& operator++(); //原值+1 const Integer operator++(int); //postfix,返回新的同值对象,原值+1 const Integer& operator--(); const Integer operator--(int); //postfix
注:通过int,编译器能够识别那个是
prefix
哪个是postfix
-
=
T& T::operator=(const T& rhs){ if(this!=&rhs){ //perform assignment } return *this; }
-
强制转换
-
通过构造函数转换
-
隐式转换
main.cpp
#include <iostream> #include <stdio.h> using namespace std; class One{ public: One(){}; }; class Two{ public: Two(const One&){} }; void f(Two t){ } int main() { One one; f(one); //隐式转换:给的是One,但是能自动转换为Tow return 0; }
-
显示转换
main.cpp
#include <iostream> #include <stdio.h> using namespace std; class One{ public: One(){}; }; class Two{ public: explicit Two(const One&){} //加入关键字构成显示转换 }; void f(Two t){ } int main() { One one; f(Two(one)); //显示转换:此时编译无法通过 return 0; }
-
-
使用强制转换(少用)
X::operator double() const; //没有返回类型,因为double已经说明了将X对象转变为T
-
-
-
十四、模板
-
场景:不同类型的数据需要用到相同的代码段
选择:
- 使用基类
- 代码克隆(不好管理)
- 无类型列表(void *)
-
函数模板
-
main.cpp
#include <iostream> using namespace std; //声明而非定义 template <typename T> void Swap(T &a,T &b){ //注意不要用stdio.h中的swap T temp=a; a=b; b=temp; cout << "swap over" << endl; } int main() { int a=1,b=2; float c=1.0,d=2.0; cout << "a:" << a << " b:" << b << endl; Swap(a,b); cout << "a:" << a << " b:" << b << endl; cout << "c:" << c << " d:" << d << endl; Swap(c,d); cout << "c:" << c << " d:" << d << endl; return 0; }
-
结果
a:1 b:2 swap over a:2 b:1 c:1 d:2 swap over c:2 d:1
-
本质
- 读取模板函数声明(有些在.h文件)
- 确定所需要的模板函数参数类型
- 生成函数
-
-
类模板:类模板里面的每个函数都是函数模板
使用:
-
main.cpp
#include <iostream> #include "TicketMachine.h" using namespace std; int main() { TicketMachine<int> t; //放入int int a=1,b=2; cout << "a:" << a << " b:" << b << endl; t.swap(a,b); cout << "a:" << a << " b:" << b << endl; return 0; }
-
TicketMachine.h
// // Created by Arno_vc on 2022/3/1. // #ifndef TICKETMACHINE_TICKETMACHINE_H #define TICKETMACHINE_TICKETMACHINE_H #include <iostream> using namespace std; //声明而非定义 template <class T> class TicketMachine { public: void swap(T& a,T& b); }; template <class T> //.cpp这里也要加templete void TicketMachine<T>::swap(T &a,T &b){ //类名后需要加T T temp=a; a=b; b=temp; cout << "swap over" << endl; } #endif //TICKETMACHINE_TICKETMACHINE_H
-
结果
a:1 b:2 swap over a:2 b:1
注:
- 模板类中函数的声明与定义都应该写在.h文件,否则链接错误(从编译角度可以理解,因为倘若放在另一个模块那是无法直接调用,必须将对应的函数定义放在同一个模块来真正构造函数)
- 类模板可以有默认参数
- 类模板可以继承
-
十五、异常
-
场景:不可控因素,例如读取一个文件,而那个文件实际不存在
-
优点:将业务逻辑和错误处理逻辑分开
-
使用
-
抛出异常,中断并上溯:
throw VectorIndexError(index);
注:
-
这个对象不是new出来的,故不在堆栈,而是在堆里
-
throw;
会直接将catch到的异常继续上抛 -
new关键字无法申请到内存,则抛出
bad_alloc异常
-
-
捕获异常
try {...} catch ... catch ...
注:
-
参数中的
...
表示捕获所有异常 -
匹配顺序(自上而下)
- exact
- base
- ellipse(...)
-
通过在函数定义后添加操作表示最多能抛出的异常
void abc(int a):throw(MathErr){ //本人最多只能抛出MathErr,如果抛出其它则会被unexpected exception替代 ... }
-
异常传递:建议用引用
-
构造函数丢异常也可能造成内存泄漏
class A{ public: A(){ buf = new char[1024]; delete this; } ~A(){ delete buf; } } int notmain(){ A a; A* p = new A(); //整个函数(以notmain函数为例)在此结束,无法通过delete p收回char[1024]的空间 delete p; return 0; }
-
-
十六、流
-
特点:一维单方向
- 缺点
- 比较慢
- 比较繁琐(内部)
- 优点
- 开发效率较高
注:与之相对的fscanf等接口是随机访问的
- 缺点
-
二进制文件:不以人的阅读为目的的文件(因为从某个角度来说,所有的文件都是二进制的)
-
c++中的流对象
- cin
- cout
- cerr:错误
- clog:日志
-
操纵器(manipulate):操纵器,例如:endl,flush
十七、STL模板
-
主要内容
-
数据结构
常用:
- Vector
- Deque:expands at both end
- List:double-linked
- sets and maps
特点:
- 自动增长
- 建议用
!=.end()
判断是不是走到头了
-
算法(函数模板)
常用:
- sort
- search:二分
-
-
通用的iterator
-
运算符通常是有结果的
-
可以对对象进行赋值(虽然看起来毫无意义)
#include <iostream> #include <stdio.h> #include "TicketMachine.h" //在main函数中以双引号引用 using namespace std; class B{ public: B(int i){ } }; int main() { B b(1); //相对于B *b,B b申请了内存空间 b=0; return 0; }
-
尽量将代码建筑在已有的代码基础上