C++学习笔记__持续更新

写在前面的话

此文章是鄙人自学C++的体会随笔,鉴于本人只是从业单片机裸机开发的小小工程师,且只有C语言背景,所以此文章的思考角度都是基于C语言或硬件角度,不足之处,敬请大家见谅,如若有片面或错误的地方,还请大家指点出来,鄙人会及时更正,谢谢!

引用:reference

“引用”本质上来看,可以理解为变量的一个别名,对引用所做的读写操作实际上是直接作用于原变量上的。

引用的使用有什么好处?

对比如下两段程序及其输出结果。code__1是通过reference实现两变量变量值的互换。code__2则是通过pointer实现两变量变量值的互换。

容易看出:

  1. 用引用来实现变量值互换,相对地,可以减少输入*号和&号的操作。
  2. 对比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只是向编译器建议,并不能强制。
那我们为什么需要这个功能呢?

  1. 使用内联函数,而不直接在调用处写代码是为了使代码更易读更易维护。(其实这一点跟普通的函数一样)。
  2. 使用内联函数的话,减少了函数调用的时间开销。换句话说,其实是在用空间来换时间。这是inline的主要意义。

扩展

  1. 当inline函数需要被多个文件调用时,inline的声明和定义,都需要放在头文件中。因为编译器编译时,是以c文件为单位进行逐个编译obj,所以每个c文件的编译是独立的,该c文件用到的外部函数都在编译时预留一个符号,只有等到所有obj生成后链接时才给这些符号地址(链接脚本决定地址)。因此,如果不把inline函数的定义放在头文件的话,其他c文件编译时只会看到这个函数的声明而无法知道它的实体,就无法做到内联展开。
  2. 当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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值