我已经学完了C++了, 但是这学期好像没有学到什么东西,觉得C++我还没有入门,我想通过记笔记的形式再来学习C++,其中会有我个人对于C++的想法和不懂的地方我都会写在这里(希望高手能帮我解决不懂的地方和指出我错误的地方,谢谢),今天写第一篇,我应该每天都会写一篇的。
一:const、enum 、inline 的用法总结:
在用于替换不带参数的#define 是请用const 和enum 不仅是因为比#define 的安全性要高(会进行类型检查),而#define 定义的宏没有参数类型,只是简单的替换,而且const可用于任何的作用域的对象、函数参数、函数的返回值类型、成员函数体(可以达到防止用户修改的目的).如果在有参数的时候请用inline 代替带参数的宏(可以避免歧义,和一些错误). 对于const 修饰的变量可用一句话来表示"近水楼台先得月(变量的数据类型不看)"
有趣的是既然你上面这么说的,好我就这样做
eg0:
const char* str = "Name" ; //把char不看说明const修饰的是*str(指向常量的指针也就是说*str的内容不能变,但是它的地址可以变
str[0] = 'F' //false
str = "Fame" //true
而:
char* const str = "Name" //把char不看说明const修饰的是str(常指针),内容可以变,但是地址不能变
char* const str = "Name" ; //常指针
str[0] = 'F' ; //没有语法错误完全通过但是会出现数据冲突
// Unhandled exception at 0x00d914d8 in Test.exe: 0xC0000005: Access violation writing location 0x00d97838.这是由于什么引起的???
估计”Name"被当作常量放在只读段中,因此不能写
但是我用new 来给str分配5个空间再逐个赋值却没有问题,这是怎么回事???
char * const str = new char[5] ;
str[0] = 'F' ;
str[1] = 'a' ;
str[2] = 'm' ;
str[3] = 'e' ;
str[4] = '\0' ;
这时候str输出为: Fame(知道原因的请告诉我,谢谢)
用const比#define 要好
eg1:
#define MAX 5 //可用下面
const int cmax = 5 ; //替换(编译器在编译时会进行类型检查)
若在class的申明内,而不能在声明内定义类型的初始值,但是却要确定数组的大小时,可用enum 或static const 来代替#define .
eg2:
class CGamePlayers
{
private:
enum { NumTurns = 5} ;
int m_Scores[NumTurns] ;
};
或者
class CGamePlayers
{
private:
static const int NumTurns = 5 ; //声明变量NumTurns,注意static 不能少,因为static 申明的变量在编译的
// 时候就确定其大小,不然的话下面的这句就会出错
int m_Scores[NumTurns] ;
} ;
const int CGamePlayers::NumTurns ; //不能在定义其大小,因为NumTurn的大小为5不能变了。
用inline代替带参数的宏,效率也很高
eg3:
#define CALL_WITH_MAX(lhs , rhs) Fn((lhs) > (rhs) ? (lhs) : (rhs)) //假设函数Fn已定义, 记得带上"()"
int lhs = 5 , rhs = 0 ;
CALL_WITH_MAX(++lhs , rhs) ; //a被累加两次输出lhs = 7
CALL_WITH_MAX(++lhs , rhs+10) ; //a被累加一次输出lhs = 6
不信你去试一下!!为什么当第一个参数大于第二个参数的时候lhs会自增两次,反之lhs却自增一次,我没有明白(我以为在lhs > rhs 的时候他会调用Fn 函数两次,其实两个语句都是调用一次,但是为什么lhs的自增次数会受到rhs大小的影响我没有弄懂(知道的多谢指教)
若用inline却不存在这样有歧义的语句
template<typename T>
inline void Call_With_Max(const T& lhs , const T& rhs)
{
Fn(lhs > rhs ? lhs : rhs) ; //假设Fn函数已定义
}
再调用函数的那边lhs的值两次都是6.(符合我们的正常思维)。
1.对于const修饰的成员函数分为两种观点: logic constness and bitwise constness.
bitwise constness 的观点就是说 :“它不更改对象的任一bit”, 这样一来 , 编译器只要成员变量有赋值的行为即可,这是对只读性的定义,因此,const 成员函数不可以更改对象中任一non- static 成员变量。
eg1:
class TextBlock
{
public:
............
char& operator [] (std::size_t position) ;
const char& operator [] (std::size_t position) const ;
private:
std::string text ;
} ;
TextBlock bt("Jack") ;
std::cout<<bt[0] ; //true , read a non-const TextBlock
bt[0] = 'T' ; //true , write a non-const TextBlock
const TextBlock cbt("Rose") ;
std::cout<<cbt[0] ; //true , read a const TextBlock
cbt[0] = 'L' ; //false , cannot write a const TextBlock !!
char *ptr = &ctb[0] ;
*ptr = 'T' ; //true , not change the adress of ctb[0] , but the content has changed!!
而logical constness的观点是可以改变对象内的某些bits ,例如eg1中的*ptr = 'T' ; 这样还是让ctb中的pText 值改变了!!
若类的常成员函数有non-const 数据成员,并且要进行赋值操作, 那只能用mutable 来修饰non-const 数据成员了
eg2:
class TextBlock
{
public:
TextBlock(char * str = "\0" ) : pText(str) , textLength(0) , lengthIsValid(false)
{}
std::size_t length() const ;
private:
std::size_t textLength ;
bool lengthIsValid ;
} ;
std::size_t TextBlock::length() const
{
if(!lengthIsValid)
{
textLength = std::strlen(pText) ;
lengthIsValid = true ;
}
return textLength ;
}
2.在const 与non-const 成员函数避免重复,这时候需要进行解除const的只读性和加上const 的只读性。
eg3:
class TextBlock
{
public:
TextBlock(string name = "\0") ;
const char& operator [] (std::size_t position ) const ; //常成员函数
char& operator [](std::size_t position) ; //普通成员函数
private:
std::string m_Name ;
} ;
TextBlock::TextBlock(string name):m_Name(name)
{}
const char& TextBlock::operator [] (std::size_t position) const
{
if(position >= m_Name.length() || position < 0) //边界检查
{
throw 0 ; //抛出异常
}
else
{
return m_Name[position] ;
}
}
//利用普通成员函数去调用常成员函数
char& TextBlock::operator[] (std::size_t position)
{
//static_cast<const TextBlock&>类型转换非常对象变成常对象,以此调用常成员函数
//cosnt_cast是把常operator[]的返回常量去掉
//若不这样进行两次转换,会出现无限的递归调用non-const operator[] 函数
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]) ;
}
1.在同一个编译单元中对象的初始化.
class PhoneNumber{.....} ;
class Info
{
public:
Info(const std::string name , const std::string address , const std::list<PhoneNumber> phonenum) : m_Name(name) , m_Address(address) , m_PhoneNum(phonenum)
{}
private:
std::string m_Name ;
std::list<PhoneNumber>
std::string m_Address ;
} ;
(1). 注意若用赋值的方法逐个的赋值给相应的数据成员,这不是初始化,仅仅是赋值,并且这样的话,这个构造函数进行了两次赋值,传参的时候也要一次,所以这样的话导致效率低。
(2).要注意数据成员的初始化的顺序, 它是按照数据成员在类中的声明的顺序来初始化的,与你所写的初始化列表中的顺序无关!!
(3).如果有多个不同的构造函数的时候,并且,它们之间有很多的相同的数据成员时,可调用共同的私有的成员函数Init(),来进行初始化!!
2.若对象在不同的编译单元, 若在一个non-local static 对象要使用另外一个位于不同的编译单元的对象,这个时候就不能保证这个对象被使用前已被初始化,其实不同编译单元的对象之间的初始化顺序是不确定的。这样我们使用designe pattern 的Singleton模式的方法来解决,即:将每一个non-local static对象搬到自己的专属函数内(该对象被声明为static),此类函数返回一个reference指向它所含的对象,这时候当用户使用这些对象时,而不直接涉及这些对象,这时候调用的就是local static 对象了。
eg1:
class FileSystem //文件系统class
{
public:
..........
std::size_t numDisk() const ;
} ;
FileSystem tfs ;
假设客户建立一个管理系统文件的类
class Directory
{
Directory(params) ;
} ;
Direcoty::Directory(params)
{
std::size_t disks = tfs.numDisks() ; //此时使用的tfs对象可能还未初始化,所以有很大的漏洞
}
所以我们用上面的所说的方法来解决此问题:
class FileSystem{.....} ; //类同上
FileSystem& tfs()
{
static FileSystem fs ;
return fs ;
}
class Directory{....} //同上
Directory& DirecTemp()
{
static Directory dirtemp ;
return dirtemp ;
}