头文件
#pragma once起到与#include守卫相同的作用,但具有几个优点,包括:减少代码,避免名称冲突,以及提高编译速度.
#pragma和ifndef的区别
#pragma once和#ifdef都是旨在使当前源文件仅在单个编译中包含一次。在能够支持这两种方式的编译器上,二者并没有太大的区别,但是两者仍然还是有一些细微的区别。
首先#pragma once是编译器相关的,有的编译器支持,有的编译器不支持,就是说移植型差。#pragma once则由编译器提供保证:同一个文件不会被编译多次。注意这里所说的“同一个文件”是指物理上的一个文件;
缺点就是,某个头文件有多份拷贝,本方法不能保证他们不被重复包含。
#ifndef方式,这个是C++语言相关,这是C++语言中的宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的。如果写的程序要跨平台,最好使用这种方式。#ifndef由语言支持,移植性好。它依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。另外,为了保证不同头文件中的宏名不冲突,故采取类似于_ABC_H_的取名方式。其中,abc.h为当前头文件名。
#ifndef的方式依赖于宏名字不能冲突,所以这可以保证同一个文件中的内容不会被包含多次,也能保证多份拷贝的文件不会被同时包含;缺点: 就是如果不同内容头文件的宏名不小心“撞车”,编译器则只能包含其中一个,想包含的另一个头文件包不进去。
综上:
#ifndef 保证宏名不重复;
#pragma once保证物理文件不重复;
性能上的区别
使用 #ifndef 的话,编译器每次看到#include这个文件都需要读入文件,解析代码;
而使用#pragma once 编译器根本不会重复打开文件, 大大提高了效率。
·编码风格上的区别
使用#pragma once的代码简洁,重要的是它避免了头文件标号(如_myheader_h_)的 重定义或者 #endif 包含范围错误的情况。
·语意上的区别
#pragma once是针对文件的,它告诉编译器,本文件只编译一次。
#ifndef…#define…#endif 只是针对文件中的某一个标号而言的,它能防止三个指令间包含的内容的重复性处理。后者更灵活。
可移植性方面
#pragma once是微软的开发工具中所使用的,如 msvc 等工具可以完好的支持;
#ifndef #define #endif是C++标准里面的一部分,对于任何完好支持c/c++的编译器都能使用。显而易见,后者的可移植性更高。
善于使用inline函数
具体参考另一篇文章C++ 中 inline 的用法
使用成员初值列
成员变量初始化有三种方式:
- 在构造函数体内赋值初始化
- 在自定义的公有函数体中赋值初始化(一般用于成员变量的初始化)
- 在构造函数的成员初始化列表初始化
一、构造函数体内初始化
说明:在构造函数体内的初始化方式,本质是是为成员变量赋值,而不是真正意义上的初始化,这点要特别注意!(下面介绍成员初始化列表时会有演示案例对比说明)
class Cperson
{
private:
int m_age;
float m_height;
char* m_name;
public:
Cperson(int age,float height,const char* name)
{
m_age=age;
m_height=height;
if(m_name)//先判断当前是否为空
delete[] m_name;
if(name)//如果外部传入的不为空
{
int len=strlen(name);
m_name=new char[len+1];//创建内存
strcpy(t m_name,name);
}
else
m_name=nullptr;
}
}
二、自定义的公有函数体中赋值初始化
说明:与构造函数体内初始化方式一样,此种方式本质上也是赋值,而不是初始化
class Cperson
{
private:
int m_age;
float m_height;
char* m_name;
public:
void setPerson(int age,float height,const char* name)
{
m_age=age;
m_height=height;
if(m_name)//先判断当前是否为空
delete[] m_name;
if(name)//如果外部传入的不为空
{
int len=strlen(name);
m_name=new char[len+1];//创建内存
strcpy(t m_name,name);
}
else
m_name=nullptr;
}
}
三、成员初始化列表初始化
写在构造函数的后面,随着构造函数的执行而执行
初始化顺序:
初始化顺序与书写的在构造函数后的顺序无关,而与成员变量的定义顺序有关(下面有演示案例)初始化列表初始化优先于构造函数内的代码执行顺序
一般只对无动态内存的成员、const成员、引用初始化(const成员、引用成员必须在初始化列表初始化)
成员初始化列表初始化效率更高
有动态内存的成员必须在构造函数内部进行初始化(为什么?因为动态内存不能进行简单的赋值,因此所存在的地址不同,要自己申请动态内存并初始化)。牢记:内部数据内部处理,外部数据外部处理
class Cperson
{
private:
int m_age;
float m_height;
char* m_name;
public:
Cperson(int age,float height,const char* name);
}
//m_name为指针类型,需要自己申请空间
Cperson::Cperson(int age,float height,const char* name):m_age(age),m_height(height)
{
if(m_name)//先判断当前是否为空
delete[] m_name;
if(name)//如果外部传入的不为空
{
int len=strlen(name);
m_name=new char[len+1];//创建内存
strcpy(m_name,name);
}
else
m_name=nullptr;
}
- 根据上面的三种方式,总结出:成员初始化列表初始化成员才是真正意义上的初始化,其他两种方式都是为赋值
- 初始化和赋值涉及到底层效率的问题:初始化是直接初始化。而赋值是先初始化一个临时变量,再赋值。前者效率高
参数传递
什么时间传引用、什么时间传值;什么时间返回引用、什么时间返回值;
需要连续赋值??
函数后面加const
当函数内容不发生改变时,一定加上const,因为设计类与使用类的可能不是一个人。当错误使用,编译器会报错,例如下面代码:
#include <iostream>
using namespace std;
class mycomplex {
public:
mycomplex(double r = 0, double i = 0) :re(r), im(i) {}
double real() { return re; }
double imag() { return im; }
private:
double re, im;
};
int main() {
const mycomplex c1(2, 1);
cout << c1.real() << endl;
cout << c1.imag() << endl;
}
将ctor放入private中
一种设计模式,单例模式,注意static使用。
调用static函数的方式:
- 通过对象
- 通过类名
类的成员函数带有隐藏this指针,但静态成员函数中没有
构造函数、拷贝构造函数、拷贝赋值函数
为什么必须有copy ctor 和 copy op=?
经典写法,但还可以考虑安全性的解法,见《剑指offer》:由于内存不足导致分配失败,可以先进行new新内容,再释放旧内容,分配失败时,实例不会被修改。或者下面的思路:
inline
String& String::operator=(const String& str){
if(this!=&str){
String strTemp(str);
char* pTemp=strTemp.m_data;
strTemp.m_data=m_data;
m_data=pTemp;
}
return *this;
}
将自身实例与临时实例进行交换,由于strTemp是临时对象,运行到if外面会自动析构掉