c++&面向对象学习笔记/总结(1)

简单概念

  • 对象表现的特征是:属性(数据),行为(处理)
  • 类:相同属性和行为对象抽象

**面向对象的特征:**抽象,封装,继承,多态

面向对象的关键要素:对象;属性;方法;消息/服务。


  • 类的定义
// 例:时钟类的定义(和结构类似,最好起个名)
class Clock
{
public:       // 外部接口,公有成员函数,外面能调用的部分
 void SetTime(int NewH = 0, int NewM = 0, int NewS = 0);
 void ShowTime();
private:        // 私有数据成员,只能类里自用
 int Hour,Minute,Second;
};
//补充:protect,保护型,在私有和公有之间,可被派生(继承)类使用
//public 和 private没有先后顺序,甚至可以穿插
  • 对象
Clock a1;   //创建对象
a1.Showtime();    //调用
  • struct 和 class 的区别之一:前者继承时默认为 public,后者默认为 private(但 private 继承其实用的真不多)
  • 三种继承方式:均无法继承 private 成员,其他成员在子类中转为相应权限,但权限不能放大,protected 无法升格为 public
  • 成员函数可以在外部定义(要先声明)
void Clock::SetTime(int newH = 0,int newM = 0,int newS = 0){    
	Hour = newH;
    Minute = newM;
    Second = news;
}
//返回值 类名::函数名(参数表,可带默认值){函数体}
  • 内联函数:好处是快

原理:普通函数调用,经分配内存,传递参数,处理返回值,回收内存,返回原地址等操作,都要时间。内联函数可理解为智能地将函数解包整合到此处,无需另辟空间。多敲个单词,对人和机器都好

因此只适用层次较浅的简单函数。

理论上多数函数能内联,使用 inline 会自动检查,复杂函数若无法内联,则忽略该关键字

平时上课不用,是因为程序太简单,没必要,不要被迷惑

//示例
inline void Test::output(){
    cout << “正确的" << endl;
}

知识充电站:String 对象

string 里封装了具体字符串的地址和其他信息。相比 char[] 没有越界问题,头文件 <string>

可以定义 string 数组,实现了 cin / cout,赋值不用考虑越界,支持一些字符串的运算,如比较(字典序)。可以正常使用下标定位字符。此外还有一堆便利函数

但要想成为砖家,还是得深入底层熟练使用指针


构造函数(基础)

定义为类同名的成员函数,用于对象初始化无返回值,可传参可重载

若未定义,编译时生成一个空的构造函数

**个人注解:**为类所特有,使用类名创建对象的同时,执行构造函数,一般只调用一次

//宣总的示例,构造函数重载
class Test{
private:
public:
    Test();		//1号
    Test(int n);	//2号
    Test(string str);	//3号
};
int main(void){
    string s1 = "wang", s2 = "zhang";
    Test x;       //生成一个名 x 的对象,执行1号
    Test y(15);   //生成对象,执行2号
    Test array1[2] = {s1,s2};   //生成两个对象,执行2次3号
}
知识充电站:重载函数
  1. 表面上重名,但底层视为两个不同函数

  2. 参数不能出现矛盾 / 歧义

    ① 调用时传入的参数类型,没有符合的函数,按照类型转换的道理,找到参数个数相同的函数进行调用,如果符合的函数不唯一,出现歧义

    ② 函数在声明时,已赋默认值的参数可以被省略,也会出现参数个数的歧义(特别是无参数)

    这点在 python 中得到避免

  3. 函数中多个有意义的参数,调用时可从右往左省略

拷贝构造函数

属于重载构造函数,参数是该类对象引用。一般用于复制对象,主要是过于常用(摘自宣总 ppt)

//已知类 A 有 private 数据 n
A::A(A& a);
{
	n = a.n;
}
初值列——构造函数特有用法
class Point {
	public:
		Point (double a = 0, double b = 0)
			:re (r), im (i)     //这一行叫初值列
		{ }   
	private:
    	double x, y;
}
  1. 初值列实际是调用成员构造函数,顺便支持了基本变量的赋值
  2. 一般初始化分创建和赋值两步;而初值列一步到位
Singleton——单例模式

直接在外部创建对象,构造函数为 public

一些特殊的类,为了防止外部干涉,构造函数需要 private ,配以特有的实例化方式 ↓

class A {
  public:
    static A& getInstance();    //静态函数访问静态对象,返回 private 对象的引用
    setup() { ... }
  private:
    A();
    A(const A& rhs);
    ...
  };
A& A::getInstance()
  {
    static A a;
    return a;
  }
  
A::getInstance().setup();     //使用函数返回的对象,static 意味着该类只有唯一的对象 a

常量成员函数(限非静态函数)

  • 参数和函数体间加 const ,使函数不能修改成员变量,属于保险

通俗讲就是只读标签,凡调用此函数,皆不能修改数据。(在只读条件下,则一律要加保险)

**注意:**对象或成员为 const 时,函数必须为 const 。只读函数未标 const ,仍然会编译错误

例: int a() {return a;}{const A p1(1, 2); cout << p1.a();} 下报错

参数传递

大型数据可传引用,节省开销。(c++ 的引用与指针,在底层相似)

区别大概是——指针是传送门,是看得见摸得着的变量。而引用是绑定,和八云紫老太的隙间一样,看得见摸不着。仅为个人理解

听到了隙间电车创来的声音——

引用定义时必须赋变量初始化,不可改变指向,可用作变量别名,也可作参数

引用类型—— 变量类型 & ,如 int &

指针是变量,能被引用;引用不是变量,但能初始化另一个引用,共同指向一个变量

可以在函数内令引用指向一个未定义的外部变量,只要调用前定义了就行(bug?)

  • 引用可以直接修改外界数据,既方便,又有潜在风险

因此,在参数前加 const ,即不能通过此引用修改(但仍可通过其他途径修改)

补充:const 引用能用 const 或非 const 的变量/引用初始化,而非 const 不能用 const 初始化

指针和引用
  1. 传参时,引用更直观简洁

  2. 指针能改变指向干坏事,再借形参之便,在外隐藏行踪,是潜在危险分子;而引用源头杜绝问题

  3. 使用指针的指针 / 指针的引用,将指针本身变成被修改的外部数据

指针的引用一般这样写: int * & aint* 是变量,所以表示对 int* 的引用

另外,引用调用成员直接用 “.” 而非 “->”

返回值传递

在合理情况下,函数返回值也建议使用引用承接

//举个例子:标准库中定义的复数类,重载 += 运算
inline complex& __doapl(complex* ths, const complex& r) {
  ths->re += r.re;
  ths->im += r.im;
  return *ths;
}       //重载标准库的__doapl函数,将后者加到前者中,返回传入的指针

inline complex& complex::operator += (const complex& r) {
  return __doapl(this, r);
}     
//在类中重载+=。this代表当前对象的指针,是隐藏参数。不能参与声明,只能被调用。返回值为complex对象,适用于连加式
//__doapl()不仅可用于连加,还可以放进其他函数,降低了内外层耦合性,体现了标准库设计的泛用性和可移植性
  • 简单数值可以返回临时值,比如 + 运算
  • 需要引用时,要考虑返回的引用是否有效

函数注定是局部空间,外界数据可全身而退。但内部创造的内容,引用将随函数消亡而失效

类比拷游戏只拷个快捷方式的屑,顺着引用来找的时候,却已物是人非

学个c++却隐约嗅到刀子的气息


友元friend

背景:类是封装的,其中 private 一般不给外人看,相对的就是朋友

在类 A 中声明一个前面加了 friend 的函数 / 类。此后 A 可无视权限调用此函数 / 类(和类的成员)

友元类型
  • 可以是一般路过外界函数(函数声明前加 friend

  • 可以是类的成员函数( friend 类名::函数 )。必须先定义该类,再声明友元函数

  • 可以是类( friend class 类名 )。友元类可以先声明,再定义。该类的成员也能被访问

    让我访问!!!

补充
  1. 和友元相似的,同类的所有对象,彼此都能直接引用,访问对方的成员(家是永远的精神港湾)

  2. 友元不必是相互的,经常有单向的 friend 声明(舔狗行为)


操作符重载

  • 成员函数
//标准库中的 +=
inline complex& complex::operator += (const complex& r) {
  return __doapl(this, r);
}     //返回引用,是为了支持连加,运行效率很高
  • 非成员函数
//标准库中复数的 +
inline complex operator + (const complex& x, const complex& y) {
  return complex (real (x) + real (y), imag (x) + imag (y));//实部和虚部
}
//这个临时的complex就是个不能返引用的例子
c2 = c1 + c2;	//+ 和 += 逻辑的不同在底层体现
临时对象

就地构造的对象,有效范围仅限本行,常用于中转

  • 举个(不太严谨的)栗子

c++ 特有语法—— int(a) 可将 a 转换为 int 型参与计算

形式上像是 int 类的构造函数,但 c++ 没有完全面向对象,保留了基本类型,但增加了那种函数

但从面向对象角度, int(a) 是临时 “对象”

//+ 的重载,表示正号
inline complex operator + (const complex& x){
  return x;
}
cout << +c1;
  • 有人就要问了:为什么不返回引用呢?

  • 标准库只是一套代码示范,不是金科玉律,在简单代码上争辩,也不是明智之举

操作符重载的延伸

操作符的区别是,函数前面有个 operator ,但有 operator 的不一定是操作符

//流的重载
#include <iostream.h>
ostream& operator << (ostream& os, const complex& x)
{
  return os << '(' << real (x) << ',' << imag (x) << ')';
}   //返回ostream &,适用于连续的 << 输出
  1. << 一般不重载为成员,因为标准库中, coutostream 类对象,<<ostream 的成员函数,所以一般用 cout << ,表示 cout 为调用者,被输出的内容写在后面
  2. 若重载为成员函数,被输出的类为调用者,写在 << 前面,流都倒了还怎么输出,输入同理
  3. 因为 << 在左移输出时,不断更新 cout 的状态,所以该方法不加 const

以上是个人理解,欢迎纠正


实例分析

探究 <string> 中的 String 类背后的原理

class String {
  public:
    String(const char* cstr = 0); //构造函数
    String(const String& str); //拷贝构造函数
    String& operator=(const String& str); //操作符重载,给予“赋值”功能
    ~String(); //析构函数
    char* get_c_str() const { return m_data; }  //(内联的)功能函数
  private:
    char* m_data; //字符串首地址作为成员变量
};
inline String::String(const char* cstr = 0) {
  if(cstr){		//PS:const char*是字符串常量指针,不能改字符串
      			//若char* const则是指针常量,不能动指针
    m_data = new char[strlen(cstr)+1];
    strcpy(m_data, cstr);
  }else{    //无字符串输入
    m_data = new char[1];
    *m_data = '\0';   //空字符串为'\0'
  }
} //以上仅为一种初始化方式
String s1("hello");
//析构函数在消亡时用于清理,如
inline String::~String() {
  delete [] m_data;   //分配的内存需要手动free
}
知识充电站:c++ 动态内存分配之 new 和 delete
String *p = new String();
delete p;   //delete 时调用析构函数
  1. 对于单个数据,使用new和delete
  2. 对多个数据,则用 new type [n] 和delete [ ]。

**注:**delete对于基本数据类型的数组,不需要加 [ ]也可以一次性删除

——但自定义数据类型,特别是内部有指针内存分配的结构,要加 [ ] 指明。,否则 delete 的流程是:检测到第一个指针,释放第一个指针,同时删除所有指针,导致后续指针内存泄漏。

总之都加上

String 的拷贝构造和赋值重载

拷贝构造:实现复制

赋值重载:如果不重载,默认赋的是指针,称为浅拷贝;

重载后,先清理内存,再分配内存,实现深拷贝。因为清理了内存,还能防止原先指向的内存泄漏

//String的拷贝构造
inline String::String(const String& str) {
	m_data = new char[strlen(str.m_data) + 1];  //先拷内存
	strcpy(m_data, str.m_data);   //再拷字符串
} //只用于初始化
String s2(s1);
  • 运算符重载
inline String& String::operator=(const String& str) {
	if (this == &str)
    	return *this;   //检测自赋值情况,防止字符串被清空
	delete[] m_data;    //打扫门户
	m_data = new char[ strlen(str.m_data) + 1 ];  //重开内存
	strcpy(m_data, str.m_data);   //存放数据
	return *this;
}
  • 输出字符串
ostream& operator<<(ostream& os, const String& str) {
  os << str.get_c_str();  //重载 << 获取字符串的本体
  return os;
}
cout << s1;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值