C++学习笔记__持续更新
写在前面的话
此文章是鄙人自学C++的体会随笔,鉴于本人只是从业单片机裸机开发的小小工程师,且只有C语言背景,所以此文章的思考角度都是基于C语言或硬件角度,不足之处,敬请大家见谅,如若有片面或错误的地方,还请大家指点出来,鄙人会及时更正,谢谢!
引用:reference
“引用”本质上来看,可以理解为变量的一个别名,对引用所做的读写操作实际上是直接作用于原变量上的。
引用的使用有什么好处?
对比如下两段程序及其输出结果。code__1是通过reference实现两变量变量值的互换。code__2则是通过pointer实现两变量变量值的互换。
容易看出:
- 用引用来实现变量值互换,相对地,可以减少输入*号和&号的操作。
- 对比x和y的地址(以&x,&y表示),用引用的话,x、y的地址与a、b的地址是一样的,而使用pointer的话,x、y的地址与a、b的地址是不一样的。由此可知,使用引用作为形参时,函数调用时MCU并不会申请RAM空间,可以相对地节省系统资源。
// Code__1: sway the value of x and y by reference
void swap( int& x, int& y) {
int t;
t = x;
x = y;
y = t;
}
int main() {
int a, b;
a = 10; b= 5;
swap(a,b);
return 0;
}
如下程序是通过reference实现两变量变量值的互换。
// Code__2: sway the value of x and y by pointer
void swap(int* x, int* y) {
int t;
t = *x;
*x = *y;
*y = t;
}
int main() {
int a, b;
a = 10; b = 5;
swap(&a, &b);
return 0;
}
Key word: decltype
功能描述:在编译时,用开推导一个表达式的类型。目前感觉主要用于泛型编程使用,则做模板。
auto fun1(){return "char";}
auto fun2(){return 10;}
int main() {
decltype( fun1() ) x;
decltype( fun2() ) y;
cout << typeid(x).name() << endl;
cout << typeid(y).name() << endl;
}
其运行结果如下:
char const *
int
如若,在未来的一天,因需求的变更,我们将fun1返回类型由chart改成int,fun2返回类型由int改成chart。main内部的程序并不需要作任何变更。
auto fun1(){return 42;}
auto fun2(){return "char";}
int main() {
decltype( fun1() ) x;
decltype( fun2() ) y;
cout << typeid(x).name() << endl;
cout << typeid(y).name() << endl;
}
其运行结果如下:
int
char const *
inline(内联函数)
inline的基本意义
内联函数的实质是:向编译器建议,将内联函数的函数体,在调用处展开(再通俗来说,就是将内联函数{}号里面的内容粘贴到调用处)。注意,必须强调,inline只是向编译器建议,并不能强制。
那我们为什么需要这个功能呢?
- 使用内联函数,而不直接在调用处写代码是为了使代码更易读更易维护。(其实这一点跟普通的函数一样)。
- 使用内联函数的话,减少了函数调用的时间开销。换句话说,其实是在用空间来换时间。这是inline的主要意义。
扩展
- 当inline函数需要被多个文件调用时,inline的声明和定义,都需要放在头文件中。因为编译器编译时,是以c文件为单位进行逐个编译obj,所以每个c文件的编译是独立的,该c文件用到的外部函数都在编译时预留一个符号,只有等到所有obj生成后链接时才给这些符号地址(链接脚本决定地址)。因此,如果不把inline函数的定义放在头文件的话,其他c文件编译时只会看到这个函数的声明而无法知道它的实体,就无法做到内联展开。
- 当inline函数放在头文件中时,最好在inline前面加上static。则static inline function_name()。原因是:inline只是向编译器建议,并不能强制,所以inline函数有可能不在调用处展开,而是被调用处真正的调用,此时如果该文件中也存在与inline函数同名的函数,此会产生冲突。在inline前面加上static则可以避免这种冲突,增加程序的可移植性。
断言: assert
assert是什么?
assert是一条用于检测假设是否成立的语句。
用法:
assert( bool_exper);//若bool_exper为假则中断程序;若bool_exper为真则继续招行后面语句。
用途
主要用于帮助调试逻辑bug(部分替代断点或单步调试)
#include<array>
#include<iostream>
std::array a{1, 2, 3};
for( size_t i= 0; i <= a.size(); i++ ){
assert( i < 3);//断言:i必须小于3,否则失败
std::cout << a[ i ];
std::cout << ( i == a.size() ? "":" ");
}
注意:
assert一般来说,只在Debug模式下有效,在Release模式中不起作用。因为Debug模式下,编译器不会定义NDEBUG,所以assert起作用。
当然,我们也可以通过手动#undef NDEBUG使assert在Release模式中也起作用。
小技巧
//使用assert时,可以采用以下方法,这样,程序中断报错时,可以直接看到错
//误的信息,其原理是字符串常量是一个非空地址,所以字符串常量的bool值
//常为true,则assert的bool值只由前面的语句决定
assert( (i<3) && "i must be less than 3");
模板函数: template function
在C语言编程中,总会遇到这样的一些情况,就是要一连写功能一样,但是数据类型不同的函数。比如:
int add(int a, int b){
return (a+b);
}
double add(double a, double b){
return (a+b);
}
针对这个问题,C++提供一个友好的解决方案:template。例程如下:
//template和typename是关键字,elemtype是自定义的临时放置类型代称
//程序要实现的功能是,实现任意两个相同数据类型的数相加,并返回运算结果
template<typename elemtype>
elemtype add( const elemtype a, const elemtype b ){
return ( a+b );
}
void main(void){
cout << "the average of double result is " << add(1.1, 2.2) << endl;
cout << "the average of int result is " << add(1, 2) << endl;
}
程序运行结果:
构造函数(constructor)
构造函数的函数句必须与class的名称相同,并且,构造函数不能有返回,则使是void。一个class中可以定义多个构造函数(则构造函数可以重载)。
构造函数的主要功能是对class进行初始化。
#include <iostream>
using std::cout;
using std::endl;
int i1 = 0; int i2 = 0;
class Stack
{
public:
Stack(){ //constructor
i1 += 1;
class_elem = i1;
cout << "The constructor without parameter was called" << endl;
cout << "i1 = " << i1 << endl;
cout << "i2 = " << i2 << endl;
cout << "class_elem = " << class_elem << endl;
cout << endl;
}
Stack( int i) {//constructor
i2 = i;
class_elem = i2;
cout << "The constructor with int parameter was called" << endl;
cout << "i1 = " << i1 << endl;
cout << "i2 = " << i2 << endl;
cout << "class_elem = " << class_elem << endl;
cout << endl;
}
int class_elem ;
};
int main() {
Stack temp; //The constructor was called while defining new class
Stack temp1 = 10;//The constructor was called while defining new class
Stack temp2(50);//The constructor was called while defining new class
return 0;
}
构造函数初始化列表
初始化列表
初始化列表有两种形式:参考下列例程中的y and z。使用列表初始化的好处是:列表初始化不允许“窄化”,编译时会报错。而传统的赋值方式,只会报警告。
int main() {
int x = 1.1;
int y = {1.1};
int z {1.1};
}
构造函数初始化列表
虽然我们平常编程时都习惯且建议在定义变量的同时进行初始化。虽然一般情况下,我们先定义、再赋值并不会产生错误,但是,对于常量和引用,先定义再赋值,则会产生错误。
那么,对数据成员,我们应该如何实现先定义再赋值呢?方法如下:
class Stack
{
public:
Stack(int ii);
void output();
private:
int i;
const int ci;
int& ri;
};
Stack::Stack(int ii):i(ii), ci(ii), ri(i) {//此处的“:i(ii), ci(ii), ri(i) ”是对Stack的初始化
//Stack::Stack(int ii):i{ii}, ci{ii}, ri{i} {//但“i(ii), ci(ii), ri(i) ”的小括号容易被当成函数,建议用{ }
i = ii;
}
void Stack::output()
{
cout << "i is " << i << endl;
cout << "ci is " << ci << endl;
cout << "ri is " << ri << endl;
}
int main() {
Stack temp2(50);
temp2.output();
}