栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++的一些杂碎知识(随缘更新)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++的一些杂碎知识(随缘更新)

文章目录
  • 基础
    • 左值右值
  • 函数
    • 构造函数
      • 构造函数三种初始化方式
    • 析构函数
    • 内联函数inline
    • 类中的接口函数
    • 类当中的成员函数
    • 对公共代码使用私有功能的函数
  • 类
    • 友元
  • 头文件
    • 函数在头文件中声明
    • 类在头文件中定义
    • 预处理器
    • 如何写一个头文件?
  • 改名字
  • const
    • 成员函数
  • 指针
    • this指针
  • extern

基础 左值右值

左值 (lvalue, locator value) 表示了一个占据内存中某个可识别的位置(也就是一个地址)的对象。

右值 (rvalue) 则使用排除法来定义。一个表达式不是 左值就是右值 。 那么,右值是一个不表示内存中某个可识别位置的对象的表达式。
详细解答参考此篇Blog。

函数 构造函数

定义:构造函数看起来像一个常规函数,除了它的名称必须与它所属类的名称相同,这就是为什么编译器知道这个特定的成员函数是一个构造函数。此外,**构造函数不允许有返回类型。**构造函数同样允许在类内声明,在类外使用class scope定义。

构造函数三种初始化方式
  • 默认构造函数
class Sales_data{
public:
	Sales_data() = default;
};

以上初始化是C++11的一个新特性,采用了默认的构造函数,默认构造函数就是在调用时不需要显示地传入实参的构造函数。
老版本的默认构造函数如下初始化。

class Sales_data{
public:
	Sales_data(){
	//什么都不做
	}
};

或

class Sales_data{
public:
	Sales_data(double r = 0.05){
	text = r;
	}
};

主要看参数列表是否数值固定或者为空。综上,无论是显式的默认构造函数(XX() = default),还是隐式合成的默认构造函数(编译器生成),**都是用来控制默认初始化过程的。**它按照如下规则初始化类的数据成员:
1.如果存在类内的初始值,用它来初始化成员。
2.如果不存在类内的初始值,默认初始化该成员。

类可能有许多构造函数,但只能有一个默认构造函数。

  • 重载构造函数
    就是有需要不同类型的同名构造函数,比如需要int/double形参的,得多定义几个。

  • 普通形式的构造函数

class Sales_data{
public:
	Sales_data(std::istream &is){is >> *this;}
};
  • 用父类构造函数初始化子类构造函数
class Sales_data{
public:
	Sales_data(const std::string &book):bookNo(book){}
};

上述的单冒号(:)的作用是初始化子类构造函数的参数列表。构造函数初始值列表负责为新创建的对象的一个或几个数据成员赋初值。构造函数初始值是成员名字的一个列表,每个名字后面紧跟括起来的成员初始值,不同成员的初始化通过逗号分隔开。

Sales_data (const std::string &book):bookNo(book), units_sold(0), sellingprice(0), saleprice(0),discount(0){ }

还有以下形式:

class rectangle //头文件中类定义
{
public:
    rectangle( int pointX, int pointY, int Width, int Length );
private:
    CPoint m_point;
    int m_Width;
    int m_Length;
};
 
rectangle::rectangle(int pointX, int pointY, int Width, int Length) : m_point(pointX,pointY),m_Width(Width),m_Length(Length)//源文件中构造函数实现
{
    todo......
}

上述构造函数与下方的代码等价

rectangle::rectangle(int pointX, int pointY, int Width, int Length)//源文件中构造函数实现
{
    m_point.X = pointX;
    m_point.Y = pointY;
    m_Width   = Width;
    m_Length  = Length;
    todo......
}

构造函数在类当中都是以内联函数形式展开运行的。

析构函数

析构函数是具有与类相同名称的公共成员函数,前面带有波浪符号(〜)。例如,A类的析构函数将被命名为 〜A。

当对象被销毁时,会自动调用析构函数。在创建对象时,构造函数使用某种方式来进行设置,那么当对象停止存在时,析构函数也会使用同样的方式来执行关闭过程。例如,当具有对象的程序停止执行或从创建对象的函数返回时,就会发生这种情况。

  1. 像构造函数一样,析构函数没有返回类型。
  2. 析构函数不能接收实参,因此它们不具有形参列表。
  3. 由于析构函数不能接收实参,因此只能有一个析构函数。
内联函数inline

如何把一个类的成员函数定义成内联函数?要想把类的成员函数定义成内联函数,有几种不同的途径。第一种是直接把函数定义放在类的内部,第二种是把函数定义放在类的外部,并且在定义之前显式地指定inline。

举个例子:

//把Sales_data::avg_price定义成内联函数

//1.隐式内联,直接把函数的定义放在类内,因为定义在类内的函数自动inline
class Sales_data
{
public:
	double avg_price() const
	{
		if(units_sold)
			return revenue / units_sold;
		else
			return 0;
	}
};

//2.显式内联,把函数在类内声明,在类外定义,需要指定inline
class Sales_data
{
	double avg_price() const;
};
inline double Sales_data::avg_price() const
//在类外定义的成员函数不能忘记class scope
{
	if(units_sold)
		return revenue / units_sold;
	else
		return 0;
}
类中的接口函数

在类中,经常需要定义几种类接口函数,比如说read和print函数,read函数将给定数据读入给定的对象当中,print将给定的对象打印到给定的流当中去。

istream &read(istream &is, Sales_data &item)
{
	double price = 0;
	is >> item.bookNo >> item.units_sold >> price;
	item.revenue = price * item.units_sold;
	return is;
}

除此之外,关于上面的函数还有两点是非常重要的。第一点,read 接受一个IO类型的引用作为其参数,这是因为IO类属于不能被拷贝的类型,因此我们只能通过引用来传递它们。而且,因为读取和写入的操作会改变流的内容,所以函数接受的都是普通引用,而非对常量的引用。
第二点,函数不负责换行。一般来说,执行输出任务的函数应该尽量减少对格式的控制,这样可以确保由用户代码来决定是否换行。
一般来说,print当中是加上const的,因为输出一般不改变对象;而read是不加const的,因为要输入。

类当中的成员函数
  1. 一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。
对公共代码使用私有功能的函数
class Screen{
public:
	Screen &display(std::ostream &os)
		{do_display(os); return *this;}
	const Screen &display(std::ostream &os)
		{do_display(os); return *this;}
private:
	void do_display(std::ostream &os) const {os << contents;}
};

为什么要额外定义一个do_display( )?

  • 一个基本的愿望是避免在多处使用同样的代码。
  • 我们预期随着类的规模发展,display 函数有可能变得更加复杂,此时,把相应的操作写在一处而非两处的作用就比较明显了。
  • 我们很可能在开发过程中给 do_display 函数添加某些调试信息,而这些信息将在代码的最终产品版本中去掉。显然,只在do_display一处添加或删除这些信息要更容易一些。
  • 而且在类内定义是inline,运行时开销忽略不计。
类
  1. 默认情况下,拷贝类的对象就是拷贝类的数据成员。
  2. 定义在类内的成员函数是自动inline的。
  3. inline成员函数也应该与类定义在同一个头文件里。
  4. 类的成员函数都得加上&,例如
class Screen{....};
inline Screen& Screen::set(char c){....}
友元
  1. 如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。除了令整个类作为友元之外,还可以只为成员函数提供访问权限。当把一个成员声明成友元时,我们必须明确指出该成员函数属于哪个类。(使用class scope符)
  2. 甚至就算在类的内部定义该函数,我们也必须在类的外部提供相应的声明从而使得函数可见。换句话说,就算你在类中定义了友元,也得在外部声明。
  3. 假设现在有两个类别,一个是Screen,一个是Windows_mgr,clear是Windows_mgr的成员函数,现在要让其成为Screen的友元。

要想让 clear 函数作为Screen的友元,只需要在 Screen 类中做出友元声明即可。本题的真正关键之处是程序的组织结构,我们必须首先定义Window mar类,其中声明clear函数,但是不能定义它;接下来定义Screen类,并且在其中指明clear函数是其友元;最后定义clear 函题意的程序如下所示:

#include 
#include 
#include 

using namespace std;

class Windows_mgr
{
public:
    void clear();
};

class Screen
{   friend void Windows_mgr::clear();
    private:
        unsigned height = 0, width = 0, cursor = 0;
        string contents;
    public:
        Screen() = default;
        Screen(unsigned ht, unsigned wd) : height(ht), width(wd), contents(ht * wd, ' '){ }
        Screen(unsigned ht, unsigned wd, char c) : height(ht), width(wd), contents(ht * wd, c){ }
        Screen& move(unsigned r, unsigned c)
        {
            cursor = r * width + c;
            return *this;
        }
        Screen& set(char ch)//ch就是要替换的那个数值
        {
            contents[cursor] = ch;
            return *this;
        }
        Screen& display()
        {
            cout << contents;
            return *this;
        }
};


//在类外进行定义
void Windows_mgr::clear()
{
    Screen myscreen(10, 20, 'X');
    cout << "清理之前myscreen的内容是:" << endl;
    cout << myscreen.contents << endl;
    myscreen.contents = " ";
    cout << "清理之后myscreen的内容是:" << endl;
    cout << myscreen.contents << endl;
}

int main()
{
    Screen myScreen(5, 5, 'X');
    myScreen.move(4, 0).set('#').display();
    cout << "n";
    myScreen.display();
    cout << "n";
}

头文件 函数在头文件中声明

头文件很重要,函数通常声明在头文件(.h),定义在源文件(.cpp)。这是为什么呢?

第一,
预编译指令#include的作用是将所包含的文件全文复制到#include的位置,相当于是个展开为一个文件的宏。

第二,
C++允许多次声明,但只允许一次实现。比如int A();就是一次声明,而int A(){}就是一次实现。如果编译时有多个.cpp文件中#include了同一个含有函数实现的.h,这时候链接器就会在多个目标文件中找到这个函数的实现,而这在C++中是不允许的,此时就会引爆LNK1169错误:找到一个或多个重定义的符号。因此为了让函数可以在各个.cpp中共享,正确的做法就是在.h中只声明函数,并在[另一个].cpp中实现这个函数。这样就不会冲突了。
类在头文件中定义

类通常是定义在函数体之外的,要在不同的文件中使用同一个类,所以类在不同源文件当中的定义就必须保持一致,为了确保类在不同文件中的定义一致,所以通常将类定义在头文件当中。
头文件通常包含那些只能被定义一次的实体,如类、const 和constexpr变量等。
头文件也经常用到其他头文件的功能。以CPPP书当中的例子,我们的Sales_data类包含有一个string成员,所以Sales_data.h 必须包含string.h头文件。
同时,使用Sales_data类的程序为了能操作bookNo成员需要再一次包含string.h头文件。
这样,事实上使用Sales_data类的程序就先后两次包含了 string.h头文件:一次是直接包含的,另有一次是随着包含 Sales_data.h 被隐式地包含进来的。
(一次是在头文件中包含,一次是在头文件被调用时候包含)

有必要在书写头文件时当处理,使具遇次包情况也能安全正常地工作。就引出了下方的预处理器。

预处理器

确保头文件多次包含仍能安全工作的常用技术是预处理器,#include就是预处理器,当预处理器看到#include的时候,就会用指定的头文件的内容替代#include,有点内联函数inline的味道。

C++程序还会用到的一项预处理功能是头文件保护符,头文件保护符依赖于预处理变量。预处理变量有两种状态:已定义和未定义。
#define 指令把一个名字设定为预处理变量,另外两个指令则分别检查某个指定的预处
理变量是否已经定义:#ifdef 当且仅当变量已定义时为真,#ifndef 当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif指令为止。
使用这些功能就能有效地防止重复包含的发生(就算这个头文件还没被包含在其他任何头文件当中,也应加上保护符):

#ifndef SALES_DATA_H //此时变量未定义,为真,执行到endif为止
#define SALES_DATA_H //开始定义
#include 
struct Sales_data{
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};
#endif

第一次包含 Sales data.h时,#ifndef的检查结果为真(因为在#ifndef之前,确实是没有定义的),预处理器将顺序执行后面的操作直至遇到#endif 为止。此时,因为碰到#define预处理变量 SALES_DATA_H的值将变为已定义,而Sales_data.h也会被拷贝到我们的程序中来(因为是预处理器,看之前的解释,类比于内联函数)。后面如果再一次包含Sales_data.h,则#ifndef 的检查结果将为假,编译器将忽略#iffndef 到#endif之间的部分。(#ifndef是实时检查的)

还有一点就是,预处理变量无视C++当中的关于作用域的规则(class scope ::),因为预处理变量不属于命名空间std,它由预处理负责管理,因此可以直接使用预处理变量而无须在前面加上std::。

整个程序中的预处理变量包括头文件保护符必须唯一,通常的做法是基于头文件中类
的名字来构建保护符的名字,以确保其唯一性。为了避免与程序中的其他实体发生名字冲
突,一般把预处理变量的名字全部大写(Sales_data.h变为SALES_DATA_H)。

如何写一个头文件?

加上保护措施,再把那些只能被定义一次的实体,如类、const和constexpr变量等挪到#ifndef或#ifdef与#endif之间就行。

 
改名字 
  1. 使用typedef:
typedef std::string::size_type pos;
  1. 使用using:
using pos = std::string::size_type;
const 成员函数

因为把this初始化成常量有助于程序的灵活性,(因为this不做更改),而且this是隐式的常量,所以该如何声明const type *const this呢,第一个const应该放在哪里更好?最后得出放在成员函数的参数列表之后。也就是如下形式:

std::string Sales_data::isbn() const {return bookNo;}

等价于:

std::string Sales_data::isbn(const Sales_data *const this) {return bookNo;}
指针 this指针
  1. 通过this指针访问成员的优点是,可以非常明确地指出访问的是对象的成员,并且可以在成员函数中使用与数据成员同名的形参;缺点是显得多余,代码不够简洁。
extern
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1012567.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号