平时记录的一些C++语法知识点。
C++
-
成员函数指针
成员指针并不指向一个具体的内存位置,它指向的是一个类的特定成员,而不是指向一个特定对象的特定成员,最直接的理解是将其理解为一个偏移量。
-
assert()函数
- assert宏原型定义在#include
中。 - 其作用是先计算表达式,如果其值为假,那么它先向stderr打印一条出错信息,然后通过调用abort来终止程序运行。
- 缺点是:频繁的调用会极大的影响程序的性能,增加额外的开销。
- assert宏原型定义在#include
- define宏定义多行函数
- 函数调用会带来额外的开销,它需要开辟一片栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆栈。这种开销不仅会降低代码效率,而且代码量也会大大增加。函数的参数必须被声明为一种特定的类型,所以它只能在类型合适的表达式上使用,我们如果要比较两个浮点型的大小,就不得不再写一个专门针对浮点型的比较函数。
- 宏定义在代码规模和速度方面都比函数更胜一筹;宏定义可以用于整形、长整形、单浮点型、双浮点型以及其他任何可以用“>”操作符比较值大小的类型,也就是说,宏是与类型无关的。
- 使用
#define
声明多行宏函数与声明单行宏函数没有本质区别; - 多行声明时,回车换行前要加上字符
\
,即\[enter]
,注意字符\
后要紧跟回车键,中间不能有空格或其他字符。 - 在Linux操作系统中
\[enter]
称为跳脱字符,意思是一行写不完的时候可以使用跳脱字符换行,但对于操作系统而言,它认为你并没有换行。
- 使用
-
函数模板与类模板
-
类模板例子
template <class T> //声明一个模板,虚拟类型名为T。注意:这里没有分号。 class Compare //类模板名为Compare { public : Compare(T a,T b) { x=a;y=b; } T max( ) { return (x>y)?x:y; } T min( ) { return (x<y)?x:y; } private : T x,y; };
-
函数模板例子
template <class T> T Max(const T a, const T b) { return (a>b)?a:b; }
-
声明类模板时需要增加一行:
template <class 类型参数名>
template
意思是”模板“,是声明类模板时必须写的关键字。在template后面的尖括号的 内容为模板的参数表列,关键字class(也可以用typename
)表示其后面的时类型参数。 -
虚拟类型参数名T:
在建立类对象时,如果将实际类型指定为int型,编译系统就会用in取代所有的T,如果指定为float型,就用float取代所有的T。这样就能实现”一类多用“。
-
类模板外定义函数
template <class T> T Compare<T>::max() { return (x > y)? x : y; }
-
类模板的实例化
Compare <int> cmp(4, 7); Compare <int> cmp(4.0, 7.0);
-
模板特化
在模板中某些参数类型需要进行特殊处理。
template <class T> class Compare { public: bool IsEqual(const T& arg, const T& arg1); }; //已经不具有template的意思了,已经明确为float template<> class Compare<float> { public: bool IsEqual(const double& arg, const double& arg1); }; //已经不具有template的意思了,已经明确为double template<> class Compare<double> { public: bool isEqual(const double& arg, const double& arg2); }; template <class T> bool Compare<T>::IsEqual(const T& arg, const T& arg1) { cout<<"Call Compare<T>::IsEqual"<<endl; return (arg == arg1); } bool Compare<float>::IsEqual(const float& arg, const float& arg1) { cout<<"Call Compare<float>::IsEqual"<<endl; return (abs(arg - arg1) < 10e-3); } bool Compare<double>::IsEqual(const double& arg, const double& arg1) { cout<<"Call Compare<double>::IsEqual"<<endl; return (abs(arg - arg1) < 10e-6); }
-
模板偏特化
偏特化指提供一份template定义式,而其本身仍为templatized;也就是说,针对template参数更进一步的条件限制所设计出来的一个特化版本。
模板偏特化与模板特化的区别在于:模板特化以后,实际上其本身已经不是templatized,而偏特化,仍然带有templatized。
// 一般化设计 template <class T, class T1> class TestClass { public: TestClass() { cout<<"T, T1"<<endl; } }; // 针对普通指针的偏特化设计 template <class T, class T1> class TestClass<T*, T1*> { public: TestClass() { cout<<"T*, T1*"<<endl; } }; // 针对const指针的偏特化设计 template <class T, class T1> class TestClass<const T*, T1*> { public: TestClass() { cout<<"const T*, T1*"<<endl; } };
-
-
memmove()
函数:头文件<string.h>
中void *memmove(void *str1, const void *str2, size_t n)
从
str2
复制n
个字符到str1
,但是在重叠内存块方面,memmove()
是比memcpy()
更安全的方法。如果目标区域和源区域有重叠的话,
memmove()
能够保证源串在被覆盖之前将重叠区域的字节拷贝的到目标区域中,拷贝后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和memcpy()
功能相同。 -
assert(0)作用
- 捕捉逻辑错误。可以在程序逻辑必须为真的条件上设置断言。除非发生逻辑错误,否则断言对程序无任何影响。即预防性的错误检查。
- 在认为不可能执行到的情况下加一句assert(0),如果运行到此,代码逻辑或条件就可能有问题。
- 程序没写完的标识,放个assert(0)调试运行时执行到此为报错中断,好知道成员函数还没写完。
-
显式转换(显式地将对象强制转换成另一种类型)
-
static_cast
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
double slope = static_cast<double>(j) / i;
当需要把一个较大的算数类型赋值给较小的类型时,static_cast非常有用。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不在乎潜在的精度损失。
-
const_cast
只能改变运算对象的底层const。去掉”const“性质,编译器就不再阻止我们对该对象进行写操作。
const char* pc;//pc是个常量 char* p = const_cast<char*>(pc);//正确:但是通过p写值是未定义的行为
const char* cp; char* q = static_cast<char*>(cp);//错误:static_cast不能转换掉const性质 static_cast<string>(cp);//正确:字符串字面值转换成string类型 const_cast<string>(cp);//错误:const_cast只改变常量属性
-
reinterpret_cast
reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。
int* ip; char* pc = reinterpret_cast<char*>(ip);
必须牢记pc所指的真是对象是一个int而非字符,如果把pc当场普通的字符指针使用就可能在运行时发生错误。
string str(pc);//可能导致异常的运行的行为。
-
dynamic_cast
-
-
using
using声明使特定的标志符可用,using编译指令使整个名称空间可用。
- 前缀++(++i)、后缀++(i++) 的区别
-
++i:先增加后引用,先让i加1,然后在i所在的表达式中使用i的新值。
++i是将i的值先+1,然后返回i的值。
A operator ++() //前缀++ { i=i+1; return *this; }
-
i++:先引用后增加。先让i所在的表达式中使用i的当前值,然后让i加1。
i++是先将i的值存到寄存器里,然后执行i+1,然后返回寄存器里的值。
A operator ++(int) //后缀++ { A t=*this; //先保存一份变量 ++(*this); //执行i+1 return t; //返回原值 }
-
注意:调用代码的时候,要优先使用前缀形式,除非确实需要后缀形式返回原值。
-
-
explicit
关键字explicit
用来声明类构造函数是显式调用的,而非隐式调用,所以只用于修饰单参构造函数。 以下是
explicit
关键字的注意点:-
explicit
关键字只能用于类内部的构造函数声明上。 -
explicit
关键字作用于单个参数的构造函数。 -
explicit
关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换。
//未加explicit时的隐式类型转换 class Circle { public: Circle(double r) : R(r) {} Circle(int x, int y = 0) : X(x), Y(y) {} Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {} private: double R; int X; int Y; }; int _tmain(int argc, _TCHAR* argv[]) { //发生隐式类型转换 //编译器会将它变成如下代码 //tmp = Circle(1.23) //Circle A(tmp); //tmp.~Circle(); Circle A = 1.23; //注意是int型的,调用的是Circle(int x, int y = 0) //它虽然有2个参数,但后一个有默认值,任然能发生隐式转换 Circle B = 123; //这个算隐式调用了拷贝构造函数 Circle C = A; return 0; }
//加了explicit关键字后,可防止以上隐式类型转换发生 class Circle { public: explicit Circle(double r) : R(r) {} explicit Circle(int x, int y = 0) : X(x), Y(y) {} explicit Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {} private: double R; int X; int Y; }; int _tmain(int argc, _TCHAR* argv[]) { //一下3句,都会报错 //Circle A = 1.23; //Circle B = 123; //Circle C = A; //只能用显示的方式调用了 //未给拷贝构造函数加explicit之前可以这样 Circle A = Circle(1.23); Circle B = Circle(123); Circle C = A; //给拷贝构造函数加了explicit后只能这样了 Circle A(1.23); Circle B(123); Circle C(A); return 0; }
-
-
extern “c”
实现C和C++的混合编程。
在C++源文件中的语句前面加上
extern "c"
,表明它按照C的编译和链接规则来进行。这样在C的代码中就可以调用C++的代码。 -
const
修饰成员函数的作用-
声明一个成员函数的时候用
const
关键字是用来说明整个函数是“只读函数”,也就是说明这个函数不会修改任何数据成员。 - 为了声明一个const成员函数,把
const
关键字放在函数括号的后面。声明和定义的时候都应该放const关键字。 - 任何不会修改数据成员的函数都应该声明为
const
类型。如果在编写const
成员函数时,不慎修改了数据成员,或者调用了其它非const
成员函数,编译器将指出错误,可以提高程序的健壮性。
#include class temp { public: temp(int age); int getAge() const; void setNum(int num); private: int age; }; temp::temp(int age) { this->age = age; } int temp::getAge() const { age += 10;//错误!getAge()为只读函数,不能修改任何数据成员。 return age; }
-
-
类static成员变量与类static成员函数
-
类static成员变量,只能在类体外进行初始化:
数据类型类名::静态数据成员名=初值
如果不对静态成员变量进行显式初始化,系统缺省初始为0。
不能在类中初始化。不能用初始化列表初始化。
-