侯捷课程笔记(一)(传统c++语法,类内容)

这篇具有很好参考价值的文章主要介绍了侯捷课程笔记(一)(传统c++语法,类内容)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

侯捷课程笔记(一)(传统c++语法,类内容)

2023-09-03更新:
本小节已经完结,只会进行小修改
埋下了一些坑,后面会单独讲或者起新章节讲

最近在学习侯捷的一些课程,虽然其中大部分内容之前也都已经了解过了,不过还是收获颇丰,特别是独立与所谓语法之外的,还有许多与设计相关的。

这一章内容比较简单,也就直接摆出内容吧, 相关重点内容就提一提,大部分都直接掠过了。然后会加入一些自己的侯捷没有讲到的内容。

那就直接开始吧


类设计时头文件防止重复包含

通常用法就是

#ifndef FOO_H__
#define FOO_H__
// ... 主体内容

#endif // FOO_H__

因为这个很常用,但是每次都要写三行,也就有了简化版本

#pragma once

不过这个可能需要编译器支持(大部分肯定都是没问题的)


类内成员函数默认就是inline

inline就是内联,inline允许我们在头文件中定义函数重复包含时而不会发生重复定义问题
由于很多时候我们写类是在头文件中(声明在头文件),这时候,如果成员函数定义在类内,不也就是把函数放在了头文件吗,编译器比较智能,也就默认给类内定义的成员函数自动加上了inline属性
如果我们要把类的成员函数写在类外,就没有inline这个属性了,这时候如果还在头文件,我们就要手动加上inline


类使用初始化列表对成员进行初始化

初始化列表其实也就只有两个需要注意的点:

  1. 初始化的顺序不是按照写的顺序来的,而是按照成员变量定义的顺序来的
  2. 如果父类没有默认构造函数,也就是子类必须显式调用函数初始化父类,这时候也就必须要使用初始化列表初始化父类

类内对函数加上const(常函数)

正常的成员函数是可以更改类的,所以对于一个const属性的类,编译器考虑到这个类不可以更改,也就不允许它调用普通的成员函数,只能调用常函数(带const属性的函数)
一个良好的变成习惯是:对于一些不会更改成员变量/类属性的函数,都应该加上const属性,比如获取变量GetVal类似的函数

ps:使用const实例化一个对象,这个类一定要有用户定义的构造函数


对于第一点,比如下面的代码:

struct A {
  int a;
  int b;
  A() : b(1), a(2) {}
};

在这里初始化的顺序实际上是先执行a=2,再执行b=1,因为a是先定义的那个
看起来好像无所谓,但是下面的代码

struct A {
  int a;
  int b;
  A() : b(1), a(b) {}
};

就会出现a先初始化,但是这时候b还没有初始化的问题


对于第二点,比如

struct Base {
  int a;
  Base(int){}
};
struct Derive : public Base {
  Derive(){};
};

父类没有默认的构造函数,子类必须要也只能通过初始化列表初始化父类

struct Derive : public Base {
  Derive():Base(10){};
};

对于一个类来说,常用的构造函数或者基本架构是什么样的

在侯捷的课程中,常写的是

  • 构造函数
  • 析构函数
  • 拷贝构造函数
  • 拷贝赋值函数

其中如果类中包含指针,常常会自己写拷贝构造和拷贝复制,并会在析构函数中对指针进行处理

而在c++11后引入右值的概念
增加了

  • 移动构造函数
  • 移动复制函数
class MyClass {
 public:
  MyClass();
  MyClass(const MyClass &) = default;
  MyClass(MyClass &&) = default;
  MyClass &operator=(const MyClass &) = default;
  MyClass &operator=(MyClass &&) = default;
  ~MyClass();
};

类中使用友元和重载cout

我们通常会这么写,并且也可以直接把重载函数写在类内

struct A {
 public:
  friend std::ostream& operator<<(std::ostream& os,const A& obj) {
    os << obj.a;
    return os;
  }
 private:
  int a;
};

可以注意几点:

  • 加上friend就可以访问类中的私有变量
  • 输入的os没有加上const,是因为os执行<<会更改内部内容,无法使用const
  • 输入的引用是为了避免不必要的拷贝,obj加上const是加上&后也可以传入右值
  • 返回引用是可以使用链式编程

带有指针的类的结

如果类内带有指针,比如类的构造函数里会new一块内存。

  • 需要在析构函数内释放对应的内存
  • 需要注意深拷贝和浅拷贝的问题
    • 拷贝构造函数和复制构造函数需要重写,重新开辟内存来进行深拷贝
  • 在赋值构造前需要注意是否是自身赋值(自己赋值给自己),需要判断这种特殊情况以防止bug

比如下面这个示例,注意关注一下前面提到的几点

template <typename T>
struct A {
 public:
  A(int n) {
    size = n;
    str = new T[n];
  }
  ~A() { delete[] str; }
  A(const A& obj) { Copy(obj); }
  A& operator=(const A& obj) {
    if (&obj == this) return *this;
    Copy(obj);
    return *this;
  }
  void Copy(const A& obj) {
    if (str) delete[] str;
    size = obj.GetSize();
    str = new T[size];
    std::memcpy(str, obj.str, size * sizeof(T));
  }
  int GetSize() const { return size; }

 private:
  int size = 0;
  T* str = nullptr;
};

内存分配和管理

这一部分其实单独属于一块内容,后面会单独讲


设计模式(单例模式或者其它)

这一部分其实单独属于一块内容,后面会单独讲


类转换成标准类型(operator int())

类可以使用operator转换成一些类型,在编辑器认为转化可以通过编译时会进行转换,当然我们也可以使用static_cast显式转化

struct A {
 public:
  A(int a_) { a = a_; }
  int a;
  operator int() {
    return a;
  }
  operator float() {
    return a;
  }
}
void Test() {
 Aa(10);
 int b=10+static_cast<int>(a);
 int c=static_cast<float>(a)+100;
}

需要注意的是,这个operator函数并不需要返回值,默认返回值类型就是你写的要转换的类型
这里operator不光可以转为内置类型int/float,还可以转换成自己写的类等等

类通过单参数的构造函数自动转化/explicit

c++可以通过operator将类转化成一些类型,同样也支持反向转化,编译器可以自动根据单参数输入的构造函数,将对应的参数的自动执行构造函数构造对象
💡:这里说的单参数,只指可以输入是一个参数的函数,比如一个函数有三个三数,但是后面两个带了默认参数,也满足单参数;又或者只有一个参数,并且这个参数是默认参数,也属于单参数

struct A {
 public:
  A(int a_) { a = a_;}
  int a;
  A operator+(const A& obj) {
    return A(a+obj.a);
  }
};

比如我要执行: A a(10); A b=a+10; 这里重载了+运算符,c++会自动将后面的10调用构造函数,转换成一个类

如果是A b=10+a就不行
这里的+运算符属于内置的int,在这里就不会默认转换
如果要运行这句话,就要用前面的operator int()

operator int() {
  return a;
}

运行的顺序就是先把a转换成int,然后10+整数,然后将加之后的结果转换成A类类型赋值给b

如果我们同时写了单参数的构造函数和operator 类型(),就有可能出现冲突,就比如上面的例子:


struct A {
 public:
  A(int a_) { a = a_;}
  int a;
  A operator+(const A& obj) {
    return A(a+obj.a);
  }
  operator int() {
    return a;
  }
}

void Test() {
 A a(10);
 A b=a+10;
 A c=10+a;
}

这里的A b=a+10;就会有问题,因为冲突了

  • 因为既可以把10调用构造函数转换成类然后执行operator +
  • 也可以把a转换成int,然后加了后再调用构造函数转换成类

这时候我们就可以使用explicit显式的禁止允许单参数的构造函数的默认转换,这样那个构造函数就只允许我们显式调用,而不允许转换了。在示例中也就是不允许10转换成类的类型了


让类表现出指针形式(重载*和->)

我们可以重载*->让类表现出类似于指针的形式,使用示例比如说智能指针和容器的迭代器

struct A {
 public:
  int& operator*() const {
    return *p;
  }
  int* operator->() const {
    return &(this->operator*());
    // return p;
  }
  int* p;
};

在这里,*用来表现解引用,->用来表现指针的成员内容,一般来说->在函数返回值表现形式后还会有一个潜在的->(设计如此)


让类表现出函数形式(重载括号运算符)

这个其实非常常用,比如在ceres库中用于传递代价函数,平时也把这种函数叫做仿函数(模仿函数?)

struct A {
 public:
  template <typename T>
  T operator()(const T& a,const T& b) {
    return a>b?a:b;
  }
};
void Test() {
  A a;
  std::cout << a(10,20);
}

函数模板/类模板/模板模板参数

对于模板来说,关键字classtypename是一样的,为啥有两个?历史原因,不重要了
函数模板:

template <typename T>
void Foo(T t) {}

类模板:

template <typename T>
struct Foo {
  Foo(T t) {}
}

模板模板参数:

template <typename T>
struct Foo {
  T foo;
};

template <typename T,template <typename> class my_class>
struct A {
  my_class<T> class_a;
};

template <typename T1,typename T2>
struct Goo {
  T1 goo1;
  T1 goo2;
};
template <typename T1,typename T2,template <typename,typename> class my_class>
struct B {
  my_class<T1,T2> class_b;
};

void Test() {
  A<int,Foo> a;
  B<int,float,Goo> b;
}

其中:template <typename> class my_class是一个示例,表示输入的是带有一个模板参数的类

  • template表示这是一个模板
  • typename的个数表示对应类的模板个数,需要和传入的模板类对应
  • class 也是个关键词,和typename一样,换成typename也是可以的
  • my_class是这个模板名

在上面的示例中,分别给了一个参数和两个参数的模板类传参示例


模板特化和偏特化

其实特化也就是指定模板的某一个或者任意个参数。
所以特化也就分成全特化和偏特化,全特化就是所有模板参数都指定的特化,偏特化就是只指定部分的特化

有一条规则是函数模板只允许全特化,不允许偏特化,类模板允许偏特化

比如在c++标准库中判断一个参数是否为整数的源码:

// Integer types
template <typename _Tp>
struct __is_integer {
  enum { __value = 0 };
};
template <>
struct __is_integer<bool> {
  enum { __value = 1 };
};
template <>
struct __is_integer<char> {
  enum { __value = 1 };
};
template <>
struct __is_integer<signed char> {
  enum { __value = 1 };
};
template <>
struct __is_integer<unsigned char> {
  enum { __value = 1 };
};
...
后面有其它整数类型包括了intlong,都是和前面一样的
...

在这里列出所有整数类型,只要是整数就会进入到特化的版本,这样只需要根据__value的值就可以判断了
上面这种形式就是全特化

再给个函数模板全特化的例子:

template <typename T1,typename T2>
void Foo(T1 a,T2 b) {
  std::cout << "Test1" << std::endl;
}

template<>
void Foo<int,float>(int a,float b) {
  std::cout << "Test2" << std::endl;
}

void Test() {
  Foo<int,int>(10,20);
  Foo<int,float>(10,20);
  Foo(10,20);
  Foo(10,20.0f);
}

下面演示下类模板偏特化和全特化

template <typename T1,typename T2>
struct A {
  A(T1 a,T2 b) {std::cout << "A" << std::endl;}
};

template <typename T>
struct A<int,T> {
  A(int a,T b) {std::cout << "A<int,T>" << std::endl;}
};

template <typename T>
struct A<float,T> {
  A(int a,T b) {std::cout << "A<float,T>" << std::endl;}
};

template <>
struct A<int,int> {
  A(int a,int b) {std::cout << "A<int,int>" << std::endl;}
};

template <>
struct A<int,float> {
  A(int a,float b) {std::cout << "A<int,float>" << std::endl;}
};

void Test() {
  A("10","20");
  A(10,"20");
  A(10.0f,"20");
  A(10,20);
  A(10,20.0f);
}

在上面的例子中就是给了两个偏特化和两个全特化

通过前面的全特化都可以看出来,全特化一般会伴随template <>出现(因为全都指定了,也就没有T类型了)


auto/右值引用/变长模板 等c++11的内容

这些内容后面都会单独讲,而且其中很多内容在c++14/c++17会有变化和增强
比如右值在c++17后定义更加成熟,变长模板增加了一些展开方式
后面单独讲比在这里粗浅的讲要好


继承/虚函数/虚表

这一块最好也是单独开一块内容来讲,关于虚的内容还是很多的。底层实现也很值得研究。文章来源地址https://www.toymoban.com/news/detail-691662.html


到了这里,关于侯捷课程笔记(一)(传统c++语法,类内容)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 侯捷 C++ part2 兼谈对象模型笔记——7 reference、const、new/delete

    7.1 reference x 是整数,占4字节; p 是指针占4字节(32位); r 代表 x ,那么 r 也是整数 ,占4字节 引用与指针不同,只能代表一个变量,不能改变 引用底部的实现也是指针,但是注意 object 和它的 reference 的 大小是相同的,地址也是相同的 (是编译器制造的假象) reference 通

    2024年02月12日
    浏览(29)
  • C++算法之旅、03 语法篇 | 全内容

    cstdio 有两个函数 printf,scanf 用于输出和输入 iostream 有 cin 读入,cout 输出 使用了std命名空间,cin、cout定义在该命名空间中,不引入空间会找不到导致出错 函数执行入口 ⭐所有 cout、cin 都能用 scanf、printf 替换,但反过来,由于cout、cin效率可能较低会导致超时 ⭐ printf %c 会读

    2024年02月10日
    浏览(45)
  • 侯捷C++(一、面向对象)

    笔记 使用 同类型相加,Fraction类会使用析构函数将4类型转换 给析构函数加上 explicit 表示明确的析构函数,即此函数只进行析构操作(不会被编译器用作他处,如转换) https://www.cnblogs.com/-citywall123/p/12694761.html 指针指针的使用效率不会比一般的指针高,但是它胜在更安全、更

    2024年02月13日
    浏览(37)
  • 【JS笔记】JavaScript语法 《基础+重点》 知识内容,快速上手(四)

    BOM(Browser Object Model): 浏览器对象模型 其实就是操作浏览器的一些能力 我们可以操作哪些内容 获取一些浏览器的相关信息(窗口的大小) 操作浏览器进行页面跳转 获取当前浏览器地址栏的信息 操作浏览器的滚动条 浏览器的信息(浏览器的版本) 让浏览器出现一个弹出

    2024年01月18日
    浏览(43)
  • 【期末复习】北京邮电大学《数字内容安全》课程期末复习笔记(2. 信息隐藏与数字水印)

    【相关链接】 【期末复习】北京邮电大学《数字内容安全》课程期末复习笔记(1. 绪论) 【期末复习】北京邮电大学《数字内容安全》课程期末复习笔记(3. 文本安全) 【期末复习】北京邮电大学《数字内容安全》课程期末复习笔记(4. 多媒体安全) 【期末复习】北京邮电

    2024年02月09日
    浏览(35)
  • STL标准库与泛型编程(侯捷)笔记4

    本文是学习笔记,仅供个人学习使用。如有侵权,请联系删除。 参考链接 Youbute: 侯捷-STL标准库与泛型编程 B站: 侯捷 - STL Github:STL源码剖析中源码 https://github.com/SilverMaple/STLSourceCodeNote/tree/master Github:课程ppt和源码 https://github.com/ZachL1/Bilibili-plus 介绍基于红黑树和hashtable的关联

    2024年01月21日
    浏览(48)
  • STL标准库与泛型编程(侯捷)笔记2

    本文是学习笔记,仅供个人学习使用。如有侵权,请联系删除。 参考链接 Youbute: 侯捷-STL标准库与泛型编程 B站: 侯捷 - STL Github:STL源码剖析中源码 https://github.com/SilverMaple/STLSourceCodeNote/tree/master Github:课程ppt和源码 https://github.com/ZachL1/Bilibili-plus 下面是第二讲的部分笔记:C++标

    2024年01月21日
    浏览(54)
  • STL标准库与泛型编程(侯捷)笔记6(完结)

    本文是学习笔记,仅供个人学习使用。如有侵权,请联系删除。 参考链接 Youbute: 侯捷-STL标准库与泛型编程 B站: 侯捷 - STL Github:STL源码剖析中源码 https://github.com/SilverMaple/STLSourceCodeNote/tree/master Github:课程ppt和源码 https://github.com/ZachL1/Bilibili-plus 下面是C++标准库体系结构与内核

    2024年01月16日
    浏览(43)
  • C++、STL标准模板库和泛型编程 ——适配器、补充(侯捷)

    侯捷 C++八部曲笔记汇总 - - - 持续更新 ! ! ! 一、C++ 面向对象高级开发 1、C++面向对象高级编程(上) 2、C++面向对象高级编程(下) 二、STL 标准库和泛型编程 1、分配器、序列式容器 2、关联式容器 3、迭代器、 算法、仿函数 4、适配器、补充 三、C++ 设计模式 四、C++ 新标准 五、

    2023年04月27日
    浏览(64)
  • C++、STL标准模板库和泛型编程 ——迭代器、 算法、仿函数(侯捷)

    侯捷 C++八部曲笔记汇总 - - - 持续更新 ! ! ! 一、C++ 面向对象高级开发 1、C++面向对象高级编程(上) 2、C++面向对象高级编程(下) 二、STL 标准库和泛型编程 1、分配器、序列式容器 2、关联式容器 3、迭代器、 算法、仿函数 4、适配器、补充 三、C++ 设计模式 四、C++ 新标准 五、

    2023年04月25日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包