编程语言
首页 > 编程语言> > C++总结——语法篇

C++总结——语法篇

作者:互联网

Static关键字

1、静态全局变量

static修饰全局变量使变量成为静态全局变量。该变量存储在静态存储区,只能在本文件中使用,因此其他文件还可以定义名字相同的变量,不会发生冲突。

2、静态局部变量

是指static修饰局部变量。作用域仍然是局部作用域,存放在内存的全局数据区,直至程序运行结束前都不会消失,但只在定义它的函数中可见,只初始化一次。未赋初值时会自动为0。

3、静态函数

指static修饰函数时,该函数变为静态函数。只能在声明他的文件中可见,不能被其他文件使用。其它文件中可以定义相同名字的函数,不会发生冲突。

4、静态数据成员

static修饰类的数据成员。特点:

  1. 对于普通数据成员,每个类的实例都会有一份自己的拷贝。而对于静态数据成员来说,它不属于任何类的对象,无论类产生了多少对象,静态数据成员在程序中也只有一份,由该类的所有对象共享访问,即所有对象操作同一个静态数据成员。
  2. 静态数据成员定义时要分配空间,所以不能在类声明中定义。静态数据成员必须在类内声明,类外初始化。由于静态数据成员不属于任何一个对象,只属于类,所以对静态数据成员的所有操作(除声明外)都要使用类名作为作用域进行操作。并且静态数据成员和普通数据成员一样遵从public,protected,private访问规则。
  3. 静态数据成员存储在全局数据区,但静态成员变量不占用类的大小,在编译时创建并进行初始化

5、静态成员函数

引用

引用与指针的区别

string& foo() {
    string* ptr = new string("123");
    return *ptr;
}
string temp = foo();  //new生成的这块内存无法释放 
//上面这句话可以如下处理,但会麻烦
string& tmp = foo();
string str = tmp;
delete &tmp;

左值、右值、左值引用、右值引用

左值:既能够出现在等号左边,也能出现在等号右边的变量。即左值是可寻址的变量,有持久性,能被修改的变量
右值:只能出现在等号右边的变量。即右值一般是不可寻址的常量,或在表达式求值过程中创建的无名临时对象,短暂性的。右值无法被修改
左值引用:引用一个对象;
右值引用:就是必须绑定到右值的引用,C++11中右值引用可以实现“移动语义”,通过 && 获得右值引用。
右值引用的作用之一:移动语义(std::move),用来减少临时资源的开辟

class Person {
public:
	//默认构造函数
	Person() {
		m_Age = 0;
		m_Height = nullptr;
	}

	//拷贝构造函数
	Person(const Person& p) {
		m_Age = p.m_Age;
		m_Height = new int(*p.m_Height);  //深拷贝,防止浅拷贝
		//cout << "拷贝构造函数申请的地址为:" << m_Height << endl;
		cout << "调用了拷贝构造函数" << endl;
	}

	//移动构造函数
	Person(Person&& p) noexcept {
		m_Age = p.m_Age;
		m_Height = p.m_Height;
		p.m_Height = nullptr;
		cout << "调用了移动构造函数" << endl;
	}

	//赋值运算符
	Person& operator=(Person& p) {
		m_Age = p.m_Age;
		m_Height = new int(*p.m_Height);
		//cout << "赋值运算符申请的地址为:" << m_Height << endl;
		return *this;
	}
	//移动赋值运算符
	Person& operator=(Person&& p) noexcept {
		//先自我检测,释放自身资源
		if (this != &p) {
			delete m_Height;
		}
		
		m_Age = p.m_Age;			//接管p的资源
		m_Height = p.m_Height;

		p.m_Height = nullptr;      //将p的资源置空
	}

	//有参构造函数
	Person(int age, int height) {
		m_Age = age;
		m_Height = new int(height);
		//cout << "有参构造函数申请的地址为:" << m_Height << endl;
		cout << "调用了有参构造函数" << endl;
	}

	//析构函数
	~Person() {
		if (m_Height != nullptr) {
			delete m_Height;
			m_Height = nullptr;
		}
	}

	int m_Age;
	int* m_Height;
};


int main(){
	//传统的左值引用
    int a = 10;
    int& b = a;  // 定义一个左值引用变量
    b = 20;      // 通过左值引用修改引用内存的值

    //下面一行代码无法通过编译,因为等号右边的数无法取地址
    int &var = 10;  

    //上面一行代码可以改成如下的常引用,理由上面已经说过
    const int& var = 10; 
    
    //但改成常引用就无法修改var的值了,因此需要使用右值引用来解决问题
    //下面这行代码就能编译通过
    int&& var = 10;

    //并且右值引用也能改变值
    var = 1; 

	vector<Person> vec;
	Person p1(20, 160);

	vec.push_back(p1);			//p1会在传入参数时调用赋值构造函数被拷贝一次,之后马上销毁
	vec.push_back(std::move(p1));	//p1会被转换成右值,于是会调用移动构造函数,不会调用拷贝构造,
									//提升效率	
 }

右值引用的作用之二:完美转发
完美转发,它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。
看下面这段代码

template<typename T>
void f(T&& x){ 
    cout << ++x; 
}
f(2); // 3

函数f接受一个右值x,但是f(2)这条语句将2传入时,可以对2进行自增。这说明右值引用本身时左值,即x时左值。那么在传参时就会出现问题。看下面这段代码

template<typename T>
void g(T&& x) {
    cout << "右值" << endl;
}

template<typename T>
void g(T& x) {
    cout << "左值" << endl;
}

template<typename T>
void f(T&& x) {
    cout << "f右值引用" << endl;
    g(x);
}
template<typename T>
void f(T& x) {
    cout << "f左值引用" << endl;
    g(x);
}

结果如下:
在这里插入图片描述

一共有三个函数,f函数调用了g函数,g函数的作用是如果传入的参数是左值就输出左值,如果传入的参数是右值就输出右值。但是由于g函数的参数是通过f函数传入的(即x先通过外部的f函数传入,再由f函数传给g),经过第一次传参时x已经变为左值了(可以和第一个例子比对着看),所以g(x)永远只会输出左值(即永远只会和第二个函数模板匹配),这明显与我们想要的结果不匹配。那么我们可以这么写:

template<typename T>
void f(T&& x) {
    cout << "f右值引用" << endl;
    g(std::forward<T>(x));
}
template<typename T>
void f(T& x) {
    cout << "f左值引用" << endl;
    g(std::forward<T>(x));
}

此时,输出的结果如下:
在这里插入图片描述
符合我们的预期。std::forward函数保持了 x 的引用类型。
那么再结合移动语义想象一下这样的一个例子:你需要通过函数传入的参数进行一个赋值(或者拷贝操作),但是如果不保持参数的性质,虽然你为了减小开销,外面传入的是一个右值,但是一进入函数就会变成左值。举个例子:
回到之前的移动语义的例子中来,之前的代码不变,我们增加一个函数

void fun(Person&& p) {
	Person p2(p);
}
int main(int argc, char* argv[])
{
	Person p1(20, 160);
	fun(std::move(p1));
}

结果如下:
在这里插入图片描述
我们在主函数中构造了P1对象,fun()函数本义是利用移动构造函数来减小复制次数,但是可以看到尽管我们在main函数中使用了右值,但是fun函数里任然使用了拷贝构造函数。我们做如下修改:

Person p2(std::forward<Person>(p));

结果如下:
在这里插入图片描述
可以看到符合我们的预期

new delete与malloc free

相同点:都能从堆上申请、释放空间
区别:

  1. new与delete属于运算符,而malloc是函数
  2. malloc只负责开辟空间,new不仅仅有malloc的功能,可以进行数据的初始化
  3. malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常
  4. free只会释放空间,而delete会先调用类的析构函数,在释放空间。

标签:总结,静态数据,函数,右值,int,成员,C++,语法,引用
来源: https://blog.csdn.net/qq_43903506/article/details/122397223