C++Primer读书笔记-第06章-函数

局部静态对象:

有时需要令函数中局部变量的生命周期贯穿函数调用及之后的时间,则将其修饰为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);

含有可变形参的函数:

  1. 形参类型相同但数量不确定:使用initializer_list类型,其中的元素永远是常量
  2. 形参类型不同:使用可变参数模板
  3. 还有一种特殊的形参类型:省略符,用来传递可变数量的实参,一般只用于和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;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《C Primer》是一本针对C语言初学者的经典教材,第五版的第六主要介绍了函数的基本概念和用法。 在第六中,教材首先介绍了函数的定义和调用。函数是具有独立功能的代码块,可以通过函数名来调用。函数函数头、参数、函数体和返回值组成。函数头包括函数类型、函数名和参数类型,参数用于接收传递给函数的值,返回值用于将结果返回给调用者。 接着,教材详细介绍了函数的参数传递方式,包括按值传递、按引用传递和按指针传递。按值传递是指将参数的值复制给形参,按引用传递是指将参数的引用传递给形参,而按指针传递是将参数的地址传递给形参。不同的传递方式在函数内部对参数进行操作时,会对应不同的结果。 此外,教材还讲解了函数的返回值和函数的调用顺序。函数的返回值类型由函数头中的类型确定,可以是任意类型。当函数需要返回多个值时,可以使用结构体或指针进行返回。函数的调用顺序决定了函数执行的顺序,即哪个函数先执行,哪个函数后执行。 在函数的实现过程中,教材介绍了函数的定义和声明、局部变量和全局变量、递归等内容。通过这些知识点的学习,读者可以了解到函数的具体细节和一些实践技巧。 总的来说,第五版的第六通过清晰的语言和丰富的例子,循序渐进地讲解了函数的基本概念和用法。学完这一,读者将能够理解函数的定义、调用、参数传递、返回值和实现等方面的知识,为之后的学习和实践打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值