首页 > 编程语言 >侯捷C++上期笔记

侯捷C++上期笔记

时间:2024-05-20 15:51:56浏览次数:23  
标签:上期 const 函数 C++ String complex 侯捷 data 变量

1.头文件和类、构造函数

c++和c最大的不同在于C++会把数据以及处理数据的函数放到一个对象objects(class)里,不同类之间不可见,类似C中结构体struct

防止头文件重复声明

ifndef complex

//当之前没有声明过这个头文件时,才进行后续的声明

define complex

(2)补充定义
(1)类定义
(3)类功能解释

endif

(1)类定义
class complex
{
public:
complex (double r=0,double i=0)//参数默认值
: re(r),im(i)//构造函数才能使用的特殊赋值写法
{}//{re=r,im=i}当然可以,但效率不高
//不带指针不需要析构函数
complex() : re(r),im(i) {}
//构造函数经常重载(同名但函数不同)
//但这两个构造complex的函数不能同时存在,
//因为第一个变量要默认值,第二个没有变量
//当编译器编译时如果没有变量,不知道该调用哪一个

    double real() const { return re;}
    double real(double x) {return re+x;}
    //这样两个就可以
    //const的含义此处不解释,下一节说
private:
    double re,im;
    //私有的变量,main就不能直接访问
    //需要访问只能在类定义部分写一个公开的调用函数
    //私有的变量只能在定义函数内部调用
friend complex                 

}
2.参数传递与返回值

单例设计模式singleton
可以通过把构造函数放到private里实现,这样做会实现:
外界main不能调用构造函数,所以无法创建新的对象
只有类定义函数可以创建对象,可以做到一个类只有一个对象

当函数不会改变数据时,要加上const
const还有其他的用法
const int x=2;
//表示这个变量是常量,数据无法被改变

const complex c1(1,2)
cout<<c1.real()<<endl;
//会出现问题,如果real()函数在设计时没有加入const
//在打印c1时,调用real函数,编译器会认为
//一个常量即将调用一个可能会改变其的函数,就会报错
//所以当函数不会改变数据时,要加上const
double real() const {return re;}

参数传递分为 by value 和 by reference 两种
两者区别在于by value 会将变量值传函数,机器底层就是将变量复制到函数的所占据的内存堆栈
后者只会传出一个地址,类似但不是一个指针,可以提升传递速度
大型项目推荐都使用by reference 类型的传递

此外还有,如果传递的是地址,类似引用类的传递
在函数中对变量进行修改,是会改变该变量的
如果不希望修改,需要加上const
以 double 为例,现在我们传 const double&

既然传入参数有两类,那么传出参数的返回值当然也有两类
返回值也可以带const

返回值传递也分为 by value 和 by reference 两种

by reference本质上是地址,但在使用时当做值来用a.re,而不是a->re
double& printt(double a,const double& b){
a = b + 1;
return a;
}
//输入参数,输出返回值都是采用 by reference的形式
//而且不会修改原来的b值,使用const
//其实一般不会用到double&,一般只可能对类,如complex使用

友元可以突破private的限制,直接访问类数据
friend complex& _doapl(complex*,const complex&)
同一类的不同objects互为友元,所以同一类的函数可以调用自己的private部分

但不是所有的情况都可以使用by reference 的模式,主要是const的使用要谨慎

3.操作符重载与临时对象
c++允许操作符重载,比如把+=重载
所有的成员函数其实都包含一个隐含的参数this, 表示调用这个成员函数的变量

complex& complex::operator +=(this,const complex& r){
return _doapl(this,r);
}//成员函数

c3+=c2+=c1;
//存在连续计算的可能性,所以就需要返回complex&
//如果不需要连续计算,可以不返回,
//因为引用型的调用会直接改变值,不需要再返回

complex& _doapl(complex* a,const complex& b){
a.re=a.re+b.re;
a.im=a.im+b.im;
return a;//返回a指针的值,即a所指的complex变量
}
//为什么return的是变量
a,函数却可以写complex&来接收
//这在语法里是允许的,传递者无需知道接收者以refrence形式接收

使用inline 可以在类定义里简单声明,然后在类定义外部写成员函数,看起来比较清晰

全局函数与成员函数
inline complex&
complex::operator +=(this,const complex& r){
...//包含类名complex::
}//在类定义函数外面写的时候就要加上complex::

inline double
imag (const complex& x)
{
...//不含类名,是全局函数
}

inline complex
operator + (const complex& x,const complex& y)
{
return complex(x.re+y.re,x.im+y.im);
}
//typename(a),创建临时对象,就是没有名称的变量
//只能在返回到时候用,因为只能存在一行代码的时间
//这里不能用complex&,因为函数创建了一个新的对象,必须要一个新地址

//关于为什么要把operator +写成非成员函数
是因为,成员函数都有一个隐含的左值变量,函数一定是要对左值使用的
c=a+b对右边两值使用,c+=a就包含对左值的使用

特殊的操作符(<<输出)有特殊的设计方法,操作符重载必须注意

//练习负数类的书写,读,理解,写

4.三大函数(拷贝构造,拷贝赋值,析构)
当我们创建一个新的类时,对应这个类会有一系列操作函数,例如+-=
基本的数据类型都有自己的操作函数
所以我们也需要给新建的类创建操作函数
此外编译器会有默认的=函数(构造、拷贝函数),就是直接进行位位对应的复制(浅拷贝)
在之前的负数类中,不含有指针变量,全是值,所以可以使用默认的
但在含有指针的类中,需要创建一个新的变量,而不是复制一个地址,所以需要自己写(深拷贝)

class String{
public :
String (const char* cstr =0);//构造情况复杂,不采用初始化方式
String (const String& str);//拷贝构造
////////strlen和strcpy函数对字符串,字符都可以操作的
String& operator= (const String& str);//拷贝赋值
~String(); //析构函数
char* get_c_str() const { return m_data;}//不改变,中间加上const
private :
char* m_data;
}

inline //尝试将函数放入到类定义函数中
String::String(const char* cstr=0){
if (cstr){
m_data = new char[strlen(cstr)+1];
//含有隐含的变量this.m_data
//strlen表示字符串长度函数
strcpy(m_data,cstr);
//strcpy表示字符串复制函数
///////strlen和strcpy函数都是对字符串操作的
else{
m_data = new char[1];
m_data = '\0'; //创建的是一个指针,赋值时需要加
}
}
}

inline
String::String(const String& str){//const 表示在这个函数里不改变这个变量
m_data = new char[strlen(str.m_data)+1];
strcpy(m_data,str.m_data);
///////不能用字符串的长度去构建字符指针
}//没有自我拷贝构造,因为一个变量只能构造一次

inline
String& String::Operator= (const String& str){
if(this==&str) return str;
//发现自我赋值,直接返回原变量
//返回变量,以便出现连续赋值的情况
else{
delete[] m_data;
//如果自我赋值不写,这里杀死空间后,下一句就没法赋值了
//因为两个变量其实用的是同一个空间,杀死后就都消失了
m_data = new char[strlen(str.m_data)+1];
strcpy(m_data,str.m_data);
return *this;
//this是一个指针,我们需要返回变量
}
}

inline
String:: ~String(){
delete[] m_data; //new和delete的用法放到了后面介绍
}

//写成全局函数,是为了可以 cout << str 这样使用
//如果写成成员函数,在调用时是 str << 的用法,类似++
ostream& operator<<(ostream& os,const String& str){
os << str.get_c_str();
return os;
}

5.堆栈Stack、Heap

Stack是某个作用域内的一个内存空间,需要手动释放空间
m_data = new char[strlen(str.m_data)+1];

Heap由操作系统分配的空间
String s;
在离开作用域后就会被操作系统释放(函数内部的参数变量)

static String s;
静态变量在程序结束时才释放

全局变量:不是在某个函数内部定义的变量
全局变量在程序结束时才释放

内存是有限的,必须在使用后释放

new先分配空间,再调用构造函数
(分配指定大小的空间,将空间转化成指定类型,变量命名以及值的写入)

delete先调用析构函数,再释放空间
(调用析构函数删除类包含的基础变量或者类指针所指的值,再释放类变量空间除变量外的空间)
String:: ~String(){
delete[] m_data; //new和delete的用法放到了后面介绍
}
先删除了m_data,再释放String的空间

在分配变量空间时,不仅仅是变量占用的空间,还有空间头尾的字段(记录空间长度)、调试字段
这个总空间的长度必须是16字节的倍数,组成原理里有介绍为什么

new [ ]与delete[ ] 搭配
当new的空间存放的是指针时,delete就需要把在释放指针空间之前,把指针所指的变量空间也释放,这需要调用多次delete,就用delete[ ] 形式表示

当类定义中含有指针时,有许多地方需要注意
必须写深拷贝的构造和赋值函数,必须要写析构函数,必须在析构函数中使用delete[ ]

何时不可以使用by reference的形式:
当函数返回的值存放的空间是一个新的空间时,就只能用by value的形式
这个新的空间是在函数内部创建的,就需要存下来
如果返回值保存的空间在执行函数前就存在,就无需保存,直接把地址传出去即可
by reference本质上就是使用地址来传递信息
在函数内部创建的函数在,函数执行结束后就消失了

const 函数,是在函数不改变其包含的变量值的情况下才加
而且必须加
因为如果不加,当常量调用该函数时会报错
因为常量不会信任一个可能改变其变量值的函数

6.static 静态,唯一一份,但可以修改,不是常数

静态成员属于类,所有的类变量共用一个静态成员变量
实例成员属于类变量,每个类变量有自己的实例成员变量

静态变量必须要在类定义外部写一个初始化定义,那个时候才会分配内存

//只有静态变量才能这样写,换成普通变量就不行了

// 以银行账户类为例
class Account{
public:
static double m_rate;
static void set_rate(const double& x){
m_rate=x;///////////
// 普通的成员变量是不能这样写的
// 因为对于普通的成员变量,每个类变量都对应一个成员变量
// 所以应该在建立类变量之后,才能有
}
};

double Account::m_rate = 8.0;///////////
// 默认值
// 我不明白为什么静态变量不能直接在类里设置初始值

int main(){
Account::set_rate(5.0);/////////
// 在还没有建立类变量之前,就可以设置m_rate的值
Account a;
a.set_rate(7.0);
}

基于静态变量设计的单例模式
class A {
public:
static A& getInstance();
steup(){
...
}
private:
A();
A(const A& rhs);//构造函数放到了私有里,不允许外部创建类变量
...
}

A& A::getInstance(){
static A a;
return a;
}
// 把类定义的函数放到外面
// 只有在调用这个函数时才会真的去创建这唯一的一个类变量,更合理
// 如果把这个函数直接写到类里面,在创建类的时候就会创建这个唯一的类变量
// 把类定义的函数放到外面
// 只有在调用这个函数时才会真的去创建这唯一的一个类变量,更合理
// 如果把这个函数直接写到类里面,在创建类的时候就会创建这个唯一的类变量

7.类模板,通用的类

将类中涉及的变量类型换成变量,而不是固定的
在编译时会生成多份类结构
//一个通用型的类
template
//template是固定的,typename也是固定的,typename可以换成class,两者是等价的
class complex{
public:
complex(T r=0,T i=0) : re(r),im(i) {
};
complex& operator += (const complex&);
T real() const{//函数不会改变调用函数的类变量值
return re;
};
T imag() const{
return im;
};
private:
T re,im;
friend complex& _doapl (complex*,const complex&);
//友元函数可以直接使用类的私有成员函数和变量
}

int main(){
complex c1(2.5,2.5);//使用时把需要定义的类型放到<>里
complex c2(2,2);
...
}

8.函数模板
class stone{
public:
stone(int w,int h,int we)
: _w(w),_h(h),_we(we)
{}
bool operator< (const stone& s) const {
//<是对左边的变量来调用的,就像b调用<函数,使用a参数代入
return _we<s._we;
}
private:
int _w,_h,_we;
};

template
inline
const T& min(const T& a,const T& b){
return b<a? b:a;
//<是对左边的变量来调用的,就像b调用<函数,使用a参数代入
}

stone r1(2,3),r2(3,3),r3;
r3=min(r1,r2);

9.namespace
加入命名空间,减少部分变量的表达代码(用来指明变量的来源类)

本质上namespace是一个类的代码
当我们写using *** 时,就是把这个类调用了,后面的代码都是在这个类的环境下编写的
比如说 using namespace std,就是调用了namespace std的代码
cin的全称应该是std::cin,但由于处于using namespace std环境下,就不必写std::

三种类和类之间的关系
10.复合 composition
类中的成员是另一个类
改造,适配

构造函数就是用来建立类对应的变量的
析构函数只有在包含指针的类中才有,因为在含指针的类中
类无法使用默认的浅拷贝,每个类变量的指针应该是不同的,例如两个类变量的Int类型值可以使用不同空间,存相同的值。但指针型成员不可以,不同的空间也不能存相同的值,必须再新建一个指针地址,这就是深拷贝。
这样带指针的类在用完后也就需要释放指针申请的新空间,也就是析构函数

构造,先基础后复合;析构先复合后基础

箭头表示基础类,黑色方块表示用基础类构造的复合类(容器)
(1)复合类的构造函数,会先执行基础类的默认构造,再执行自己的构造函数,由内到外
如果你需要的不是默认构造函数,就要自己写
(2)复合类的析构函数,先删去自己的,再删内部的,由外到内。因为在删除外部时可能会需要用到内部的函数,如果先释放了内部的基础类,那可能在释放外部类会出错

11.委托 delegation

类中的成员是另一个类的指针 composition by reference
区别在于是指针,图中表示就是白色的菱形框

将一个类的具体实现部分放到另一个类中,同时使用一个指针来指
这样可以在不改变左边的情况下,来修改右边的,非常灵活
因此在现代编程里非常常见
也被称为:point to implementation 指向实现的指针
string.h文件
class stringrep;
//因为后面的类定义函数里有其他类的指针,
//所以需要在这之前写一个其他类的定义函数
//因为真正需要执行的是.cpp文件,所以在头文件中不需要没有#include "main.cpp"的写法
class string{
public:
string();
string(const char* s);
string(const string& s);//拷贝构造
string &operator=(const string& s);//拷贝赋值
~string();//析构函数
private:
stringrep* rep;
};

main.cpp文件

include

include "string.h"

namespace {
class stringrep{
friend class string;//友元
stringrep(const char* ch);//构造函数
~stringrep;//含有指针必须有析构函数
int count;
char* rep;
};
};

12.继承
子类继承父类,子类是一种父类
父类的数据完整的继承到子类中
三角是父,线是子

构造和析构函数的写法与复合类似
父类的析构函数必须是virtual虚函数,否则会出现未定义行为
struct node_base{
node_base* next;
node_base* prev;
}

template
struct node
:public node_base{//这里就是继承的写法
T data;
}

13.虚函数
非虚函数,不允许子类重新定义
虚函数,允许
纯虚函数,子类必须要重新定义
空函数不是纯虚函数,只有写成const=0才是

子类可以直接使用父类的变量和函数,但也可以重新定义,不定义就是用父类的

最完美经典的虚函数执行流程(灰线)
template method

经典设计模式一共有23个
父类就是应用框架,子类是对父类基础操作的组合和专业化

继承与复合的组合

多态:

继承与委托的复合(效果最好的)

对于同一份数据,具有多种表现形式;数据改变,则所有表现形式都会改变
左边的subject就是数据,他具有多个指针
子类创建的变量也是一种特殊的父类变量,所以子类变量可以放到数据类subject的容器vector中
资料observer1的变量ob也属于observer父类,同时ob可以使用observer1的独有函数
observer1 ob;
//资料observer1创建的变量ob也属于observer父类,同时ob可以使用observer1的独有函数
一份数据生成三种图

代码里的vector表示动态数组容器,可以根据需要自动调节长度

14.委托相关设计
文件系统:

primitive 个体文件
componet 文件
composite 复合文件
个体文件和复合文件都是一种文件,所以文件是他们的父类
同时复合文件包含多个文件,使用委托,每个文件都可能是个体文件或者复合文件

复合设计模式,真的非常漂亮美丽
先画图后写类,真的非常棒

文件的add不能写纯虚函数,因为个体文件没有add

原型设计模式

父类不知道子类的名称
子类该怎么将自己放入到父类的容器里呢?
解法是:在创建子类landSatImage时,先创建一个静态的子类变量LSAT(子类的一个成员是静态的子类自身),创建时就会调用子类的构造函数landSatImage(),该构造函数会调用addPrototype函数,这是父类的函数,他会将创建的类变量放进父类的容器里。
这样就实现了在父类不知道子类名字的情况下,将子类变量放入父类容器
当我们真的需要创建一个子类变量,并且不想放入父类容器时;就需要写一条不同的构造函数,不放入父类,加一个参数来区别(不加参数的构造函数会将变量放入父类容器,加参数的不会,本质上这个参数不使用,仅用来区分两个构造函数)

图示里LSAT的下划线表示静态
-表示private

表示protected

不写就是public

父类

子类

标签:上期,const,函数,C++,String,complex,侯捷,data,变量
From: https://www.cnblogs.com/atopes-chw/p/18202129

相关文章

  • C++身份证二要素实名认证api、实名认证接口
    在数字化时代背景下,个人信息安全成为了社会关注的焦点。为进一步加强网络空间的安全管理,提升服务效率,身份证实名认证接口的出现为各行业提供了更为安全、高效的身份验证解决方案。随着互联网+政务服务、金融科技、电子商务等领域的快速发展,实名认证需求日益增长。翔云身......
  • 龙哥量化:注册simnow上期所的期货仿真模拟交易账户教程步骤
     永远顺着趋势交易在技术分析这种市场研究方法中,趋势的概念绝对是核心内容。分析师所使用的全部工具, 诸如支撑和阻挡水平、价格形态、移动平均线、趋势线等等,其唯一的目的就是辅助我们估量市场趋势, 从而顺应着趋势的方向做交易。在市场上,“永远顺着趋势交易”、“决......
  • C++ 异常处理注意事项总结
    C++异常处理注意事项总结:异常安全代码:编写异常安全的代码是至关重要的。这意味着你的代码应该在面对异常时能够正确地清理资源并维持程序状态。使用RAII(ResourceAcquisitionIsInitialization)技术可以帮助自动管理资源,减少内存泄漏的风险。使用noexcept:对于不会抛出异常......
  • C++ 多线程编程要点总结
    C++多线程编程要点总结:选择合适的线程库:C++11引入了 <thread> 头文件,提供了对线程的原生支持。也可以使用第三方库,如Boost.Thread,它提供了更多高级功能和更好的跨平台兼容性。线程创建与管理:使用 std::thread 类创建新线程,并传入函数或可调用对象作为线程的入口......
  • C++中 符号的优先级
    符号运算顺序::从左至右a++a--type()type{}a()a[].->从左至右!~++a--a+a-a(type)sizeof&a*anewnew[]deletedelete[]从右至左.*->*从左至右a*ba/ba%b从左至右a+ba-b从左至右<<>>从左至右<<=>>=从左至右==!......
  • c++实现的小型stl中遇到的问题
    friendstd::ostream&operator<<(std::ostream&ot,constList&lis);参数表中第一个ot应该是引用,因为std::ostream是不可复制的,只能通过引用进行传递,以确保正确的流传递和状态维护。要访问类的私有成员时,非成员函数重载运算符必须声明为友元函数因为参数表中第一个ot是引......
  • Effective C++:3.资源管理
    所谓资源,就是一旦用了,就要还给系统。C++中最常见的就是动态分配内存。其他资源还包括文件描述符、互斥锁等等1.条款13:以对象管理资源把资源放到对象内,那么就可以通过C++的析构函数自动调用的机制去确保资源被释放。这种观念常被称为RAII(资源取得时机就是初始化时机)智能指针shar......
  • 【每周例题】力扣 C++ 一年中的第几天
    一年中的第几天题目一年中的第几天 思路分析1.substr函数分割字符串,stoi函数将字符串转为十进制stoi函数介绍substr函数介绍2.判断是否为闰年,如果是闰年,则二月的天数+1代码#include<bits/stdc++.h>usingnamespacestd;intmain(){ intmonths[13]={0,31,28,3......
  • UE4 C++ 攀爬功能
    UE中的TEXT()UE中使用TEXT()包含字符串后,将字符串转换为宽字符,其将被处理为支持Unicode和跨平台兼容性,而普通类型的字符串为一个窄字符类型,可能在跨平台出现问题。最主要的问题是在FString的构造函数中是接受TCHAR的所以对于FStringFNameFTEXT的构造需要传入TEXT("xxxx")。......
  • 百度 Apollo 使用 bazel 编译 C++ Boost 依赖出现 undefined reference to `boost::pyth
    CSDN搬家失败,手动导出markdown后再导入博客园因为一些原因,楼主想在Apollo自动驾驶框架里使用Boost.python工具来用C++调用Python,从网上找了个例子想编译下试试。C++代码如下(boost.python/EmbeddingPython-PythonWiki):#include<boost/python.hpp>usingnamesp......