c++学习记录整理

c++学习记录整理

初学c++的一个学习记录,对c++的设计思路进行阐述。

声明、定义和初始化

在c++中,声明、定义和初始化是三种不同的概念。
声明一个对象的含义是,把这个对象添加到符号表,告诉编译器这个对象是有效的,但是,还没有给这个对象分配具体的空间。
定义一个对象的含义是,不但声明了这个对象,还给这个对象分配了具体的内存空间。
初始化一个对象的含义是,不但定义了这个对象,还调用了这个对象的初始化函数,给这个对象赋予了具体的值。
声明、定义和初始化是三个递进的过程。
只声明不定义的情形主要有以下几种情况:

  1. 用extern修饰的变量。用extern修饰的变量只是告诉编译器有这个变量只是一个引用,真正的定义在文件外部。
  2. 可以在类中声明一个函数,在类外定义,这时声明和定义是分离的。
  3. 类中的成员变量只能算作声明,只有初始化一个对象时才算初始化。(至于类的静态成员变量,必须单独在类外初始化。)
  4. 一个类在没完成定义时(大括号结束),只能算声明了这个类。这时,可以声明一个指向该类的指针,但不能声明一个该类的实例化成员。

只定义不初始化的情景只发生在内置类型上,对于自定义的类的对象,定义的同时一定会初始化。

c++声明规则

c++声明过程中,讲究调用和声明的一致性。因此,如果按照下面的方式声明:

int (*a)[];

是为了表达:

(*a)[0]

的类型是int。
同时,考虑到const int a,是为了表达a是常量;int * const a是为了表达a是常量。

作用域与编译单元

直接定义在文件中的变量被称为全局变量,拥有全局作用域。
定义在函数体或循环体中的变量被称为局部变量,拥有局部作用域。
没有名字,只能临时存在的变量被称为临时变量,临时变量的作用域只存在与表达式中,表达式结束自动销毁。可以通过绑定延长临时变量的作用域。(后面会展开分析)
c++中,一个.cpp文件对应一个编译单元,每个编译单元单独编译,编译完成后再链接到一起。每个编译单元中,完成了定义的全局变量和类的成员函数会放到符号表中,供其它编译单元中声明的同名符号使用。
一般来说,所有编译单元的所有符号表中,声明可以有任意次,但定义只能有一次。但是,用inline修饰的成员函数可以定义多次,在类中定义的函数默认有inline属性。
值得注意的,由于类的静态成员变量必须要用全局变量的形式初始化一次,因此它也具有全局作用域。
下面给出一个非常好玩的例子:

// example 1
// source.cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct foo {
 static int a;
 int c{ 5 };
 void func();
 void func_();
};
int foo::a = 4;
int main()
{
 foo().func();
 foo().func_();
}

// foo.cpp
#include <iostream>
using namespace std;
struct foo {
 static int b;
 int d{ 6 };
 void func();
 void func_();
};
int foo::b = 100;
void foo::func() {
 cout << d << endl;
}
void foo::func_() {
 cout << b << endl;
}

这个例子在visual studio2019上的运行结果是:
5
100
请思考一下为什么。

内存管理

c++的内存分为5个部分:

  1. 全局/静态变量区;
  2. 常量区;
  3. 栈;
  4. 堆;
  5. 自由存储区;

全局/静态变量区、常量区中变量的地址在编译期就已经确定,栈、堆、自由存储区中变量的地址在运行期才确定。
全局/静态变量区中,存储全局变量和静态变量,静态变量。
常量区中,存储常量。
栈中,存储局部变量和临时变量。
堆中,存储malloc分配的变量。
自由存储区中,存储new分配的变量。
关于常量:
有的常量会保存在常量区,例如字符串常量;有的常量直接在编译期就完成了替换,例如整数常量。不要试图对常量进行修改,这样会导致未定义行为。
关于静态变量:
用static修饰的类成员变量和成员函数具有全局作用域,但静态成员函数没有this指针。
用static修饰的全局变量被称为静态全局变量,具有文件作用域,只在编译单元有效。
用static修饰的局部变量被称为静态局部变量,作用域不变。
静态变量具有唯一性。
关于栈:
这个栈其实就是数据结构中的栈,先进后出,通过栈来完成函数调用。
关于堆和自由存储区:
一般来说,编译器的operator new往往通过调用malloc实现,这时自由存储区也就是堆。但是,c++标准没有强制要求一定要通过malloc实现,可以采用不同的方法实现operator new,这时自由存储区和堆不同。
最后,一个可执行程序还有一个名为代码段的部分,用来存储各个函数的代码。

临时变量和右值引用

可以直接定义一个变量(常引用,右值引用也行)并绑定到临时变量上,这时,临时变量直接生成在这个变量分配的内存上,不会产生任何额外的开销。绑定后临时变量的作用域和绑定的变量一致。虽然部分类型还提供了移动构造函数,但是把临时变量绑定到一个变量上不会调用移动构造函数。另外,使用static_cast强制转换会产生绑定。
通常的右值引用其实是一个左值,引用绑定后和普通的左值没有任何区别,仅仅表示这个变量是将亡值,可以通过破坏它来完成移动构造函数。只有用static_cast强制转换(也就是move)才真正具有右值引用的类型。

// example 2
#include <iostream>
#include <string>
using namespace std;
struct foo {
    int val;
    foo(int x): val{x} {
        cout << "construct " << val << endl;
        cout << "the ptr is " << this << endl;
    }
    ~foo() {
        cout << "destruct " << val << endl;
    }
    foo operator+(const foo& other) {
        return foo(val + other.val);
    }
};
int main()
{
    // foo(2) + foo(3);
    foo a = foo(2) + foo(3);
    cout << "--------------" << endl;
    return 0;
}

关于引用的一些废话

左值引用其实是通过指针实现的。
T&&根据引用折叠规则实现了通用引用。
通过值传递函数参数会执行拷贝构造函数。
左值引用可以绑定左值和左值引用;右值引用可以绑定右值和move强制转换的右值引用。
值和常引用可以绑定任何左值、右值、左值引用、右值引用。
使用通用引用时,由于会丢失右值属性,可以通过forward实现完美转发。

对象的初始化

每一个自定义类的对象都会自动初始化。先初始化基类,再依次初始化成员变量,初始化列表可以重载这个过程。我们既可以通过小括号,也可以通过大括号来调用初始化函数,一般没有区别。但是,大括号还有初始化列表的含义。所以vector v{3}和vector v(3)会产生不同的结果。另外,还可以通过大括号给出默认的初始化函数,如example 1。
值得一提的是,初始化过程中,编译器会自动优化operator=。
如果对象有虚函数,初始化过程中,先初始化基类,基类的构造函数完成后,再把基类的虚函数表指针换成子类的虚函数表指针。
对于多继承的函数,拥有多个虚函数表,也对应的有多个虚函数指针。
如果有虚继承,还会初始化一个虚基类表。(gcc编译器中,如果虚基类没有成员变量,编译器会无视virtual命令)

智能指针与对象的所有权

智能指针通过RAII机制管理资源。
shared_ptr和unique_ptr都表示对对象所有权的占有。生成shared_ptr传递给其它函数时,相当于分享了对象的所有权(weak_ptr也是一样,因为有lock方法)
多线程时最好不要传this指针,如果要传this指针必须保证this指针只绑定了一次智能指针。

函数

c++中,一共有三种函数:

  1. 普通函数
  2. 成员函数
  3. 仿函数

相比普通函数,成员函数隐含了一个this指针作为成员变量。一般把静态成员函数也看作普通函数。
仿函数则通过构建一个struct来存储当前的环境变量,通过重载operator()来模拟函数。
c++中,lambda表达式其实是仿函数的语法糖。
三种函数的类型表达方式大不相同,但是可以通过function和bind进行转换,给出统一的接口。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值