1、const的作用
1、修饰变量:类似于#define宏定义,但可以指定类型,编译器里的符号表
2、修饰指针:指针常量,顶层const,指针不可变(int *const)*在const之前
指向常量的指针,底层const
3、修饰引用:指向常量的应用,指向形参类型,既避免了拷贝,又避免了函数对值的修改,因为通过实参的地址传递,保护实参所指向 的变量
4、修饰成员函数:函数声明后不可更改,修饰返回的指针或引用,或传入的参数为const并不能修改
##被 const 修饰(在 const 后面)的值不可改变
同时类的常对象只能访问类的常成员函数,const成员函数可以访问非const的数据成员,所以成员函数可以加const修饰
// 类
class A
{
private:
const int a; // 常对象成员,只能在初始化列表赋值
public:
// 构造函数
A() : a(0) { };
A(int x) : a(x) { }; // 初始化列表
// const可用于对重载函数的区分
int getValue(); // 普通成员函数
int getValue() const; // 常成员函数,不得修改类中的任何数据成员的值
};
void function()
{
// 对象
A b; // 普通对象,可以调用全部成员函数、更新常成员变量
const A a; // 常对象,只能调用常成员函数
const A *p = &a; // 指针变量,指向常对象
const A &q = a; // 指向常对象的引用
// 指针
char greeting[] = "Hello";
char* p1 = greeting; // 指针变量,指向字符数组变量
const char* p2 = greeting; // 指针变量,指向字符数组常量(const 后面是 char,说明指向的字符(char)不可改变)
char* const p3 = greeting; // 自身是常量的指针,指向字符数组变量(const 后面是 p3,说明 p3 指针自身不可改变)
const char* const p4 = greeting; // 自身是常量的指针,指向字符数组常量
}
// 函数
void function1(const int Var); // 传递过来的参数在函数内不可变
void function2(const char* Var); // 参数指针所指内容为常量
void function3(char* const Var); // 参数指针为常量
void function4(const int& Var); // 引用参数在函数内为常量
// 函数返回值
const int function5(); // 返回一个常数
const int* function6(); // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const function7(); // 返回一个指向变量的常指针,使用:int* const p = function7();
2、static的作用
1、修饰普通变量,存储在静态区,提前分配空间
2、修饰普通函数,在定义文件中可见,其他地方不可见
3、修饰成员变量,只保存一个对象,只有一份拷贝,属于类被类所拥有的,在类外定义实例时初始化
4、修饰成员函数,不需要生成对象就可以访问,被类的所有对象访问,与类相关联,但是本身不能访问其他静态成员函数
因此可以分为两大部分,包括全局区域内分配内存时的static和在面向对象设计时的static
只能在类外初始化
- 不能通过初始化列表初始化,
- 不能在类内进行初始化,
- 不能在构造函数中初始化,
3、this指针
this是隐含在非静态成员函数中的特殊指针,指向调用该成员函数的那个对象
在this指针的定义内部它不仅是指针常量也是指向常量的指针,因此不能对this指针赋值,同时this指向的对象也不能被更改
在以下场景中,经常需要显式引用 this
指针:
- 为实现对象的链式引用;
- 为避免对同一对象进行赋值操作;
- 在实现一些数据结构时,如
list
。
4.inline函数:
类内定义是隐式内联,类外定义需要显式内联,声明函数类型是inline类型
inline在编译时解决小函数调用大空间的问题,类中的成员函数默认都是构造函数
5、volatile关键字:
类型修饰符,它申明的类型变量在编译过程中可能被某些未知因素修改,所以申明后告诉编译器不应该对这样的类型修改。
每次从内存中重新读取,其他一般的变量可能直接从CPU寄存器中取值。
每次都需要重新读取,解决多进程之间的并发问题
- 一个函数要求在__main()__之前执行,可以使用全局变量构造函数,全局变量赋值,或者__attribute__关键字
- 元编程就是模板编程。利用template模板进行推导编译之后再获得目标代码
- 原子操作,要不不执行要么执行完,可以解决多线程并发的安全问题,线程之间切换时单核内部避免数据竞争的问题
6、assert()
assert()是宏,而非函数。assert 宏的原型定义在 <assert.h>
(C)、<cassert>
(C++)中,其作用是如果它的条件返回错误,则终止程序执行。可以通过定义 NDEBUG
来关闭 assert,但是需要在源代码的开头,include <assert.h>
之前。
#define NDEBUG // 加上这行,则 assert 不可用
#include <assert.h>
assert( p != NULL ); // assert 不可用
7、extern()
extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,声明定义变量,在多个文件中共享代码,共享变量与模板类,是与static关键字相对的,不同模板引用即使在编译阶段找不到目标函数也不会报错,而是在连接阶段自动连接、
其中extern“C”可以实现代码混用,让C++编译器将extern“C”声明的函数当做C语言进行处理
8、sizeof()
sizeof对数组,得到整个数组所占空间大小
sizeof对指针,得到本身指针所占空间大小
sizeof空类时是1,sizeof只有虚函数的类时是4,因为有虚函数表指针,指针变量所占空间大小为4
9、struct和class
总的来说,struct 更适合看成是一个数据结构的实现体,class 更适合看成是一个对象的实现
- 最本质的一个区别就是默认的访问控制
- 默认的继承访问权限。struct 是 public 的,class 是 private 的。
- struct 作为数据结构的实现体,它默认的数据访问控制是 public 的,而 class 作为对象的实现体,它默认的成员变量访问控制是 private 的。
字节对齐的问题:
在struct内部定义char和int类型的时候,变量在内存上的存储位置是不同的,设计到字节对齐的内容,即:
- char 偏移量必须为sizeof(char) 即1的倍数,可以任意地址开始存储
- short 偏移量必须为sizeof(short) 即2的倍数,只能从0,2,4...等2的倍数的地址开始存储
- int 偏移量必须为sizeof(int) 即4的倍数,只能从0,4,8...等4的倍数的地址开始存储
- float 偏移量必须为sizeof(float) 即4的倍数,只能从0,4,8...等4的倍数的地址开始存储
- double 偏移量必须为sizeof(double)即8的倍数,只能从0,8,16...等地址开始存储
当用#pragma pack(n)指定时,以n和结构体中最长的成员的长度中较小者为其值.
同时最终sizeof输出的一定是最长类型长度的整数倍
10、explict显式关键字
explict修饰构造函数时,可以防止隐式转换和列表初始化
explict修饰转换函数时,可以防止隐式转换,但语境转换除外
struct A
{
A(int) { }
operator bool() const { return true; }
};
struct B
{
explicit B(int) {}
explicit operator bool() const { return true; }
};
void doA(A a) {}
void doB(B b) {}
int main()
{
A a1(1); // OK:直接初始化
A a2 = 1; // OK:复制初始化
A a3{ 1 }; // OK:直接列表初始化
A a4 = { 1 }; // OK:复制列表初始化
A a5 = (A)1; // OK:允许 static_cast 的显式转换
doA(1); // OK:允许从 int 到 A 的隐式转换
if (a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a6(a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a7 = a1; // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a8 = static_cast<bool>(a1); // OK :static_cast 进行直接初始化
B b1(1); // OK:直接初始化
B b2 = 1; // 错误:被 explicit 修饰构造函数的对象不可以复制初始化
B b3{ 1 }; // OK:直接列表初始化
B b4 = { 1 }; // 错误:被 explicit 修饰构造函数的对象不可以复制列表初始化
B b5 = (B)1; // OK:允许 static_cast 的显式转换
doB(1); // 错误:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换
if (b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
bool b6(b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
bool b7 = b1; // 错误:被 explicit 修饰转换函数 B::operator bool() 的对象不可以隐式转换
bool b8 = static_cast<bool>(b1); // OK:static_cast 进行直接初始化
return 0;
}
11、指针与引用
指针就是引用,在编译器层面汇编语言定义是相同的
指针有内存空间,sizeof指针大小是4,NULL初始化。需要解引用操作
引用相当于给对象起的别名,大小取决于被引用对象的大小,指向存在对象,修改对象会引起引用改变
指针可以改变但是引用不行,返回动态对象,内存必须用指针
初始化也有区别,指针在定义时可以不初始化,但是引用必须初始化
- 左值引用与右值引用,move语义移动构造函数
- C++11的特性,利用右值引用,存储在CPU或者寄存器中,不可以求地址,通过move语义在复制构造函数和移动构造函数中自动选择,在临时对象被析构之前,直接将对象内容进行拷贝和复制出来,从而避免了内存多次调用的问题
- 存储在CPU或者内容中,直接修改指针,避免内存多次调用
string a(x); // line 1
string b(x + y); // line 2
string c(some_function_returning_a_string()); // line 3
'''
如果是a=b,这样就会调用复制构造函数来初始化that(因为b是左值),赋值操作符会与新创建的对象交换数据,深度拷贝。这就是copy and swap 惯用法的定义:构造一个副本,与副本交换数据,并让副本在作用域内自动销毁。这里也一样。
如果是a = x + y,这样就会调用转移构造函数来初始化that(因为x+y是右值),所以这里没有深度拷贝,只有高效的数据转移。相对于参数,that依然是一个独立的对象,但是他的构造函数是无用的(trivial),因此堆中的数据没有必要复制,而仅仅是转移。没有必要复制他,因为x+y是右值,再次,从右值指向的对象中转移是没有问题的。
'''
12、面向对象
面向对象程序设计(Object-oriented programming,OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。
面向对象三大特征 —— 封装、继承、多态
封装
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。关键字:public, protected, private。不写默认为 private。
public
成员:可以被任意实体访问protected
成员:只允许被子类及本类的成员函数访问private
成员:只允许被本类的成员函数、友元类或友元函数访问
继承
- 基类(父类)——> 派生类(子类)
多态
- 多态,即多种状态(形态)。简单来说,我们可以将多态定义为消息以多种形式显示的能力。
- 多态是以封装和继承为基础的。
- C++ 多态分类及实现:
- 重载多态(Ad-hoc Polymorphism,编译期):函数重载、运算符重载,静态的多态
- 子类型多态(Subtype Polymorphism,运行期):虚函数,动态的多态,在运行时确定
- 参数多态性(Parametric Polymorphism,编译期):类模板、函数模板
- 强制多态(Coercion Polymorphism,编译期/运行期):基本类型转换、自定义类型转换
C++中多态和虚函数的实现:
在基类中定义虚函数,虚函数的指针指向虚函数表的入口地址,构建时会先构建基类的指针对象和成员,再实例化派生类的构造函数,其中虚函数的基类指针会复制给派生类,在构造函数和析构函数时顺序执行
具体方式通过绝对地址+偏移类在O(1)的时间内确定对应位置。其中虚函数位于正常代码区,虚函数表位于程序的只读数据段或者常量区,是固定写好的
一般的虚函数是需要在子类中被继承覆盖的,继承接口的同时实现父类的实现,在子类中可以不重写。但是纯虚函数不同,对应抽象类,提供统一接口,必须在子类中创建该实例对象
13、mallo/free与new/delete的区别:
mallo是分配固定大小到指定的堆空间,new是根据对象自主判断开辟的内存空间的大小
mallo是库函数,new是运算符,一个返回类型是void*,一个是指向对象的指针
new相当于operate重载new操作符,再调用构造函数
new[],delete[]申请数组时也是动态构造函数,动态析构,顺序执行。
14、内存管理
操作系统中是分页加分段,段页结合进行从虚拟地址到逻辑地址的映射,分段可以分为代码段和数据段
其中C++在内存中存储时分为5个部分,栈区、堆区、自由存储区、全局静态存储区(全局变量,静态变量)、常量区
栈是编译器自动分配的,不需要清除的变量存储区,是由系统提供的数据结构,栈由高地址向低地址生长,存储参数,局部变量
堆是由new自动申请释放的,会有内存碎片,由os自动回收,堆是不连续的内存,区域是由链表存储的,动态分配,alloca函数分配
比较二者不同包括系统响应(栈溢出,堆链表),申请大小的响应,分配方式,分配效率
2、内存泄露:new'以后没有delete,可以通过shared_ptr和weak_ptr来解决循环引用的问题,或者加入list指针动态捕捉
3、野指针:初始化或者free后没有置null,跨合法范围操作,野指针和常量更改会引起段错误
4、内存越界:缓冲区越界(数组越界),栈溢出(length过长)
三种管理内存的方式:自动存储,静态存储和动态存储(new delete)
自动存储(栈存储):函数的形参,函数内部声明的变量以及结构体变量
静态存储(编译器预分配):永久存储
extern关键字:全局变量或外部变量,定义性声明和引用性说明,在新的文件中声明后可以进行访问
15、其他
1、静态绑定是在编译期所决定的,绑定声明时的类型,动态绑定是在运行多态是实现
2、成员初始化列表可以减少一次对默认构造函数的调用,直接调用构造函数就可以实现初始化
3、static cast:上行转换、类型转化
dynatic cast:运行时完成,返回null指针,要求有虚函数
常量强制转换:去指针/引用,可以更改
reinterpret cast:将指针/引用转换为int类型
16、C++实现一个string类的操作
基本变量是要动态的维护内存空间,主要包括构造、拷贝、移动复制、析构函数、赋值操作符重载这几类
https://blog.csdn.net/u012814856/article/details/79753031
class String
{
public:
String(const char *str=nullptr);
String(const String &other);
String(String &&other);
~String();
String &operator = (const String &other);
private:
char *m_data;
};
##默认构造函数与自定义构造函数
String::String(const char *str)
{
if (str == nullptr)
{
m_data =new char[1];
*m_data = ['\0'];
}
else
{
int length = strlen(str);
m_data = new char[length+1];
strcpy(m_data,str);
}
}
##复制构造函数
String::String(const String &other)
{
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data,other.m_data);
}
##移动构造函数
String::String(const String &&other)
{
m_data = other.m_data;
other.m_data = nullptr;
}
##析构函数
String::~String()
{
delete[] m_data;
}
##赋值操作符重载
String::String &operator= (const String &other)
{
if(this != &other)
{
if(!m_data) delete[] m_data;
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data,other.m_data);
}
return *this
}
17、C++中实现memcpy()内存拷贝函数
其中传入参数是void* 类型,以防止指针强制转换的问题,同时考虑到内存重叠的问题,当内存指针重叠时,会选择从右往左进行内容复制,不重叠时选择从左往右进行复制
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
void my_memmove(void *dst,const void *src,int size)
{
void *ret = dst;
char *p = (char *)dst;
char *q = (char *)src;
assert(dst);
assert(src);
if(q<p&&(q+size>p))//内存折叠,从右至左拷贝
{
p = p + size - 1;
q = q + size - 1;
while(size--)
{
*p = *q;
p--;
q--;
}
}
else
{
while(size--)
{
*p = *q;
p++;
q++;
}
}
return ret;
}
int main()
{
int a[] = {1,2,3,4};
my_memmove(a+1,a,sizeof(a));
system("pause");
return 0;
}
17、大端与小端机器:
大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址端。TCP/IP协议规定为大端模式
小端模式,是指数据的高字节保存在内存的高地址中,低位字节保存在在内存的低地址端。
.大小端模式各有优势:
小端模式强制转换类型时不需要调整字节内容,直接截取低字节即可;大端模式由于符号位为第一个字节,很方便判断正负。
- 直接读取存放在内存中的十六进制数值,取低位进行值判断
int a = 0x12345678;
int *c = &a;
c[0] == 0x12 大端模式
c[0] == 0x78 小段模式
- 用共同体来进行判断,利用联合体共享内存的特性,截取低地址部分
BOOL IsBigEndian() { union NUM { int a; char b; }num; num.a = 0x1234; if(0x12 == num.b) { return TRUE; } return FALSE; }
19、C++中实现一个智能指针类
https://www.cnblogs.com/QG-whz/p/4777312.html
//模板类作为友元时要先有声明
template<typename T>
class SmartPtr;
template<typename T>
class U_Ptr //辅助类
{
private:
//该类成员访问权限全部为private,因为不想让用户直接使用该类
friend class SmartPtr<T>; //定义智能指针类为友元,因为智能指针类需要直接操纵辅助类
//构造函数的参数为基础对象的指针
U_Ptr(T *ptr):p(ptr),count(1){};
/析构函数
~U_Ptr(delete p);
//引用计数
int count;
//基础类型指针
T *p;
};
template<typename T>
class SmartPtr
{
public:
SmartPtr(T *ptr):rp(new U_Ptr<T>(ptr)) { }; //构造函数
SmartPtr(const SmartPtr<T> &sp):rp(sp.rp) { ++rp.count}; //复制构造函数
SmartPtr &operator= (const SmartPtr<T> &rhs) //重载赋值操作符
{
++rhs.count; //首先将右操作数引用计数加1,
if (--rp.count ==0) //然后将引用计数减1,可以应对自赋值
delete rp;
rp = rhs.rp;
return *this;
}
T &operator *() //重载*操作符
{
return *(rp.p)
}
T* operator ->() //重载->操作符
{
return rp->p;
}
~SmartPtr() { //析构函数
if (--rp->count == 0) //当引用计数减为0时,删除辅助类对象指针,从而删除基础对象
delete rp;
else
cout << "还有" << rp->count << "个指针指向基础对象" << endl;
}
private:
U_Ptr<T> *rp;
};
int main()
{
int *i = new int(2);
{
SmartPtr<int> ptr1(i);
{
SmartPtr<int> ptr2(ptr1);
{
SmartPtr<int> ptr3 = ptr2;
cout << *ptr1 << endl;
*ptr1 = 20;
cout << *ptr2 << endl;
}
}
}
system("pause");
return 0;
}
20、各种成员初始化
http://guozet.me/post/C++-Static-keyword/
class CTypeInit{
public:
CTypeInit( int c):m_c(c),m_ra(c){ }
private:
int m_a; //通过初始化列表初始化,或者构造函数初始化
/*引用*/
int &m_ra; //只能通过初始化列表初始化
/*静态变量*/
static int m_b;
/*常量*/
const int m_c;
/*静态整型常量*/
static const int m_d;
/*静态非整型常量*/
static const double m_e;
};
//静态成员变量,必须在类外初始化,且要去掉static关键字
int CTypeInit::m_b = 6;
const int CTypeInit::m_d = 6;
const double CTypeInit::m_e = 3.1415926;
int main()
{
CTypeInit obT(2);
return 0;
}
静态变量
static int m_b;
static成员变量需要在类定义体外进行初始化与定义,因为static数据成员独立该类的任意对象存在,它是与类关联的对象,不与类对象关联。例如:上述程序中的c变量的初始化。,static只能相互之间访问
只能在类外初始化
- 不能通过初始化列表初始化,
- 不能在类内进行初始化,
- 不能在构造函数中初始化,
常量
const int m_c;
只能通过初始化列表初始化
- 不能在类内进行初始化
- 不能在构造函数中初始化
- 不能在类外初始化
静态整型常量
static const int m_d;
- (整型)能否在类中初始化,取决于编译器
- 能在在类外初始化,不能带static
静态非整型常量
static const double m_e;
- (double型)能否在类中初始化,取决于编译器
- 能在在类外初始化,不能带static
21、重载与重写的区别
重载是在同一个类中对相同函数名的多重定义
1、相同的范围,在同一个类中;
2、函数名相同,参数不同;,virtual关键字可有可无
重写是在派生类中对同名函数的重定义,不同范围,基类和派生类中,函数名和参数名相同,是子类对父类同名函数的重写
重载:函数名相同,函数的参数个数、参数类型或参数顺序三者中必须至少有一种不同。函数返回值的类型可以相同,也可以不相同。发生在一个类内部。
重定义:也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) ,指派生类的函数屏蔽了与其同名的基类函数。可以理解成发生在继承中的重载。
重写:也叫做覆盖,一般发生在子类和父类继承关系之间。子类重新定义父类中有相同名称和参数的虚函数。(override)
22、静态链接库与动态链接库
静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时
多个源文件.c文件都会生成.obj可执行文件,将目标文件进行链接形成可以执行的程序过程就是静态链接,浪费空间,更新困难,因为内存中存在多个副本
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件
程序运行时会有相应的.dll文件库作为辅助,加载模块后就可以多次重复利用。
23、设计模式
单例模式、MVC模式、享元模式、装饰器模式
单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
基本思路是如果实例不存在,会创建一个实例;如果已存在就会返回这个实例
MVC模式是指 Model-View-Controller(模型-视图-控制器) 模式,一种设计思想,model里存放的是模型对象的数据信息,view视图实现对象数据的可视化,Controller控制数据流向模型对象,并且在可视化过程中实时更新。
from abc import abstractmethod,ABCMeta
# 创建Student类,一个Model类
class Student():
_rollNo = ""
_name = ""
def getRollNo(self):
return self._rollNo
def setRollNo(self,inRollNo):
self._rollNo = inRollNo
def getName(self):
return self._name
def setName(self,inName):
self._name = inName
# 创建视图StudentView
class StudentView():
def printStudentDetails(self,inStudentName,inStudentRollNo):
print("Student :")
print("Name : {0}".format(inStudentName))
print("Roll No : {0}".format(inStudentRollNo))
# 创建控制器
class StudentController():
def __init__(self,inModel,inView):
self._model = inModel
self._view = inView
def setStudentName(self,inName):
self._model.setName(inName)
def setStudentRollNo(self,inRollNo):
self._model.setRollNo(inRollNo)
def getStudentRollNo(self):
return model.getRollNo()
def updateView(self):
self._view.printStudentDetails(self._model.getName(),self._model.getRollNo())
# 调用输出
if __name__ == '__main__':
def retrieveStudentFromDatabase():
student = Student()
student.setName("Robert")
student.setRollNo("10")
return student
model = retrieveStudentFromDatabase()
view = StudentView()
controller = StudentController(model,view)
controller.updateView()
# 更新模型数据
controller.setStudentName("John")
controller.updateView()
享元模式:https://www.runoob.com/design-pattern/flyweight-pattern.html
有大量对象时可能会造成内存溢出,因此我们可以创建将其中共同的部分抽象出来,如果有相同的话直接返回不需要重新创建,
用 HashMap ,哈希表存储这些对象。
应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。
1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。
类对象的生成过程:接口----实体类---工厂(生成实体类的对象)---使用工厂获取对象
装饰器模式:https://www.runoob.com/design-pattern/decorator-pattern.html
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
装饰器的实现和闭包,输入函数返回函数,实现函数功能的扩充。
工厂模式:https://www.runoob.com/design-pattern/factory-pattern.html
工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
24、C++实现树的构建与遍历:
#include <iostream>
#include <cstdio>
using namespace std;
struct TreeNode{
char data;
TreeNode* left;
TreeNode* right;
TreeNode(char c):data(c),left(NULL),right(NULL){}
};
TreeNode* Build(int& pos,string str){
char c = str[pos++];
if(c=='#'){
return NULL;
}
TreeNode* root = new TreeNode(c);
root->left = Build(pos,str);
root->right = Build(pos,str);
return root;
}
void inorder(TreeNode* root){
if (root == NULL){
return;
}
inorder(root->left);
cout<<root->data<<" ";
inorder(root->right);
return;
}
int main(){
string str;
while(cin>>str)
{
int pos =0;
TreeNode* root = Build(pos,str);
inorder(root);
cout<<endl;
}
return 0;
}