加载中

类和对象初步

类和对象初步

目录

补充知识

引用&

基本定义

类型名 & 引用名=变量名,引用名相当于变量名的一个别名

  • 定义时必须初始化成某个引用变量

  • 只能引用变量不能引用常量、const变量和表达式和⚠️迭代器(右值不允许绑定)

    特殊的,对于const 引用(const 类型名 &

    • 定义时仍然必须初始化
    • 但是可以绑定到变量、常量、const变量或表达式(此时将延长表达式值的周期)也可以引用迭代器
  • 初始化后便一直引用该变量,不会引用其他变量

    int a = 10;
    int b = 20;
    int &ref = a; // ref is now an alias for a
    ref = b;      // This does NOT make ref refer to b! 仅仅只是将b的值赋给了a
  • 引用某个引用的引用也成为该原变量的别名

int* & ref=p;合法

引用作为函数返回值

!!!注意⚠️:此时可以对返回值赋值

#include<iostream>
using namespace std;
int n=4;
int & setvalue(){ return n;}
int main(){
	setvalue()=40; // 对返回值进行赋值→对n赋值
	cout<<n<<endl;
	return 0;
}//程序输出40

常引用

  • 不可通过常引用修改值修改本值可以

  • 与引用属于不同类型

    初始化:常引用=引用 or 普通变量(正确,反之则错)

    int n=100;
    const int & r=n;
    r=200; // error!
    n=200; // accepted

const 关键字(避免误改)

基本内容

  • 常量

  • 常量指针

    • 不可通过常量指针更改值

    • 可以改变常量指针指向

      int r=4,rr=8;
      const int * p = &r;
      *p=6; // error!
      r=4; // ok
      p=&rr // ok
    • 可把非常量指针赋给常量指针反之不行,因为常量指针能保证不修改非常量指针指向的那个值,反之不行,除非强制类型转换

      const int *p1;
      int *p2;
      p1=p2; // ok
      p2=p1; // error! 
      // p2 现在允许修改原本被限制的内容,这可能导致通过 p1 指向的 const 对象被意外修改,违反 const 语义
      p2=(int*)p1 // ok
      // 去掉了 const 限定,原本const的对象可以被p2修改,但修改的合法性取决于原对象是否真的是常量:
      // - 原对象本是非常量 → 安全
      // - 原对象本是常量 → 未定义行为 (正常语法下,普通指针绝不允许绑定常量)
  • 使用常量指针、常量引用、常量对象避免修改

    // 常量指针
    void PrintMy(const char*p){
    	strcpy(p,"this"); // error!
    	cout<<p; //ok
    }
    // 常量引用
    int n;
    const int & r=n;
    r=5; // error!
    n=3; // ok

    常量对象只能使用构造函数、析构函数带const关键字的函数

    // 常量对象
    class Sample(){
    private:
    	int value;
    public:
    	void GetValue(){}
    };
    const Sample obj; //常量对象
    obj.GetValue(); // error!

常量方法(常量成员函数)

即在类的成员函数说明后面加 const 关键字

  • 不能改非静态属性的值(可以访问非静态,常量本就不能改,静态变量可以修改
  • 不能调用同类的非常量成员函数(静态成员除外
  • 定义和声明都应使用 const 关键字
class Sample {
public:
    int value;
    void GetValue() const;   // 常量成员函数
    void func() { }          // 非静态、非const成员函数
};

void Sample::GetValue() const {
    // value = 0;   // 错误:常量成员函数不能修改成员变量
    // func();      // 错误:常量成员函数不能调用非const成员函数
}

int main() {
    const Sample o;        // 定义常量对象,使用默认构造函数
    o.GetValue();          // 常量对象只能调用常量成员函数
    return 0;             
}
class Foo {
public:
    int data = 42;

    int get_val() const {          // ✅ 按值返回,没问题
        return data;
    }
    
    const int& get_ref() const {   // ✅ const 引用,没问题
        return data;
    }
    
    // int& get_ref() const {       // ❌ 编译错误!
    //     return data;             // 在 const 函数中 data 被视为 const int
    // }                            // const int 不能绑定到 int&
};

常量成员函数的重载

两个函数名字、参数表均相同。但一者带有 const 关键字另一者无,算重载

编译器将自动选择合适的

mutable 关键字

可以在 const 成员函数中修改 带mutable关键字的成员变量

class CTest {
private:
    mutable int m_n1; // 使用mutable关键字
    bool m_b2;
public:
    bool GetData() const
    {
        m_n1++;
        return m_b2;
    }
};

加快速度与避免改变

  • 为了防止生成形参导致的复制会引发复制构造函数的调用,开销较大

    可以考虑将 TT&const T&(const 确保实参不改变)

以下是一段有关 const 函数调用说明

class Complex{
private:
    double real,imag;
    mutable int access;
    // 用于打破 const
public:
    void setreal(double r){
        real=r;
    }
    double getreal() const{
        access++; // ok for "mutable"
        //imag++; // error!
    }
}

void Func(const Complex &c){
    //c.real=1.0; // error!
    //c.setreal(20.0); //error!
    double val=c.getreal(); // ok
    // const 函数内部只能调用 const 的成员函数
}

比较奇葩的改变方式:该变全局c来促使const的东西也变了

#include <iostream>

class Complex {
private:
    double real;
    double imag;

public:
    // Constructor (构造函数)
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // Non-const member function (非常成员函数)
    // This can modify private members
    void setReal(double r) {
        real = r;
    }

    // Const member function (常成员函数)
    // This allows read-only access for the print statement
    void print() const {
        std::cout << "(" << real << ", " << imag << ")" << std::endl;
    }
};

// Global Instance (全局变量)
Complex global_obj(1.0, 1.0);

// Function using a const reference
void Function(const Complex & c) {
    std::cout << "Value of c before global change: ";
    c.print();

    // Aliasing 
    // Even though 'c' is const, the memory it points to is NOT immutable
    // if there is another non-const pointer/reference to it.
    global_obj.setReal(100.0); 

    std::cout << "Value of c after global change: ";
    c.print(); 
}

int main() {
    // Passing the global object to the function
    // Inside Function(), 'c' will be an alias for 'global_obj'
    Function(global_obj);

    return 0;
}

动态内存管理

谁new谁到结尾delete,一一对应,不可缺少(有new才delete)

new 关键字

  • 变量:p= new T

  • 数组:p=new T[N]

其中 T 是任意类型名,N可以是整型表达式

注意⚠️:分配后调用时编译器不会检查是否越界,应自我约束

*注意⚠️:一定要用 delete 释放内存!且 delete 必须指向new出来的所有空间*

int *p=new int;
int *pp=new int[20];
*p=5;
delete p;
p[0]=1;
// delete pp; // 不报错,但是只是释放了第一个元素!
delete []pp; // 正确!
int rows = 3, cols = 4;

// 分配
int** p = new int*[rows];        // 外层:指针数组
for (int i = 0; i < rows; ++i) {
    p[i] = new int[cols];        // 内层:每行一个 int 数组
}

// 释放(顺序必须反过来)
for (int i = 0; i < rows; ++i) {
    delete[] p[i];               // 先释放每一行的 int[]
}
delete[] p;                      // 最后释放外层的 int*[]

int* inner = new int(10);
int** p = &inner;

// 这没有动态分配二级内存,只需要:
delete inner;   // 或 delete *p;
// p 本身是栈上的,不需要 delete

函数重载

函数重载,即多个函数名字相同,然而参数个数参数类型不相同

注意⚠️:如果出现歧义,则编译报错!

int max(int a, int b) {return (a > b) ? a : b;}
double max(double a, double b) {return (a > b) ? a : b;}
int max(int a, int b, int c) {return max(max(a, b), c);}

int main() {
    cout<<max(3, 5)<<endl;  // 调用 A
    cout<<max(3.7, 2.9)<<endl; // 调用 B
    cout<<max(3, 5, 7)<<endl;  // 调用 C
    cout << max(3, 5.3) << endl;// error! 二义性报错!
    return 0;
}

函数的缺省值

定义函数的时候可以让最右边连续若干参数有缺省值,调用函数的时候,若不写参数则默认为缺省值

  • 提高可扩展性
  • 方便添加新参数与修改

注意⚠️:只能最右边开始

image-20260311180335159

类的定义和使用

定义类

class 类名
{
	访问范围说明符:
	// 可选private,public,protected,可出现多次
    // private:指定私有成员,只有该类的成员函数可以访问
    // public: 任何地方皆可访问
    // protected:参见“继承”章节
	成员变量 1
	成员变量 2
	...
	成员函数声明 1
	声明函数声明 2
    函数(){}
    // 函数类内声明+类外定义 或者 类内直接定义(此时默认为内联函数)
	
	访问范围说明符:
	更多成员变量
	更多成员函数声明
	...
    // class在缺省情况下访问范围为 private ,struct 在默认情况下访问范围为 public
}; // ⚠️class 定义必须以英文分号 ; 结尾

返回值类型 类名::函数名()
{
    语句组
}
  • 注意⚠️:类定义以分号结尾

  • 注意⚠️:A::说明后面的函数是类A的成员函数,而非普通函数

  • 一个类的成员函数可以互相调用

  • 类的成员函数可以重载可以设定参数默认值任何有定义的表达式皆可,如:

    int Max( int m, int n);
    int a, b;
    void Function2 ( int x,int y= Max(a, b), int z=a*b) {...}

  • 没写private,一定一定要写上!!!

定义对象

// 定义对象
类名 对象名;

对象占用的空间

  • 一般来说,一个对象占用的内存空间大小 = 其成员变量的体积之和,可采用sizeof(类名)查看

    Data Type Memory Size (Bytes) Typical Range / Note
    char 1 Stores a single character (单个字符)
    bool 1 Represents true or false
    int 4 Standard integer (标准整数)
    long 4 or 8 Depends on the system (取决于系统架构)
    long long 8 Large integer (大整数)
    double 8 Double-precision floating point (双精度浮点数)
    float 4 单精度浮点数
    指针 4 or 8 固定不变(32位系统为4,64位系统为8),现在大部分为8
  • 每个对象各有自己的一份存储空间,互不影响

  • 成员函数内存只有一份,但可以作用于不同函数

访问对象的成员

class CRectangle
{
    public:
    	int w,h;
    	void init(int ww,int hh)
        {
            w=ww;
            hh=h;
        }
};


CRectangle r1,r2;
// 方法一:对象名.成员名
r1.w=5;
r1.init(3,4);

// 方法二:指针->成员名
CRectangle *p1 = &r1;
CRectangle *p2 = &r2;
p1->w=6;
p2->init(3,4)

// 方法三:引用名.成员名
CRectangle &rr = r2;
rr.w =5;
rr.init(5,4); // rr的值变了,r2的值也变了

类成员的访问权限(private 隐藏机制)

  • class 缺省情况下为 private ,struct 默认为 public

  • 注意⚠️:

    类成员函数内部能够访问 :

    1. 当前对象的全部属性(变量和函数)
    2. ⚠️同类其他对象全部属性
  • 成员函数以外的地方,只能够访问该类对象的公有成员也不能cout私有成员(类似于被屏蔽的感觉)

设置私有成员的好处:

  1. 若未来成员变量的类型等属性修改后,只需要更改成员函数即可,否则得把所有直接访问成员变量的语句修改
  2. 避免对对象的不正确操作

内联成员函数

在每个调用点直接插入函数,增大函数体的体积,但是减少函数调用开销,加快程序速度

// 以下两种方法等价
class A
{
	//内写内联
	void func1(){};
	//声明内联
	inline void func2 ();
}
void A::func2 (){};

inline 对于编译器只是建议而非命令 编译器可能忽略内联请求(对于过长的函数) 也可能自动内联未标记为 inline 的短小函数(即使在类外部定义)

本文作者:flowwalker之码艺Blog

本文链接:/coding-notes-blog/posts/53f3/

版权声明:本文采用 CC BY-NC-SA 4.0 许可协议