C++学习笔记(曾经我看不懂的代码1:引用、重载、using和typedef、函数指针和指针函数、单双冒号的用法、static_cast和dynamic_cast)

本文深入探讨了C++中的引用、重载、函数指针等高级特性,详细解析了它们的工作原理及应用场景,并对比了static_cast与dynamic_cast的不同之处。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码模板一:

bool Nocase::operator()(const string& x,const string& y)const{}
1、指针和引用:
(1)使用:

string& x中,此处&不是取地址,表示引用,引用符写在变量类型后面。作用看下面例子:

直接使用int型作为参数:

//普通int 计算
#include <iostream>
using  namespace std;
void func( int a ,int b ,int c,int d)
{
    d=a+b+c;  //改变的是局部变量 d 的值
}
int main() {
    int p;
    func(1,2,3,p);
    cout<<p;
    return 0;
}
//输出结果 0
//这样计算修改的是局部变量d的值,作为外部变量的p则不会被修改

指针作为参数运算:

//指针计算
#include <iostream>
using  namespace std;
void func( int a ,int b ,int c,int *d)
{
    *d=a+b+c;
}
int main() {
    int d=1;
    int *p= &d;
    func(1,2,3,p);
    cout<<d;
    return 0;
}
//输出结果 6
//函数中的d指向p的地址

引用计算:

//引用计算
#include <iostream>
using  namespace std;

void func( int a ,int b ,int c,int &d)
{
    d=a+b+c;
}
int main() {
    int p;
    func(1,2,3,p);
    cout<<p;
    return 0;
}
//输出结果 6
//引用相当于形参作为实参的一个别名,实际是使用实参p加入计算

所以想要使用函数修改全局的变量,需要使用指针或引用。引用通过形参来修改变量实参,而指针则是直接修改实参地址指向。当有大量的数据运算时,使用引用代替指针可以避免大量的值拷贝。

参数中使用引用后,如果不希望改变变量的值,可以结合const使用。

(2)变量引用:

类似指针与指针函数,引用也是一种值传递的方式。

以下两段代码来自:添加链接描述

int main() {
    int a = 5;
    int& b = a;
    cout << &a << endl; // 取a的地址
    cout << &b << endl; // 取b的地址
    return 0;
}

//输出: 
//    0x61fe14
//    0x61fe14

此处表示创建一个对a的引用bb不占用内存,等于a的一个别名,因此对ab进行取址时,二者地址相同。

int main(int argc, const char * argv[]) {
    int a = 5;
    int& b = a;  //a的别名

    int c = 20;
    b = c;
    cout << a << endl; // 输出a修改后的值
    cout << b << endl; // 输出b修改后的值
    return 0;
}
//输出:
//    20
//    20
(3)函数引用返回值:

以下来自:https://www.jianshu.com/p/4f0a892c2f89

  • 返回引用:当函数返回引用类型的时候,没有复制返回值,而是返回对象的引用(即对象本身)。实际上是一个变量的内存地址,既然是内存地址的话,那么肯定可以读写该地址所对应的内存区域的值,即就是“左值”,可以出现在赋值语句的左边。

函数引用可直接使用全局变量:

#include <iostream>
using namespace std;

int aa=11;
int &a()
{
    int aaa=1;
    cout<<"aa="<<aa<<"   "<<"aaa="<<aaa;
}

int main()
{
    a();
    return 0;
}
//输出 aa=11   aaa=1

语法:

类型 &函数名(形参列表){ 函数体 }
  • 不使用引用时:
#include <iostream>
using namespace std;

int add(int aa){
    return aa+1;
}
int main(){
    int a=1;
    cout<<add(a);
}

add函数中产生了临时变量aa

  • 使用引用时:
#include <iostream>
using namespace std;

int& add(int& a){
    a=a+1;
    return a;
}
int main(){
    int a=1;
    cout<<add(a);
}

引用项都相当于一个别名,真正存在的只有变量a。比如,如下操作:

#include <iostream>
using namespace std;

int& add(int& a){
    a=a+1;
    return a;
}
int main(){
    int a=1;
    cout<<&a<<endl;
    cout<<&add(a)<<endl;
}

运行结果:
在这里插入图片描述
可以得出,实际上这个引用函数的内存是不存在的,自始至终存在的只有a

函数只要有形参,只要有返回值,都需要申请一个临时内存来存储,而引用就不需要。就像输出的内存地址一样,不管是引用变量,还是引用返回值的函数,都是一个别名而不占用内存。把函数中的元素都替换成引用参数和返回值引用,在大量的数据中使用引用来替换指针,能尽可能的缩减内存占用。

(4)右值引用:

上面的引用中都是作用于左值,当想要作用于右值(即作用于一个具体的变量值上时),使用int &a = 1;编译是不能通过的,使用右值引用的方法为双&符号号:

#include <iostream>
using namespace std;

int main()
{
    int &&a = 11;
    cout<<a;
    return 0;
}
//输出 11
2、重载:

资料来自:https://www.runoob.com/cplusplus/cpp-overloading.html

operator()(const string& x,const string& y)表示对运算符()进行重载。

(1)函数重载:

在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。

#include <iostream>
using namespace std;
 
class printData
{
   public:
      void print(int i) {
        cout << "整数为: " << i << endl;
      }
 
      void print(double  f) {
        cout << "浮点数为: " << f << endl;
      }
 
      void print(char c[]) {
        cout << "字符串为: " << c << endl;
      }
};
 
int main(void)
{
   printData pd;
   // 输出整数
   pd.print(5);
   // 输出浮点数
   pd.print(500.263);
   // 输出字符串
   char c[] = "Hello C++";
   pd.print(c);
   return 0;
}

输出:

整数为: 5
浮点数为: 500.263
字符串为: Hello C++
(2)运算符重载:

对运算符重载,运算符就可以具备一些特殊功能。重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。

#include <iostream>
using namespace std;
 
class Box
{
   public:
      double getVolume(void)
      {
         return length * breadth * height;
      }
      void setLength( double len )
      {
          length = len;
      }
      void setBreadth( double bre )
      {
          breadth = bre;
      }
      void setHeight( double hei )
      {
          height = hei;
      }
      // 重载 + 运算符,用于把两个 Box 对象相加
      Box operator+(const Box& b)
      {
         Box box;
         box.length = this->length + b.length;
         box.breadth = this->breadth + b.breadth;
         box.height = this->height + b.height;
         return box;
      }
   private:
      double length;      // 长度
      double breadth;     // 宽度
      double height;      // 高度
};
int main( )
{
   Box Box1;                // 声明 Box1,类型为 Box
   Box Box2;                // 声明 Box2,类型为 Box
   Box Box3;                // 声明 Box3,类型为 Box
   double volume = 0.0;     // 把体积存储在该变量中
   // Box1 详述
   Box1.setLength(6.0); 
   Box1.setBreadth(7.0); 
   Box1.setHeight(5.0);
   // Box2 详述
   Box2.setLength(12.0); 
   Box2.setBreadth(13.0); 
   Box2.setHeight(10.0);
   // Box1 的体积
   volume = Box1.getVolume();
   cout << "Volume of Box1 : " << volume <<endl;
   // Box2 的体积
   volume = Box2.getVolume();
   cout << "Volume of Box2 : " << volume <<endl;
   // 把两个对象相加,得到 Box3
   Box3 = Box1 + Box2;
   // Box3 的体积
   volume = Box3.getVolume();
   cout << "Volume of Box3 : " << volume <<endl;
   return 0;
}

输出:

Volume of Box1 : 210
Volume of Box2 : 1560
Volume of Box3 : 5400

代码中的Box3 = Box1 + Box2; 相当于执行了 Box3 = Box1.operator+(Box2);,即+号重载后的运算。

下面是可重载的运算符列表:

类型符号
双目算术运算符+ (加),-(减),*(乘),/(除),% (取模)
关系运算符==(等于),!= (不等于),< (小于),> (大于),<=(小于等于),>=(大于等于)
逻辑运算符||(逻辑或),&&(逻辑与),!(逻辑非)
单目运算符+ (正),-(负),*(指针),&(取地址)
自增自减运算符++(自增),--(自减)
位运算符|(按位或),& (按位与),~(按位取反),^(按位异或),<< (左移),>>(右移)
赋值运算符=, +=,-=, *=,/=, % = , &=, |= , ^=, <<=, >>=
空间申请与释放new, delete, new[ ] , delete[]
其他运算符()(函数调用),->(成员访问),,(逗号),[] (下标)

下面是不可重载的运算符列表:

类型运算符
成员访问运算符.
成员指针访问运算符.*,->*
域运算符::
长度运算符sizeof
条件运算符?:
预处理符号#
3、函数最后的const:

operator()(const string& x,const string& y)const

位于函数参数前的const表示这个参数不能被函数体修改,只读。
位于整个函数后的const表示禁止函数内部的成员在函数体中被修改,只读。

4、operator作用:

其一就是操作符的重载,上面已经写过了。还有就是通过operator完成隐式转换。

转换格式:

operator 类型T ()   //T是要转换到的类型。
operator double() const ;  //当前所属类隐式的转换为double()
5、补充:
(1)形参和实参:

形参:函数名或函数体中的参数,在函数被调用时,用于接收实参的值。
实参:是在调用时传递给函数的参数。 实参可以是常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。

如:

#include <iostream>
using namespace std;

void add(int& b);  //b 形参
int main(){
    int a=1;
    add(a);   //a 实参
    cout<<a;
    return 0;
}
void add(int &b)
{
    b+=1;
}

想要通过形参来改变实参,就需要用到引用。

(2)深拷贝和浅拷贝:

浅拷贝:创建一个指向被拷贝对象内存地址的指针,类似创建文件的快捷方式。源内存地址内容发生改变时,浅拷贝指针指向内容也发生改变。
深拷贝:创建一个指针,并申请新的内存空间存放深拷贝对象的所有内容,指针指向新开辟的内存。类似文件的复制,源文件删除时,复制的文件不受影响。

代码模板二:

#include <iostream>
using namespace std;

int add(int a,int b)
{
    return a+b;
}
using addFunc=int(*)(int,int);   
//定义一个未命名的函数指针类型,指向int (int,int); 指针的别名为addFunc
int main(){
    addFunc dd = add;      //dd为函数指针,函数指针指向函数 add
    int j = dd(1,2);     //使用指针代替函数
    cout<<"j="<<j;
}
//输出 j=3

int(*)(int,int);表示定义了一个int型的,未命名的函数指针,它的形参为两个int型。using addFunc表示为这个函数指针定义的别名为addFuncaddFunc dd = add;使用别名声明一个上述类型的指针dd,相当于int(*dd)(int,int);dd指向add函数。dd(1,2);由于dd指向add,实际执行的是add(1,2);

1、using和typedef:

typedef别名:部分参考自:http://c.biancheng.net/view/298.html

(1)为基本类型创建别名:
typedef int i;
i a = 1;
//或
typedef double dou;
dou b = 1.01;
(2)为结构体创建别名:
typedef struct tagPoint
{
    double x;
    double y;
    double z;
} Point;

tagPoint为结构体名,Point为别名,但是Point别名在使用的时候还是要与tagPoint一样的。

还可以使用指针来定义:

struct tagNode
{
    char *pItem;
    struct tagNode *pNext;
};
typedef struct tagNode *pNode;
(3)为数组定义简洁的类型名称:

使用方法与其他的不太一样:

typedef int INT_ARRAY_100[100];
INT_ARRAY_100 arr;   //arr是100个元素的数组
(4)为指针定义简洁的名称:
typedef char* PCHAR;
PCHAR pa;

接着即可使用PCHAR定义char型指针,这里等同于char* pa

(5)用来定义函数指针:

与数组类似,别名会取自自己定义的指针名:

typedef void (*pfun)(void)  //类型别名
pfun a;

即可使用pfun快速定义一个返回值和形参都为void的函数指针了。

参考资料中还有一个函数指针的指针的别名声明,记录一下:

//直接声明
int *(*a[5])(int,char*);
//typedef别名声明
typedef int *(*PFun)(int,char*);
// 使用定义的新类型来声明对象,等价于int*(*a[5])(int,char*);
PFun a[5];
(6)陷阱:

在函数形参中使用typedef的别名时,如果使用:

typedef char* PCHAR;
int strcmp(const PCHAR,const PCHAR);

程序运行下来,const PCHAR的效果并不是const char*,而是char* const

char* const p : 定义一个指向字符的指针常数
const char* p : 定义一个指向字符常数的指针

要得到想要的结果,如下定义:

typedef const char* PCHAR;
int strcmp(PCHAR, PCHAR);

using别名:参考:https://blog.youkuaiyun.com/shift_wwx/article/details/78742459

(1)基本数据类型别名:
using i=int;
using dou=double;

i a=1;
dou b=1.11;
(2)结构体:
using Point = struct tagPoint
{
    double x;
    double y;
    double z;
} ;

Point p;

以及:

struct tagNode
{
    char *pItem;
    struct tagNode *pNext;
};
using Point = struct tagNode;
(3)数组别名:
using arr= int [10];

int main(){
    arr a;
    a[1] = 10;
}
(4)char型指针别名:
using PCHAR = char* ;

int main(){
    PCHAR pa;
    pa = "asss";
    cout<<pa;
}
(5)函数指针:
int add(int a,int b)
{
    return a+b;
}
using addFunc=int(*)(int,int);   
int main(){
    addFunc dd = add;      
    int j = dd(1,2);     
    cout<<"j="<<j;
}
2、函数指针和指针函数:
(1)函数指针:

即指向和返回是函数的一个指针,函数指针在使用时,可以使其指向已定义的函数,using声明函数指针上面有了,下面我又写了直接声明函数指针和typedef声明函数指针的方式。

函数指针直接定义:

#include <iostream>
using namespace std;

int add(int a,int b)
{
    return a+b;
}
int (*tt)(int,int);
int main(){
    tt=add;
    int k=tt(3,3);
    cout<<"k="<<k;
}
//输出 k=6

typedef定义:

#include <iostream>
using namespace std;

int add(int a,int b)
{
    return a+b;
}
typedef int (*pfun)(int ,int);
pfun aaaa;
int main(){
    aaaa=add;
    int k=aaaa(3,3);
    cout<<"k="<<k;
}
(2)指针函数:

即返回值为指针的函数,与函数指针含义相反。下列代码来自:https://blog.youkuaiyun.com/luoyayun361/article/details/80428882

typedef struct _Data{
    int a;
    int b;
}Data;

//指针函数
Data* f(int a,int b){
    Data * data = new Data;
    data->a = a;
    data->b = b;
    return data;
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //调用指针函数
    Data * myData = f(4,5);
    qDebug() << "f(4,5) = " << myData->a << myData->b;

    return a.exec();
}

代码模板三:

System::Math::Sqrt()
//使用成员函数,相当于
System.Math.Sqrt()
int *p1 = static_cast<int*>( malloc(10 * sizeof(int)) );

申请的10倍int长度的void指针转为int指针。

1、单/双冒号用法:

以下的内容来自:https://blog.youkuaiyun.com/dajiadexiaocao/article/details/81776364

(1)单冒号:
  • 构造函数后面的冒号起分割的作用,是类给成员变量赋值的方法,初始化列表,更适用于const成员变量。
struct foo{
       foo()
       :Node("node"), _temp(temp)
       {}
};
  • public:private:后面的冒号,表示后面定义的所有成员都是公有或私有的,直到下一个public:private: 出现为止。private: 为默认处理。
public:
   int foo(int a){}
 
private:
   int a;
   std::string s;
  • 类名冒号后面的是用来定义类的继承。
class 派生类名 : 继承方式 基类名
{
     派生类的成员
};

继承方式:publicprivateprotected,默认处理是public

  • 表示机构内位域的定义(即该变量占几个bit空间)。
typedef struct _foo{
      int a:4;
      int b;
};foo
(2)双冒号:
  • 表示 “域操作符”,声明了一个类A,在类A中声明一个成员函数void foo(),但没有在类A的声明中给出函数foo的定义,那么在类外定义foo时,就要写成
void A::foo(int a,int b){cout<<a<<b;}

类似于Qt中定义槽函数功能:

void MainWindow::on_PushButton_clicked(){}
  • 表示引用成员函数和变量,作用域成员运算符。引用某个功能库下的函数或变量:
System::Math::Sqrt()
//相当于
System.Math.Sqrt()
2、static_cast和dynamic_cast用法:

以下的内容来自:https://blog.youkuaiyun.com/u014624623/article/details/79837849

(1)static_cast:

用法:static_cast< typename>(expression)

new_type为目标数据类型,expression为原始数据类型变量或者表达式

static_cast相当于传统的C语言里的强制转换,该运算符把expression转换为new_type类型,用来强迫隐式转换如non-const对象转为const对象,编译时检查,用于非多态的转换,可以转换指针及其他,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

  • 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
    进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
    进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
  • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum
  • 把空指针转换成目标类型的空指针。
  • 把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expressionconstvolatile、或者__unaligned属性。

基本数据类型转换:

char a = 'a';
int b = static_cast<int>(a);//正确,将char型数据转换成int型数据

double *c = new double;
void *d = static_cast<void*>(c);//正确,将double指针转换成void指针

int e = 10;
const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据

const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性

类上行和下行转换:

class Base
{};

class Derived : public Base
{}

Base* pB = new Base();
if(Derived* pD = static_cast<Derived*>(pB))   //转派生类
{}//下行转换是不安全的(坚决抵制这种方法)

Derived* pD = new Derived();
if(Base* pB = static_cast<Base*>(pD))  //转为基类
{}//上行转换是安全的
(2)dynamic_cast:

用法:dynamic_cast< typename >(expression)

new_type为目标数据类型,expression为原始数据类型变量或者表达式

转换方式:

  • dynamic_cast< type* >(e)
    type必须是一个类类型且必须是一个有效的指针
  • dynamic_cast< type& >(e)
    type必须是一个类类型且必须是一个左值
  • dynamic_cast< type&& >(e)
    type必须是一个类类型且必须是一个右值

e的类型必须符合以下三个条件中的任何一个:

  • e的类型是目标类型type的公有派生类
  • e的类型是目标type的共有基类
  • e的类型就是目标type的类型。

如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0。如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个std::bad_cast异常(该异常定义在typeinfo标准库头文件中)。e也可以是一个空指针,结果是所需类型的空指针。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast)。

在类层次间进行上行转换时,dynamic_caststatic_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。dynamic_cast是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。

  • 指针类型
    举例,Base为包含至少一个虚函数的基类,DerivedBase的共有派生类,如果有一个指向Base的指针bp,我们可以在运行时将它转换成指向Derived的指针,代码如下:
if(Derived *dp = dynamic_cast<Derived *>(bp)){
  //使用dp指向的Derived对象  
}
else{
  //使用bp指向的Base对象  
}

值得注意的是,在上述代码中,if语句中定义了dp,这样做的好处是可以在一个操作中同时完成类型转换和条件检查两项任务。

  • 引用类型

因为不存在所谓空引用,所以引用类型的dynamic_cast转换与指针类型不同,在引用转换失败时,会抛出std::bad_cast异常,该异常定义在头文件typeinfo中。

void f(const Base &b){
 try{
   const Derived &d = dynamic_cast<const Base &>(b);  
   //使用b引用的Derived对象
 }
 catch(std::bad_cast){
   //处理类型转换失败的情况
 }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值