局部静态对象:
有时需要令函数中局部变量的生命周期贯穿函数调用及之后的时间,则将其修饰为static,local static object在程序第一次经过对象定义语句时被初始化,直到程序终止时才被销毁
统计自己被调用了多少次的函数:
size_t count_calls(){
static size_t cnt=0;
return ++cnt;
}
const形参和实参:
当用实参初始化const形参时会忽略掉顶层const,当形参有顶层const时,传给它常量对象或者非常量对象都是可以的,则可能会导致下面的问题:
void func(const string s);
void func(string s); //错误,重定义了func
begin(arr)
和end(arr)
是标准库中的两个函数,分别返回arr首元素的指针和尾元素下一位置的指针
数组引用形参:
void print_arr(int(&arr)[10]) {
for (auto elem : arr) {
cout << elem << endl;
}
}
在C++中,数组的大小是构成数组类型的一部分
传递多维数组:
void print(int(*matrix)[10], int rowSize);
//等价定义
void print(int matrix[][10], int rowSize);
含有可变形参的函数:
- 形参类型相同但数量不确定:使用
initializer_list
类型,其中的元素永远是常量 - 形参类型不同:使用可变参数模板
- 还有一种特殊的形参类型:省略符,用来传递可变数量的实参,一般只用于和C函数交互的接口程序
输出错误信息函数:
void print_error_msg(initializer_list<string> init_list) {
for (auto p = init_list.begin(); p != init_list.end(); ++p) {
cout << *p << " ";
}
cout << endl;
}
C语言的省略符传参:
#include <stdarg.h>
void variable_args_func(const char* str, ...) {
//可变参数列表类型
va_list args;
//确定不定参数的位置
va_start(args, str);
//按顺序获取参数并指明类型
char* s = va_arg(args, char*);
int i = va_arg(args, int);
double d = va_arg(args, double);
//带有可变参数的函数返回
va_end(args);
printf("str:%s,s:%s,i:%d,d:%lf\n", str, s, i, d);
}
int main(){
variable_args_func("hello", "I'm s", 10, 370.0);
return 0;
}
省略号只能出现在形参列表中的最后一个位置
在C++中省略符传参应仅用于与C程序交互,大多数类类型的对象在传递给省略符形参时都无法正确拷贝
左值和右值:
C++的表达式不是左值就是右值
当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)
一个重要准则:在需要右值的地方可以用左值代替,但不能把右值当成左值使用
几种常见的左值:
decltype()
作用于左值,会得到一个引用类型
int a=0;
int* pa=&a;
decltype(*pa) //得到int&类型,解引用运算符生成左值
decltype(&pa) //得到int**类型,取地址运算符生成右值
函数的返回类型决定了函数调用是否是左值。调用一个返回引用的函数得到左值,其他返回类型得到右值。所以我们能对返回类型为非常量引用的函数的结果赋值。
重载和const形参:如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时const是底层的
Record lookup(Account&);
Record lookup(const Account&); //这两个函数构成重载
Record lookup(Account*);
Record lookup(const Account*); //这两个函数构成重载
constexpr
函数:能用于常量表达式的函数,constexpr是C++11新添加的特征,目的是将运算尽量放在编译阶段,而不是运行阶段
- 函数的返回值类型及所有的形参类型都得是字面值类型
- 函数体中必须只有一条return语句
编译器会把对constexpr函数的调用替换成其结果值,为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数
内联函数和constexpr函数通常定义在头文件中
断言assert()的行为依赖于一个预处理变量NDEBUG的状态,如果定义了NDEBUG,则assert什么也不做。默认状态下是没有定义NDEBUG的,此时assert将执行运行时检查
还可以用NDEBUG定义自己的调试代码:
#ifndef NDEBUG
cerr<<__func__<<":something wrong"<<endl;
#endif
一个打印错误信息的宏函数:
#define error_msg(msg) \
std::cout << "error: " << __FILE__ << ": in function " << __func__ << " at line " << __LINE__ <<", msg: "<<msg<<std::endl;\
函数指针:
bool lengthCompare(const string&, const string&);
bool (*pf)(const string&, const string&);
//两种等价的赋值
pf=lengthCompare;
pf=&lengthCompare;
//三种等价的调用
bool b1=pf("hello", "world");
bool b2=(*pf)("hello", "world");
bool b3=lengthCompare("hello", "world");
使用重载函数指针时,上下文必须清晰地界定到底应该选用哪个函数
函数指针作为形参:
//两种等价的声明
void useBigger(const string& s1, const string& s2, bool pf(const string& s1, const string& s2));
void useBigger(const string& s1, const string& s2, bool (*pf)(const string& s1, const string& s2));
//调用时可以直接把函数作为实参使用
useBigger(s1, s2, lengthCompare);
可以使用类型别名定义函数类型和函数指针类型:
//两种等价的函数类型
typedef bool Func(const string&, const string&);
typedef decltype(lenghtCompare) Func2;
//两种等价的函数指针类型
typedef bool (*FuncP)(const string&, const string&);
typedef decltype(lengthCompare)* FuncP2;
注意decltype()
返回的函数类型,而非函数指针类型
lambda表达式(非第6章内容)
https://zhuanlan.zhihu.com/p/384314474
派生类只继承基类中的公有成员和保护成员,而不继承私有成员.说法是否正确?
首先这个说法是不正确的,子类是继承父类的私有成员的,只是一般情况下不可访问。那么如何让子类访问到父类的私有成员呢——使用友元:
class A {
private:
int m = 90;
friend class B;
};
class B : public A {
public:
void visit_m() {
printf("%d\n", this->m);
}
};
int main() {
A a;
B b;
b.visit_m();
}
虚析构问题
首先明确一般继承关系下构造与析构顺序:构造函数先父后子,析构函数先子后父
class A {
public:
A() { cout << "A constructor" << endl; }
~A() { cout << "A destructor" << endl; }
};
class B : public A{
public:
B() { cout << "B constructor" << endl; };
~B() { cout << "B destructor" << endl; };
};
int main() {
B* b = new B();
delete b;
}
运行结果:
A constructor
B constructor
B destructor
A destructor
如果类中有其他类的成员对象:
class A {
public:
A() { cout << "A constructor" << endl; }
~A() { cout << "A destructor" << endl; }
private:
const static int a = 90;
};
class C {
public:
C() { cout << "C constructor" << endl; }
~C() { cout << "C destructor" << endl; }
};
class B : public A{
public:
B() { cout << "B constructor" << endl; };
~B() { cout << "B destructor" << endl; };
C c;
};
int main() {
B* b = new B();
delete b;
}
则会先调用基类的构造函数,然后调用成员对象的构造函数,最后调用派生类自身的构造函数:
A constructor
C constructor
B constructor
B destructor
C destructor
A destructor
当引入多态时:
class A {
public:
A() { cout << "A constructor" << endl; }
~A() { cout << "A destructor" << endl; }
};
class B : public A{
public:
B() { cout << "B constructor" << endl; };
~B() { cout << "B destructor" << endl; };
};
int main() {
A* b = new B();
delete b;
}
如上所示,如果基类的析构函数不是虚函数,那么,像这种定义一个基类的指针,指向一个派生类的对象,当你delete这个基类的指针时,它仅调用基类的析构函数,并不调用派生类的析构函数。
A constructor
B constructor
A destructor
原因在于:调用的destructor被编译器设置为基类中的版本,这就是所谓的静态多态,函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为destructor函数在程序编译期间就已经设置好了。
然而如果基类的析构函数是虚函数:
class A {
public:
A() { cout << "A constructor" << endl; }
virtual ~A() { cout << "A destructor" << endl; }
};
class B : public A{
public:
B() { cout << "B constructor" << endl; };
~B() { cout << "B destructor" << endl; };
};
int main() {
A* b = new B();
delete b;
}
delete基类的指针时,不仅会调用基类的析构函数,还会调用派生类的析构函数。
A constructor
B constructor
B destructor
A destructor
而调用的顺序是先调用派生类的析构函数、然后调用基类的析构函数。
在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数,我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
如果再基类中不能给出虚函数有意义的实现,使用纯虚函数即可virtual int area() = 0;
,这个类也就随之变成了抽象类
非const的静态数据成员不能在类内初始化,因为静态成员属于类,而不是属于某个特定的对象,它是由该类的所有对象共享的,因此不能在类的构造方法中初始化
虚函数不能是static类型的函数
基类中说明了虚函数后,派生类中起对应的函数可以不必说明为虚函数
C++中并非所有运算符都可重载,如.
和::
就不可重载
const对象只能调用const类型成员函数,例如下面的调用是非法的:
class A {
public:
A() { cout << "A constructor" << endl; }
~A() { cout << "A destructor" << endl; }
void alter_a(int x) {
a = x;
}
private:
int a = 90;
};
int main() {
const A a;
a.alter_a(1);
}
而下面的调用是合法的:
class A {
public:
A() { cout << "A constructor" << endl; }
~A() { cout << "A destructor" << endl; }
void print_a() const {
cout << a << endl;
}
private:
int a = 90;
};
int main() {
const A a;
a.print_a();
}
C++的隐式类型转换:
下面哪种情况下,B不能隐式转换为A?
class B : public A{};
class A : public B{};
class B {operator A();};
class A {A(const B&);};
答:第二个,A作为子类可能比B多出一些数据成员,A转化为B时可以直接截断,但是B转化为A时无法填充数据,故无法实现B向A的隐式转换
最后一个通过拷贝构造实现了隐式转换
关于高效计算Hamming weight:
int hamming_weight(int x){
int countx = 0;
while (x) {
countx++;
x = x & (x – 1);
}
return countx;
}