C/C++教程

C++学习总结3

本文主要是介绍C++学习总结3,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

(1)C预处理,条件编译

(A)宏定义与宏替换

宏名一般大写,替换发生在编译之前,且是机械替换,不做语法检查,也不分配内存,不占用运行时间,只占用编译时间。

1、符号常量的宏定义和宏替换 :#define 标识符 字符串

#include<iostream>
#define P 3+4
using namespace std;
void main()
{
	int a=2;
	cout<<a*P<<endl;  //相当于a*3+4,而不是a*(3+4),机械替换
}

2、带有参数的宏定义和宏替换:#define 标识符(参数列表) 字符串

#include<iostream>
#define FUN(a) a*a
using namespace std;
void main()
{
    cout<<FUN(2+3)<<endl;  //机械替换,2+3*2+3
}

(B)文件包含

#include的作用是把它后面所写的那个文件的内容,一字不改地包含到当前的文件中。

  • #include <filename> 认为该文件是标准头文件,先到标准库中寻找,若没有,再到当前目录下寻找;
  • #include"filename" 一般认为是用户自定义文件,先到当前目录寻找,若没有,再到类库中寻找;

(C)条件编译

//格式
#if/ifdef/ifndef
#elif
#else
#endif

一般用在头文件中,避免多重包含,在源程序中引入头文件,相当于把头文件的内容复制到源文件当中。

我们都知道,一个符号可以多次声明,但只能定义一次。那么头文件的引用就涉及到了这个问题,当一个函数fun()在头文件source中进行定义后,若类A中包含该文件,类B也包含该文件,而在源文件C中用到了A和B,#include"A" 和#include"B"时,source在C中包含了两次,会出现fun()的多次定义,导致错误。

#ifndef S
#define S
//类的定义
#endif

(2)_cpluscplus

_cpluscplus是c++中的定义,而c中没有该定义

  • 用来判定代码是c类型还是c++类型
  • cplusplus的类型是"long int",值为199711L
int main()
{
	#ifdef _cplusplus
		printf("This is c++ program");
	#else
		printf("This is c program");
}

(3)NULL和nullptr

(A)
NULL是一个宏定义,在c和c++中的定义不同,c中NULL为(void*)0,而c++中NULL为整数0

//C语言中NULL定义
#define NULL (void*)0 			//c语言中NULL为void类型的指针,但允许将NULL定义为0

//c++中NULL的定义
#ifndef NULL
#ifdef _cpluscplus 			//用于判定是c++类型还是c类型
#define NULL 0 			//c++中将NULL定义为整数0
#else
#define NULL ((void*)0) 			//c语言中NULL为void类型的指针
#endif
#endif

所以在C++中int *p=NULL; 实际表示将指针P的值赋为0,而c++中当一个指针的值为0时,认为指针为空指针。

(B)
nullptr是c++11中的关键字,表示空指针,是一个字面值常量,类型为std::nullptr_t,空指针常数可以转换为任意类型的指针类型。

在c++中(void *)不能转化为任意类型的指针,即 int *p=(void*)是错误的,但int *p=nullptr是正确的。

void fun(int i){cout<<"1";};
void fun(char *p){cout<<"2";};
int main()
{
	fun(NULL);  //输出1,c++中NULL为整数0
	fun(nullptr);//输出2,nullptr为空指针常量,是指针类型
}

(4)C++静态成员

静态数据成员的类型可以是它所属类的类型,而非静态数据成员则受到限制,只能声明为它所属类的指针或引用。

class type
{
    static type a; //ok,静态成员可以是不完全类型
    type *b;       //ok,指针成员可以是不完全类型
    // type c;        //error,数据成员必须是完全类型
public:
    //...
};

静态成员可以作为默认实参,而非静态成员不能。

class student
{
	static int a;
	public:
	void fun(int b=a);
}
  • 类的静态成员函数不能被声明为const,因为const表明该成员函数不会修改该成员函数所属对象,而static成员函数不属于任何对象;
  • static也不能被声明为虚函数,涉及到父子类对象;
  • 不能被声明为volatile;

(5)C++引用

C++中规定,一旦定义引用就必须进行初始化,且不能在再被指定为其他变量的引用。

int a=3,b=1;
int &ref=a;     //ok,ref是a的引用
ref=b;         //将b的值赋给a,而不是将ref绑定到变量b

int &ref;
ref=a;         //error,必须在定义时初始化

引用和指针的区别:

  • 引用不能为空,创建时必须初始化(作为类的数据成员时除外,需要用初始化列表的方式初始化)。指针可以为空,可以任何时候被初始化;
  • 一旦一个引用被初始化为指向一个对象,它就不能再被改变为另一对象的引用。指针则可以随时指向另一对象;
  • 没有NULL引用。可以NULL指针;
  • 如果返回动态分配的对象或内存,必须使用指针。引用可能引起内存泄漏。(无法使用delete+引用方式释放内存)
  • 给引用赋值,改变的是引用绑定的变量的值,而不是使引用与另一对象相关联;

(6)mutable和volatile关键字

(A)mutable

在C++中,mutable是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中,甚至结构体变量或者类对象为const,其mutable成员也可以被修改。mutable在类中只能够修饰非静态数据成员。

#include <iostream> 
using namespace std;
class test
{
    mutable int a;
    int b;
public:
    test(int _a,int _b) :a(_a),b(_b){};
    void fun() const		//fun是const 函数,不能修改类的对象的数据成员,但由于a被mutable修饰,可以修改,但不能修改b
    {
        a += b;
    }
    void print()
    {
        cout << a << "," << b << endl;
    }
};

如果类的成员函数不会改变对象的状态,那么这个成员函数一般会声明成const的。但是,有些时候我们需要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutable来修饰。

(B)volatile

volatile应该解释为“直接存取原始内存地址”比较合适,一般常用于多任务环境下各任务间共享的标志应该加volatile。

(7)final 和 override

(A)final

有时我们会定义这样一种类,我们不希望其他类继承它。C++ 11新标准提供了一种防止继承的方法,即在类名后跟一个关键字final。

class base final {/* */}    //base不能作为基类
class Derived:base { /* */}    //错误,base不能作为基类

此外,final还可以修饰类中的虚函数,表示类成员函数不可以在派生类中进行覆盖。

class base
{
virtual void fun() final {...};
}
class derived:base
{
void fun(){...};     //error
}

(B)override

对于基类中的虚函数,派生类可以选择覆盖或者不覆盖,对于选择覆盖的函数用override加以修饰,表示该函数覆盖了基类的虚函数。

class base
{
public:
virtual void f1();
virtual void f2();
void f3();
}

class derived:public base
{
public:
void f1()override;        //ok
void f2(int) override;    //error,基类中不存在f2(int)虚函数
void f3() override;       //error,f3不是虚函数
}

注:final,override修饰的函数必须为虚函数。

(8)拷贝构造函数必须是一个引用

拷贝构造函数的参数必须是引用,参数传递的方式有两种,值传递和地址传递。

其中值传递即是拷贝原对象的一个副本作为实参,即参数传递的过程中也调用了拷贝构造函数,若拷贝构造函数的参数不是引用的话,会造成无穷递归的调用拷贝构造函数。

而引用是直接操作原对象,因此不会出现上述问题。

(9)模板的特化

模板分为类模板和函数模板,特化分为全特化和偏特化。

  • 全特化:给模板中的所有模板参数指定一个具体的类;
  • 偏特化:部分指定模板参数的类;
  • 类模板可以全特化也可以偏特化;
  • 函数模板只能全特化;
//类模板
template<typename T1, typename T2>
class Test
{
public:
    Test(T1 i,T2 j):a(i),b(j){cout<<"模板类"<<endl;}
private:
    T1 a;
    T2 b;
};

template<>
class Test<int , char>
{
public:
    Test(int i, char j):a(i),b(j){cout<<"全特化"<<endl;}
private:
    int a;
    char b;
};

template <typename T2>
class Test<char, T2>
{
public:
    Test(char i, T2 j):a(i),b(j){cout<<"偏特化"<<endl;}
private:
    char a;
    T2 b;
};
//函数模板
template<typename T1, typename T2>
void fun(T1 a , T2 b)
{
    cout<<"模板函数"<<endl;
}

//全特化
template<>
void fun<int ,char >(int a, char b)
{
    cout<<"全特化"<<endl;
}

//函数不存在偏特化:下面的代码是错误的
/*
template<typename T2>
void fun<char,T2>(char a, T2 b)
{
    cout<<"偏特化"<<endl;
}
*/

(10)const修饰符

(A)指向const常量的指针 和 const指针

//(1)指向const常量的指针:const int *p 或 int const *p

//p是一个指针,指向const int类型的常量;指针指向的内容为常量,因此不能改变*p的值,但指针p可以改变
const int a=2;
const int b=3;
const int *p=&a;
*p=4;         //error,p指向常量a,不能修改
p=&b;        //ok,p只要指向const int类型即可


//(2)const指针:int *const p

//p是一个指针,指针p的值不能改变,但其指向的值可以改变
int a=2;
int b=3;
int *const p=&a;    
*p=4;          //ok,p的内容可以改变
p=&b;         //error,p是常指针,指针值不能修改


//(3)指向const常量的const指针:const int *const p 或 int const *const p

//p是一个const指针,指针值和指向的对象的值都不允许修改
int a=2;
int b=3;
const int *const p=&a;
*p=4;            //error,p指向常量
p=&b;           //error,p是常指针

(B)类的const成员

类的const成员包括const数据成员和const成员函数;

(1)const数据成员:和普通的const变量一样,定义时初始化,且不能修改;
(2)const成员函数:

  • const成员函数只能访问其他的const成员函数,而不能访问非const成员函数;
  • const可以修饰static数据成员,在定义时初始化,但仍要在类外进行声明;
  • const不能修饰static成员函数,因为const表示不修改类的对象,而static成员函数属于类,不涉及到对象;
  • const成员函数不能修改类的对象,即不能修改数据成员,但当数据成员被mutable修饰时,可以修改;
这篇关于C++学习总结3的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!