首页 > 编程语言 >C++学习笔记

C++学习笔记

时间:2024-03-11 18:11:06浏览次数:30  
标签:const 函数 int brand 笔记 学习 Computer C++ 构造函数

第一章 认识C++

1.1 命名空间

1.1.1 命名空间的基本格式

  • 命名空间是一个由用户自己定义的作用域,在不同作用域中定义相同变量,不会冲突。
  • 命名空间中可以存放以下类型,这些定义/声明在结构体中的内容成为实体
    • 变量
    • 常量
    • 函数(可以是定义或声明)
    • 结构体
    • 模板
    • 命名空间(可以嵌套定义)
namespace wd{
    int number = 0;
    struct Foo
    {
        int val;
    }
    void display();
}//end of namespace wd

1.1.2 命名空间的使用方式

  • 命名空间一共有三种使用方式

    • using编译指令

      • 将该空间中的全部实体一次性引入到程序中

        using namespace std;
        
    • 作用域限定符

      • 每次要使用某个名称空间中的实体时,都直接加上"::"

        namespace wd
        {
        	int number = 10;
            void display()
            {
                //cout,endl都是std空间中的实体,所以都加上'std::'命名空间
                std::cout << "wd::display()" << std::endl;
            }
        }
        
    • using声明机制

      • using声明机制的作用域是从using语句开始,到using所在的作用域结束。
      • 在同一作用域内用using声明的不同的命名空间的成员不能有同名的成员,否则会发生重定义。(同一作用域内不可重名)
      //同一作用域内不同空间不可重名
      #include <iostream>
      //using声明机制
      using std::cout;
      using std::endl;
      //作用域限定符
      namespace wd
      {
      int number = 10;
      void display()
      {
      cout << "wd::display()" << endl;
      }
      }//end of namespace wd
      using wd::number;
      using wd::display;
      int main(void)
      {
      cout << "wd::number = " << number << endl;
      wd::display();
      }
      

1.1.3 匿名命名空间

  • 匿名空间可以不定义名字,该空间中的实体,其他文件无法引用,它只能在本文件的作用域内有效。
  • 在匿名空间中创建的全局变量,具有全局生存周期,却只能被本空间内的函数访问,是static变量的有效替代手段。
namespace{
    //其中vall只可以被本命名空间内的函数访问,是全局变量。
    //外部空间不可以访问
    int vall = 10;
    void func();
}

1.1.4 命名空间的嵌套及覆盖

#include <iostream>
using namespace std;

int number = 1;
namespace wd
{
    int number = 10;
    namespace luo
    {
        int number = 100;
        void display()
        {
            cout << "wd::luo::display()" << endl;
        }
    }//end of namespace wd
    void display(int number)
    {
        cout << "形参number = " << number << endl;
        cout << "wd命名空间中的number = " << wd::number << endl;
        cout << "luo命名空间中的number = " << wd::luo::number << endl;
    }
}//end of namespace wd
int main(void)
{
    using wd::display;
    display(number);
    return 0;
}
  • 只有在使用命名空间的时候,参数才是命名空间内部得参数
  • 嵌套命名空间得时候,想使用嵌套命名空间中得参数,要嵌套使用::

1.1.5 命名空间的使用方法

  • 在已命名的空间中定义变量,而不是直接定义外部全局变量或者静态变量

  • 如果开发了一个函数库或者类库,提前将其放在一个名称空间中

  • 对于using声明,将其作用域设置为局部而不是全局

  • 不要在头文件中使用using编译指令,这样使得可用名称变得模糊,容易出现二义性。

  • 包含头文件的顺序可能会影响程序的行为,如果非要使用using编译指令,建议放在所有#include预编译指令后。

    比如

    #include <iostream>
    using namespace std;
    //std命名空间就是头文件iostream中函数的声明位置
    

1.2 const关键字的用法

  • const 与宏定义的区别

    • 编译器处理方式不同
      • 宏定义是在预处理阶段展开,做字符串的替换
      • const是常量在编译时候分配空间
    • 类型和安全检查不同
      • 宏定义没有类型,不做任何类型检查
      • const常量具有具体的类型,在编译器会执行类型检查
        • const常量必须要进行初始化
  • 常量指针与指针常量

    • const pointer
      指针可以改变指向,不可以通过指针修改常量的值

      const int *p1 = &num;
      int const *p1 = &num;
      //const直接修饰*p1表示*p1不可变,即变量的值不可变
      
    • pointer to const

      指针不可以改变指向,可以通过指针改变变量

      int* const p3 = &number;
      //const直接修饰p3,表示p3的值不可以改变,即指针的地址不可以改变
      

1.3 new/delete表达式

1.3.1 new/delete的作用

  • new/delete在c++中用来开辟和回收堆空间
  • malloc/free在c语言中用来开辟和回收堆空间

虚拟内存空间分布

代码段 存储代码指令 数据段 存储全局静态变量 堆空间 动态内存管理,由低->高生长 栈空间 存储局部变量,由低<-高生长 内核区域用户态->内核态

1.3.2 用new/delete开辟空间

//开辟一个元素空间
int * p = new int(1);
//释放一个元素的空间
delete p;
//开辟一个数组空间
int * p = new int[10]();
//释放一个数组的空间
delete []p;
  • new/delete自动初始化,自动分配空间大小;malloc不可以

1.4 引用

1.4.1 引用的概念

  • 变量名是一段连续内存空间的别名,引用就是把这段连续的内存空间再取一个别名

    void test0{
        int a = 1;
        int & ref1 = a;
        //ref2是一个不完整的引用声明,它没有指向任何变量
        //一个完整的引用一定要进行初始化不然会报错
        int & ref2;
    }
    
  • 引用要注意的点

    • &不再是取地址符号,而是引用符号。
    • 引用类型需要和被引用的值的类型保持一致
    • 声明引用时候一定要初始化
    • 一旦绑定到某个变量之后,就不会再改变其指向

1.4.2 引用的本质

引用就是被限制的指针,占据一个指针的内存,存放一个地址,一旦被绑定就不可以再改变

1.4.3 引用作为函数参数

  • 通过形参改变改变实参的值
    • 指针:不好操作,比较复杂
    • 引用:有更好的可读性和实际意义
    • 值传递:副本进行传递,不划算

1.4.4 引用作为函数的返回值

当以引用作为函数的返回值的时候,返回的变量其生命周期一定大于函数的生命周期,函数执行完毕时,返回的变量还存在。

int temp;
int & func1(){
    temp = 100;
    return temp;
}
  • 返回的类型为引用要注意的点

    • 不能返回局部变量的引用

    • 不能在函数内部返回new分配的堆空间变量的引用。
      如果返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量

      该引用所在的空间就无法释放,会造成内存泄漏。

//number是一个局部变量,func3结束之后就被销毁了,返回错误
int & func3(){
	int number = 1;
    return number;
}
//在函数内部申请的地址,在函数结束之后就释放了,但是却返回了引用(地址),导致内存泄漏
int & fun4(){
    int * pint = new int(1);
    return *pint;
}
  • 引用总结
    • 引用主要是用于参数传递时,解决副本,指针操作可读性差的问题
    • 通过对const的使用,保证了引用传递的安全性。

第二章 类与对象基础

2.1 面向对象的思想

面向对象的三大基本特征是:

  • 封装:隐藏内部实现
  • 继承:复用现有代码
  • 多态:改写对象行为

2.2 类的定义

2.2.1 概念速览

类的定义:

  • 数据成员: 相当于现实世界中的属性
  • 成员函数: 对数据的操作
  • 注意:
    • 定义类名遵循大驼峰规则
    • 定义成员函数名遵循小驼峰规则
    • 定义数据线成员名在前面加上下划线
class MyClass{//类的定义
//……
void myFunc(){} //成员函数
int _a; //数据成员
};//一定要有分号
//类也可以先声明,后完成定义
class MyClass2;//类的声明
class MyClass2{//类的定义
//……
};//分号不能省略

访问修饰符:

  • public: 公有的访问权限,在类外可以通过对象直接访问公有成员。
  • protected: 保护的访问权限,派生类中可以访问,在类外不能通过对象直接访问。
  • private: 私有的访问权限,在本类之外不能访问,比较敏感的数据设为private。
  • 注意:
    • class定义中如果在成员定义(或声明)之前没有任何访问修饰符,其默认的访问权限为私有

struct和class的对比:

C语言中的struct可以封装数据,但是不能隐藏数据,而且成员不能是函数。

C++中中的struct对C中的struct做了拓展,基本等同于class,默认访问权限是public。

class的默认访问权限是private。

typedef struct{
    int number;
    char name[25];
    int score;
    void (*p)();
    //void print();//error
}

成员函数的定义:

成员函数定义的形式: 成员函数可以在类内部完成定义,也可以在类内部只进行声明,在类外部完成定义。

多文件联合编译时可能出现的错误: 如果在头文件中对函数进行定义,头文件内容在每个源文件都会复制一份,每个源文件都会生成一份目标文件,可能会导致在链接阶段出现相同函数定义的情况,导致重定义错误。

class Computer {
public:
//成员函数
void setBrand(const char * brand);//设置品牌
void setPrice(float price);//设置价格
void print();//打印信息
private:
//数据成员
char _brand[20];
float _price;
};

void Computer::setBrand(const char * brand){
strcpy(_brand, brand);
}

void Computer::setPrice(float price){
_price = price;
}

解决多文件联合编译错误的方法:

  • 解决方法1:在成员函数的定义前加上inline关键字,说明类内部定义的成员函数就是inline函数

    inline void Computer::setBrand(const char * brand){
    strcpy(_brand, brand);
    }
    
    inline void Computer::setPrice(float price){
    _price = price;
    }
    
  • 解决方法2:将成员函数的定义放在类的内部(和方法一本质上是一样的效果)

  • 解决方法3:函数声明放在头文件,函数定义放在实现文件中(一个.c一个.h)。

    最常用的方法就是方法3

构造函数(如何创建一个对象):

构造函数: 和类同名的函数称为构造函数,构造函数可以重载,如果没有构造函数自动用无参构造函数。

初始化列表: 利用初始化列表对对象的数据成员完成初始化,数据成员初始化的顺序与其声明的顺序保持一致,与他们在初始化列表中的顺序无关(但初始化列表一般保持与数据成员声明的顺序一致)

class Point {
public:
Point(){}
Point(int ix,int iy = 0);
//可以在声明中设定参数的默认值
private:
    //c++11之后也可以初始化数据成员,但是一般情况下还是在初始化列表中对数据成员初始化
int _ix;
int _iy;
};
inline void Point::Point(int ix, int iy)
    :_ix(ix)
    ,_iy(iy)
{
cout << "Point(int,int)" << endl;
}

指针数据成员初始化:

类的数据成员中有指针时,意味着创建该类的对象时要进行指针成员的初始化,需要申请堆空间。

class Computer {
public:
Computer(const char * brand, double price)
    //+1加的是\0
: _brand(new char[strlen(brand) + 1]())//这里新new的内存要交给析构函数进行回收
, _price(price)
{
strcpy(_brand,brand);
}
private:
char * _brand;
double _price;
};
void test0(){
Computer pc("Apple",12000);
}

对象所占空间大小:内存对齐机制,为什么要进行内存对齐:

  • 1.平台原因: 不是所有的而硬件平台都能访问任意地址上的任意数据的。
  • 2.性能原因: 64位系统默认以8个字节块大小进行读取,对齐可以防止系统读取一块儿数据的时候多次访问内存。
    • 规则:按照类中占空间最大的数据成员大小的倍数对齐。
    • 注意:如果数据成员中有数组类型,会按照除数组以外的其他数据成员中最大的那一个的倍数 对齐。
class C{
int _c1;
int _c2;
double _c3;
};
//sizeof(C) = 16
class D{
int _d1;
double _d2;
int _d3;
};
//sizeof(D) = 24

内存对齐机制

析构函数(如何销毁一个对象):

析构函数的概念:

  • 定义:对象在销毁时调用的函数
  • 作用:清理对象的数据成员申请的资源(堆空间)
  • 形式:
    • 没有返回值,即使是void也没有。
    • 没有参数。
    • 函数名与类名相同,在类名之前要加上一个(不加就是构造函数)
  • 性质:
    • 析构函数不可以重载(构造函数可以)
    • 析构函数默认情况下,系统会自动提供一个
    • 当对象被销毁时,会自动调用析构函数
      • 不建议手动调用析构函数,因为容易导致各种问题,应该让析构函数自动被调用。

自定义析构函数:

  • 什么时候需要自定义析构函数: 当数据成员中有指针时,创建一个对象,会申请堆空间,销毁对象时默认析构不够用了 (造成内存泄漏),此时就需要我们自定义析构函数。在析构函数中定义堆空间上内存回 收的机制,就不会发生内存泄漏。

    class Computer {
    public:
    Computer(const char * brand, double price)
    : _brand(new char[strlen(brand) + 1]())
    , _price(price)
    {}
    ~Computer()
    {
    if(_brand){
    delete [] _brand;
    _brand = nullptr//设为空指针,安全回收
    }
    cout << "~Computer()" << endl;
    }
    private:
    char * _brand;
    double _price;
    };
    

构造函数和析构函数的调用时机(重点):

  • 全局定义的对象: 在主函数main接收程序控制权之前,就调用构造函数创建全局对象,在整个程序结束时,自动调用全局对象的析构函数

  • 局部定义的对象: 每当程序流程到达该对象的定义处就调用构造函数,在程序离开局部对象的作用域时调用对象的析构函数

  • static定义的静态对象: 当程序流程到达该对象定义处调用构造函数,在整个程序结束时调用析构函数。

  • new创建的堆对象: 每当创建该对象时调用构造函数,在使用delete删除该对象时,调用析构函数。

    Computer *p1 = new Computer("Lenovo",6500);
    P1->print();
    delete P1;
    P1 = nullptr;
    

拷贝构造函数(如何复制一个对象):

拷贝构造函数的定义: 用一个变量初始化另一个变量

拷贝构造函数的形式: 类名(const 类名 &)

  1. 拷贝构造函数也是一个构造函数
  2. 该函数用一个已经存在的同类型的对象,来初始化新的对象,即对对象本身进行复制

拷贝构造函数的形式探究:

  1. 拷贝构造函数是否可以去掉引用符号:

    不可以。

    如果拷贝函数的参数中去掉引用符号,进行拷贝时调用拷贝构造函数的过程中会发生“实参和形参都是对象,用实参初始化形参”(拷贝构造第二种调用时机),会再一次调用拷贝构 造函数。形成递归调用,直到栈溢出,导致程序崩溃。

  2. 拷贝构造函数是否可以去掉const:

    在复制临时对象内容的时候会报错:

    Computer pc3 = Computer("ASUS",5000);
    
    • 加const的第一个用意: 为了确保右操作数的数据成员不被改变。
    • 加const的第二个用意: 为了能够复制临时对象的内容,因为非const引用不能绑定临时变 量(右值)。

拷贝构造函数的性质:

  1. 使用默认的拷贝构造函数,会进行浅拷贝,即两个指针指向同一片内存

    浅拷贝

    Point(const Point & rhs)
    : _ix(rhs._ix)
    , _iy(rhs._iy)
    {}
    
  2. 当要进行拷贝构造的类的数据成员有指针(要申请堆空间)的时候,要将拷贝构造显示写出,采用深拷贝的方式,先申请空间,再复制内容。
    深拷贝

    Computer::Computer(const Computer & rhs)
    : _brand(new char[strlen(rhs._brand) + 1]())
    , _price(rhs._price)
    {
    strcpy(_brand, rhs._brand);
    }
    

拷贝构造函数的调用时机(重点):

  1. 当使用一个已经存在的对象初始化另一个同类型的新对象时。

  2. 当函数参数(实参和形参的类型都是对象),形参与实参结合时(实参初始化形参)。

    为了避免这次不必要的拷贝,可以使用引用作为参数。

    //调用拷贝构造
    void func(Comptuer rhs){
        rhs.print();
    }
    //引用避免调用拷贝构造
    void func(Computer & rhs){
        rhs.print();
    }
    void test1(){
        Computer pc("apple",2000);
        func(pc);
    }
    
  3. 当函数的返回值是对象,执行return语句时(编译器有优化)。

    为了避免这次多余的拷贝,可以使用引用作为返回值,但一定要确保返回值的生命周期大于函数的生命周期

    Computer pc3("Acer",5400);
    //调用拷贝构造函数
    Computer func2(){
    	return pc3;
    }
    //直接引用,避免调用拷贝构造函数
    Computer & func2(){
        return pc3;
    }
    

赋值运算符函数:

赋值运算符函数的执行时机: 在执行 pt1 = pt2; 该语句时, pt1 与 pt2 都存在,所以不存在对象的构造,这要与 Point pt2 =pt1; 语句区分开,这是不同的。

Point pt1(1, 2), pt2(3, 4);
pt1 = pt2;//赋值操作

赋值运算符函数的形式: 类名& operator=(const 类名 &)

Point & Point::operator=(const Point & rhs)
{
_ix = rhs._ix;
_iy = rhs._iy;
    //返回本对象的this指针
    return *this;
}

赋值运算符函数的形式探究:

  1. 赋值运算符函数的返回必须是一个引用吗?

    可以不是但是会造成一次多余的拷贝,增加不必要的开销。

  2. 赋值操作符函数的返回类型可以是void吗?

    可以是但是无法处理连续赋值

  3. 赋值操作符函数的参数一定要是引用吗?

    可以不是但是会造成一次多余的拷贝,增加不必要的开销

  4. 赋值操作符函数的参数必须是一个const引用吗?

    无法避免在赋值运算符函数中修改右操作的内容,不合里,而且当右操作数为临时对象的时候,不是const的引用会报错。

赋值运算符函数的定义:

  • 如果对象的指针数据成员申请了堆空间,默认的赋值运算符函数就不够用了,是浅拷贝

    浅拷贝

    Computer & operator=(const Computer & rhs){
    this->_brand = rhs._brand;
    this->_price = rhs._price;
    return *this;
    }
    
  • 直接进行深拷贝是否可行?

    不可行,会发生内存泄漏。

    因为创建对象的时候就已经申请了空间,只有先把创建对象时候申请的空间释放,然后再申请空间才不会造成内存泄漏。

    深拷贝

    Computer & operator=(const Computer & rhs){
    if(this != &rhs){
    delete [] _brand;
    _brand = new char[strlen(rhs._brand)]();
    strcpy(_brand,rhs._brand);
    _price = rhs._price;
    }
    return *this;
    }
    
  • 总结——四步走(重点):

    • 考虑自复制问题
    • 回收左操作数原本申请的堆空间
    • 深拷贝(以及其他的数据成员的复制)
    • 返回*this

this指针:

this指针的本质:this指针指向本对象,this是一个隐藏的指针,可以在类的成员函数中使用,它可以用来指向调用对象。

this指针存在那里: 寄存器——编译器在生成程序时加入了获取对象首地址的相关代码,将获取的首地址存放在了寄存器 中,这就是this指针。

this指针的生命周期: 开始于成员函数的执行开始,结束于成员 函数的执行结束。如果成员函数是通过一个已经销毁或未初始化的对象调用的,this指针将是悬挂的,它的 使用将会是未定义行为。

Point & operator=(const Point & rhs){
this->_ix = rhs._ix;
this->_iy = rhs._iy;
cout << "Point & operator=(const Point &)" << endl;
return *this;
}

成员函数中可以加上this指针,展示本对象通过this指针找到本对象的数据成员。但是不要 在参数列表中显式加上this指针,因为编译器一定会在参数列表的第一位加上this指针,如果显式再给一个,参数数量就不对了。

三合成原则

拷贝构造函数、赋值运算符函数、析构函数,如果需要手动定义其中的一个,那么另外两 个也需要手动定义。

标签:const,函数,int,brand,笔记,学习,Computer,C++,构造函数
From: https://www.cnblogs.com/aCuteRabbit/p/18066611

相关文章

  • 一文学会JDBC实现java和mySQL的数据连接(尚硅谷学习课程代码+笔记+思路总结)
    JDBC是指数据库连接技术,用于java连接mySQL等数据库。本文详细介绍了尚硅谷课程中JDBC的学习内容和补充知识。概述java语言只提供规范接口,存在于java.sql.javax.sql包下,然后数据库软件根据java提供的规范实现具体的驱动代码(jar)jar包是java程序打成的一种压缩包格式,只要导入就......
  • Vue学习笔记44--mixin混入
    mixin混入:可以理解为是代码的一种重构复用一个混入对象可以包含任意组件选项(如data、methods、mounted等等)。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。示例一:局部混合示例局部混入就是在单独的vue组件中引入了mixin混入对象 Student.vue......
  • FREE RTOS学习随记
    最近开始学习实时操作系统提升知识面,刚好STM32的开发板附赠了FREERTOS的学习手册,就据此来学习吧,所谓RTOS,即Real-TimeOpreatingSystem,实时操作系统,这个系统最大的好处就是通过一系列的算法,实现了多任务的灵活切换。单片机本身是单核的,只能单条代码依序执行,所以这个实时也只是伪......
  • 【刷题笔记】LeetCode-53 最大子数组和
    题目:给你一个整数数组nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。示例1:输入:nums=[-2,1,-3,4,-1,2,1,-5,4]输出:6解释:连续子数组 [4,-1,2,1]的和最大,为 6。示例2:输入:nums=[1]输出:1示例3:输入:nums=[5,4,-1,7,8]输出:23......
  • error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft
       Defaultingtouserinstallationbecausenormalsite-packagesisnotwriteableCollectingPyQt5-sipUsingcachedPyQt5_sip-12.13.0.tar.gz(123kB)Installingbuilddependencies...doneGettingrequirementstobuildwheel...donePreparing......
  • 技术笔记(6)SourceTree Push到github时报错
    技术笔记(6)SourceTreePush到github时报错remote:SupportforpasswordauthenticationwasremovedonAugust13,2021.Pleaseuseapersonalaccesstokeninstead.​即无法通过输入账号密码的方式来验证,需要使用个人token来验证。昨晚搜到了很多无效方法,搞得有点头痛了,记......
  • 技术笔记(5)MMORPG
    技术笔记(5)MMORPG希望实现的功能或目标:搞定UI系统搞定人物选择系统‍学习笔记:RawImage在登陆界面中负责将某些特定模型渲染出来,比如:人物、怪物UIMask是可以拦截穿透的,即点击上层覆盖的UI界面时,下层是点不到的UISystem类字典:privateDictionary<string,Ba......
  • 载谭 Binomial Sum 学习笔记
    对于微分有限的生成函数\(F(x)\),有一个生成函数\(G(x)\),以及数列\(a\),如果对于\(0\lek\len\),我们已知\(\displaystyle\sum_{i=0}^na_i[x^i]G(x)^k\),那么我们能够在\(\Theta(n)\)的时间复杂度内求出\(\displaystyle\sum_{i=0}^na_i[x^i]F(G(x))\)。设\(c=[x^0]......
  • 面试学习——JVM
    讲讲JVM原理,JVM是做什么的JVM怎么判断一个对象可以销毁了类加载机制、有哪些类加载器jvm双亲委派机制,为什么要用双亲委派机制?垃圾回收器、垃圾回收算法一个对象什么时候会进入新生代、什么时候会进入老年代什么时候会发生younggc,什么时候会发生oldgc,什么时候会发生fu......
  • 【机器学习】机器学习创建算法第1篇:机器学习算法课程定位、目标【附代码文档】
    机器学习(算法篇)完整教程(附代码资料)主要内容讲述:机器学习算法课程定位、目标,K-近邻算法,1.1K-近邻算法简介,1.2k近邻算法api初步使用定位,目标,学习目标,1什么是K-近邻算法,1Scikit-learn工具介绍,2K-近邻算法API,3案例,4小结。K-近邻算法,1.3距离度量学习目标,1欧式距离,2......