muduo库中C++语法归纳

目录

1.前向声明

2.未命名的命名空间

3.全局作用域符号::

4.“=default” 、“=delete”

5.static_cast使用

6.顶层const和底层const

7.模板类的静态变量

8.for_each()函数

9.类中的运算符重载(主要针对加减乘除这种算数运算符)

10.类的临时对象

11.纯虚函数

12.父类指针指向子类

13.柔性数组

13.非类型形参

14.左值右值和引用

15.function和bind的理解


1.前向声明

前向声明就是不用引入某个头文件,就可以使用这个头文件中声明的一些数据结构,这样做一方面可以加快编译速度,最重要的是可以解决头文件相互引用的情况。

先讲述一下头文件相互引用的危害:

假设A.h中引用了头文件B.h,B.h中引用了头文件A.h,那么如果有一个源文件中includeA或者B头文件,那么在预编译时,是需要把头文件的内容复制到源文件中去的,这样就会形成无限复制,复制A.h时又需要复制B.h,然后B.h中又有A.h,这样循环往复。

但是有些情况又必须互相引用,这样就可以使用前向声明,就不用在A.h中引用B.h,只是把B.h中需要用到的数据结构在A中做一个声明,告诉编译器编译时不要报错,然后在源文件中引用时A.h和B.h一起include,这样A.h和B.h的内容就一起复制到源文件中,这样A.h中的声明就有了定义。

以一个小例子为例:

互相包括,会无限复制

/*A.h*/
#include "B.h"

struct A
{
public:
	int a = 10;
	struct B *b;
};

/*B.h*/
#include "A.h"

struct B
{
	char b;
};

struct A *a;

前向声明:

/*A.h*/
#include "B.h"
//这个头文件可加可不加,但是如果这里加了头文件,在源文件中要是想include A.h和B.h时
//只需要引用A.h就够了,如果这里不加,那就在源文件中都要引用A.h和B.h。

struct A
{
public:
	int a = 10;
	struct B *b;
};

/*B.h*/
//B.h中就没有这个引用了。
struct A;//这里只是一个不完全类型的声明,就是只知道A是一个类,但不知道A这个类具体内容,
//需要占多少内存,所以在B.h中只能定义struct A的指针或者引用类型(引用类型详解见下方)

struct B
{
	char b;
};

struct A *a;

2.未命名的命名空间

一般命名空间的定义和声明是这样的:

/*命名空间声明,一般是在头文件中*/
namespace test
{
    int a;
    int func(int a);
}

/*命名空间中内容的定义,一般是在源文件中*/
using namespace test;
int func(int a)
{
    //具体内容省略
}

/*或者这样的:*/
int test::func(int a)
{
    //具体内容省略
}
/*或者这样:*/
namespace test
{
    int test::func(int a)
    {
        //具体内容省略
    }
}

在muduo源码中会出现这样的情况:

namespace
{
    int a;
    int func(int a);
};

这种被称之为未命名的命名空间,其作用是该命名空间中的内容的作用域只是当前的源文件,而不能被其他源文件使用,比如上述的命名空间定义是在A.cpp中,那么里面的函数func和变量就不能在B.cpp中使用。

注意的一点是未命名的命名空间没有声明,直接进行定义,所以只在源文件中会出现,不会在头文件中出现,而命名的命名空间应该在头文件中定义。 

 

3.全局作用域符号::

在muduo库中经常看到一些函数或者变量在使用时,前面会加上::(双冒号),其作用是表示该函数或者变量是全局函数或者变量。例子如下:

char zhou; //全局变量

void sleep()

{

  char zhou; //局部变量

  zhou(局部变量) = zhou(局部变量) *zhou(局部变量) ;

  ::zhou(全局变量) =::zhou(全局变量) *zhou(局部变量);

}

注意:全局函数是指在类外面声明的函数,加不加extern都可以,因为extern是函数默认的声明。 

4.“=default” 、“=delete”

=default使用

一个类的创建会默认生成一些默认函数,具体如下:

(1)构造函数
(2)析构函数
(3)拷贝构造函数
(4)拷贝赋值函数(operator=)
(5)移动构造函数

以及全局的默认操作符函数

(1)operator,
(2)operator &
(3)operator &&
(4)operator *
(5)operator->
(6)operator->*
  (7)   operator new
  (8)   operator delete

主要的函数具体举例如下:

class MyClass
{
public:
    MyClass(const char * str = nullptr);  // 默认带参构造函数 // 默认构造函数指不带参数或者所有参数都有缺省值的构造函数
    ~MyClass(void);  // 默认析构函数
    MyClass(const MyClass &);  // 默认拷贝构造函数
    MyClass & operator =(const MyClass &);  // 默认重载赋值运算符函数
}

但是如果在类中实现了这些函数,那么就不会提供这些默认函数了,以构造函数为例,如果我们想在自定义了构造函数之外,还想要默认的构造函数,就可以使构造函数=default,这样就相当于默认函数也起作用了

举例说明:

/*test.h*/
class test
{
public:
	test(int a, int b);
	test() = default;//1
	int add();
	void seta(int a);
	void setb(int a);
	std::tuple<int, int> show();
private:
	int a;
	int b;
};

/*test.cpp*/
test::test(int a, int b)
{
	test::a = a;
	test::b = b;
	
}

int test::add()
{
	return test::a + test::b;
}

void test::seta(int a)
{
	test::a = a;
}

void test::setb(int b)
{
	test::b = b;
}

std::tuple<int, int> test::show()
{
	std::tuple<int, int> a(test::a, test::b);
	return a;
}

如果没有1处的声明,直接定义一个test变量,如下:

test a;

编译器会报错,而有了1处的声明,这样定义就不会报错了。

所以1处的作用就是加上类的默认构造函数定义。其实也有一种办法可以自定义一个无参数的构造函数,

比如以上的例子可以加一个这样的函数

test(){};

但是和用=default有区别,这样就不再是一个POD类型了,可能让编译器失去对这样数据类型的优化功能。(有待进一步理解)

=delete使用

就是可以删除上面讲的那些默认函数,使得类中没有这一项默认函数

举例说明:

test(const test&) = delete

删除test类的拷贝构造函数

可以参考这篇文章:https://blog.csdn.net/a1875566250/article/details/40406883

5.static_cast使用

其实与static_cast类似的有四种关键词,都是强制类型转换,统一的格式是:

cast-name<type>(experession)

其中type是转换的目标类型,而experession是要转换的值。

cast-name有以下四种关键词:(这里就写两种了)

static_cast:具有明确定义的类型转换(只要不包含底层const)

其中void*和其他类型指针之间的转换使用static_cast来操作

const_cast:将底层const对象转换成其他类型

具体可以参考这篇博客

https://www.cnblogs.com/chenyangchun/p/6795923.html

6.顶层const和底层const

个人理解:顶层const是当const修饰的是指针指向的值或者变量时,这个const是顶层const,

底层const是当const修饰指针本身,也就是指针常量,这个const是底层const。

注意:顶层底层都是用来修饰const的,而不是修饰变量的,所以一个指针既可以是顶层const,也可以是底层const

具体例子见C++primer。

7.模板类的静态变量

可以参考这篇文章:https://blog.csdn.net/itcastcpp/article/details/37812623

特别注意的是一种变量类型就可以定义一种变量类型的静态变量

8.for_each()函数

for_each()函数的具体内容如下:就是把迭代器从first到last位置依次作为fn的入口参数来执行fn

Function for_each(InputIterator first, InputIterator last, Function fn)
{
  while (first!=last) {
    fn (*first);//first指针指向的值作为fn函数的入口参数
    ++first;
  }
  return fn;      // or, since C++11: return move(fn);
}

9.类中的运算符重载(主要针对加减乘除这种算数运算符)

算数运算符重载可以在类内,也可以在类外,大部分都是在类外的,并且可以使用友元来操作类的私有变量

使用友元的程序例子:

class A
{
public:
  explicit A(int d1, int d2):a(d1),b(d2){}
  friend int operator+(A t1, A t2);//声明友元函数,就是为了让函数能够访问类的私有变量

private:
  int a;
  int b;
};


int operator+(A t1, A t2)//友元函数不需要前面加类的作用于A::
{
  return t2.a+t2.b+t1.a+t1.b;
}

int main()
{
  A t1(1,2);
  A t2(3,4);
  cout<<t1+t2<<endl;
}

不适用友元的程序例子:

class A
{
public:
  explicit A(int d1, int d2):a(d1),b(d2){}


  int a;
  int b;//只能把变量变成共有的,这样类外函数才能访问
};


int operator+(A t1, A t2)
{
  return t2.a+t2.b+t1.a+t1.b;
}

int main()
{
  A t1(1,2);
  A t2(3,4);
  cout<<t1+t2<<endl;
}

为什么算数运算符不定义在类内

举例说明

class A
{
public:
  explicit A(int d1, int d2):a(d1),b(d2){}
  
  /*情况一*/
  int operator+(A t1, A t2)
  {
    return t2.a+t2.b+t1.a+t1.b;
  }
  /*情况二*/
  int operator+(A t1)
  {
    return t1.a+t1.b;
  }

private:
  int a;
  int b;
};



int main()
{
  A t1(1,2);
  A t2(3,4);
  cout<<t1+t2<<endl;
}

情况一不可行,情况二可以,但是这种定义的类型只能是一个类变量进行操作,用处不是很大。(有待完善)

10.类的临时对象

如果直接类名+初始化值,那么会创建出一个临时变量,并且执行构造函数,并且在这个代码行结束时,执行析构函数。

程序举例如下:

class test
{
public:
  test(int a, int b):t1(a),t2(b){ std::cout<<"start"<<std::endl;}
 
  void add()
  {
    std::cout<<t1+t2<<std::endl;
  }
  ~test(){std::cout<<"byebye\n";}
private:
  int t1;
  int t2;
};
 
int main()
{
    test(3, 5).add();//这里没有定义类变量,所以是临时创建一个类,在这个一行代码结束后就会自动销毁
    return 0;
}

结果如下,可以看到执行了构造函数,所要求执行的析构函数,以及最后的析构函数。 

11.纯虚函数

在muduo库中经常看到这样的声明:

virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels) = 0;

 在声明后面加等于0,并不表示返回值等于0,而是表示这个函数是一个纯虚函数,留待派生类中去定义,所以在这个类中是无法被调用的

12.父类指针指向子类

父类指针可以指向子类,但是子类指针不能指向父类,具体原理可以参考下面的博客

https://blog.csdn.net/jiangqin115/article/details/78292937

并且注意的是父类指针指向子类指针以后,能够使用的子类成员只有子类从父类中继承过来的成员,而不能使用子类独有的成员。

并且不是所有的父类指针都可以指向子类的,共有继承的都可以,具体原理可以参考下面的博客:

https://blog.csdn.net/liufei_learning/article/details/21587085

13.柔性数组

在muduo中,看到了有关柔性数组的用法,比如以下的情况就是柔性数组:

struct test
{
  int len;
  int arr[];
}

其中arr就是一个柔性数组,之所以叫柔性,是因为这个数组所占内存可大可小。

首先如果创建一个普通的栈上的变量a

struct test a;

这个a所占内存为4个字节,也就是一个int类型变量占的内存大小。也就是说arr数组没有内存空间

测试如下:

struct test
{
	int len;
	int arr[];
};

int main()
{
	struct test a;
	cout<<sizeof(a);
	(void)a;//之所以加这个,是因为避免局部变量未使用的警告
	system("pause");
}

原理就是int arr[]不占用内存,这里有两点需要明确:1.数组名和指针用法上类似,但是本质上不一样,数组名其实和变量名一样,在编译的过程中,会被替换成对应值的存储地址,但是指针在编译时,会被替换成存储对应值地址的地址。

有点绕,用下图来说明,假设还有一个指针ptr指向arr

int *ptr = arr;

è¿éåå¾çæè¿°

画一个更加形象的图片 

 所以在编译时,arr自动被替换为2948344,而ptr被替换为3472248。这就是指针和数组本质的区别。

2.就是变量名是不占用内存空间的,只是变量名锁指向的值会占用内存。

至于具体赋值,可以采用创建动态内存的方法来扩展柔性数组如下:

struct test
{
	int len;
	int arr[];
};

int main()
{
	struct test *a = (test*)malloc(sizeof(test)+sizeof(int)*5);
	a->arr[4] = 10;
	cout << a->arr[4];
	free(a);
	system("pause");
}

更详细一点的可以查看这个博客https://www.cnblogs.com/veis/p/7073076.html

其中注意这个博客中所说的那个内存错误,每个数组的-1位置和size位置都是标志位。

注意:这种柔性数组在struct中使用是没有问题的,但是在class中使用时,就不一定能保证有用了。其中最主要的原因是不是你数组放在最后一个,你就真的在存储位置上也是结构体末尾的位置了,因为class里面特性较多,而且存储方式复杂,不是结构体那样简单顺序存储。

比如如果protected data放在private data后面就不行,具体可以参考《深度探索C++对象模型》的20页。

13.非类型形参

参考这篇博客:https://www.cnblogs.com/gw811/archive/2012/10/25/2738929.html

14.左值右值和引用

左值和右值的解释(个人理解)

左值是可以放在赋值号左边可以被赋值的值;左值必须要在内存中有实体
右值当在赋值号右边取出值赋给其他变量的值;右值在CPU寄存器中存储
一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址

值存在内存中肯定是持久。存在寄存器中肯定是短暂的,这就是左值和右值的区别。

下面这个例子中:

int a=10;//a是左值
int b=20;//b是左值
b=a;//b是左值,a变量的值是右值

个人理解是:a和b都是左值,但是把a的值赋给b时,a的值先从内存中拿出来,再放到b的内存中去,所以a的值会被存储在寄存器上,所以a的值就是右值。所以左值是可以转换成右值的。

左值引用和右值引用

int t=10;
int &a=t;//a是左值引用
int &&b=10;//b是右值引用

左值引用的等式右边必须是左值,就是有确定内存的

特例:

const int &a=10;//这个是可以的,按理10是右值,但是可以绑定在a上,这是规定

右值引用的等式右边必须是右值,就是临时变量,注意这种情况下,左值就不能转换成右值了,也就是如下情况是错误的

int a=10;
int &&b=a;//错误

右值引用的好处:就是所引用的右值对象没有其他用户,可以自由接管所引用的对象资源,个人理解就是把临时变量或者常量赋值给一个变量,也就是这样的程序是等价的

int &&a=10;
int b=10;//个人理解是等价的

引用的本质

int a=10;

int &b=a;

int *c;

c=&a;

注意,这里的b和指针是有差别的,指针c中存放的是a变量的地址,但是引用类型b只是一个a的别名,最有说服力的证明就是:

&a,&b,&c三个操作中,前面两个的值是一样的,但是&c的值不一样,所以就可以当成b和a是一个东西,无论是变量地址还是变量值,而c和a不是同一个东西,最主要的是变量地址不一样。

但是从底层来看,肯定b变量的地址和a变量的地址是不一样的,b变量存储的是a变量的地址,但是在&b时,编译器会自动翻译成&(*b)。具体可以看

https://blog.csdn.net/lws123253/article/details/80353197

还有一种说法就是引用只是在编译的符号表中多一条符号对应,但并不会在内存中为引用变量真的开辟存储空间,可以参考博客https://www.cnblogs.com/qingergege/p/6723509.html这种说法是错误的

引用和指针其实都是占据内存空间的,只是引用在编译器中会经过处理,所以表现和指针有所不一样。

使用std::move函数可以将左值强制转换成右值,其实就是移动值

15.function和bind的理解

暂时只理解function对于函数和bind对象的调用。从这个角度来看:

对于function,我的理解是一个智能函数指针,用一个小例子来说明更清楚:

#include <iostream> 
#include "functional"
extern test b;
using namespace std;

typedef function<void()> null_func;//null_func所定义的函数指针形如:void (*function)()
typedef function<void(int)> int_func;//int_func所定义的函数形如:void (*function)(int)
typedef function<void(int, const char*)> int_char_func;//int_char_func所定义的函数形如:void (*function)(int, const char*)

void show(int a, const char* b)//但我想把show函数的指针赋给上面三个定义的函数指针,就需要用到bind
{
	cout << "数值为:" << a << endl;
	cout << "字符串为:" << b << endl;
}

int main()
{
	int_char_func test1 = show;//因为show函数的形式和int_char_func定义的形式一致,所以直接赋值即可
	test1(1, "test1");
	int_func test2 = bind(show, placeholders::_1, "test2");//show比int_func所定义的函数形式多了一个入口参数,所以就需要把这个入口参数绑定到一个具体的值上,然后只留下一个入口参数,也就是int,留下的入口参数用占位符placeholders::_1来表示
	test2(2);
	null_func test3 = bind(show, 3, "test3");//show比null_func所定义的函数形式多了两个入口参数,所以在bind绑定的时候,需要把两个入口参数都和具体的对象绑定在一起。
	test3();
    int_char_func test4 = bind(show, placeholders::_1, placeholders::_2);//其实给int_char_func定义的对象赋值还有一种方法,可以使用bind,但是由于int_char_func定义的函数有两个入口参数,和show一致,所以bind中使用两个占位符
	test4(4, "test4");
	system("pause");
}

结果如图所示 

其实相比于普通的函数指针,通过function和bind,可以实现只要定义一种函数指针,所有形式的函数都可以将作为这个函数指针的值。

注意:占位符一定有要有命名空间std::placeholders::_1

注意bind如果绑定的是类成员变量时,会和之前的绑定有所不一样。

using namespace std;

typedef function<void()> null_func;
typedef function<void(int)> int_func;
typedef function<void(int, char*)> int_char_func;

class bindfunc
{
public:
	bindfunc(int t) :c(t) {};
	void show(int a, const char* b)
	{
		cout << "数值为:" << a << endl;
		cout << "字符串为:" << b << endl;
		cout << "私有变量为:" << c << endl;
	}
private:
	int c;
};


int main()
{
	bindfunc a(10);
	int_char_func test1 = bind(&bindfunc::show, &a, placeholders::_1, placeholders::_2);
//有两点需要注意的地方,1.这里需要传入的第一个参数需要带成员函数所属类
//2.紧接着需要的参数是这个成员函数所属哪一个类对象的指针(智能指针也可以)
	test1(1, "test1");
	system("pause");
}

注意:指针中普通指针和智能指针shared_ptr都可以,测试程序如下:

#include <iostream>
#include <unistd.h>
#include <sys/eventfd.h>
#include <sys/epoll.h>
#include <pthread.h>
#include <memory>
using namespace std;

typedef function<void()> null_func;
typedef function<void(int)> int_func;
typedef function<void(int, char*)> int_char_func;

class bindfunc
{
public:
	bindfunc(int t) :c(t) {};
	void show(int a, const char* b)
	{
		cout << "数值为:" << a << endl;
		cout << "字符串为:" << b << endl;
		cout << "私有变量为:" << c << endl;
	}
private:
	int c;
};


int main()
{
	shared_ptr<bindfunc> a(new bindfunc(10));//这里a是智能指针,但是好像只有share_ptr可以使用
	int_char_func test1 = bind(&bindfunc::show, a, placeholders::_1, placeholders::_2);
	test1(1, "test1");
}

注意:unique_ptr就不可以,weak_ptr肯定也不可以,所以我认为只有shared_ptr可以

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值