C++基础杂记(2)

发布时间 2023-10-31 13:26:56作者: Torch_HXM


将数组传入函数

void test(int array[]);
void test(int* array);

两种传递方式完全等价,但是第一种传递方式的可读性更高。

禁止修改数组的值

void test(const int array[]);
void test(const int* array);

注意,const 能用则用,如果声明函数时。函数的参数是带 const 的,则函数可以接受带 const 或不带 const 的参数。若函数参数不带 const,则只能接受不带 const 的参数。

函数的地址与函数的指针

#include <iostream>
using namespace std;

void myPrint()
{
        cout<< "myPrint\n";
        return;
}

void getResult(void (*printMethod)(void))   // 函数的参数为函数指针/地址
{
        (*printMethod)();           // printMethod(); 同样有效
        return;
}

int main(int argc, char** argv)
{
        void (*myPrintPtr)(void);   // 函数指针
        myPrintPtr = myPrint;       // 将函数地址赋值给函数指针

        cout<< "myPrint 函数地址:"<< (void*)myPrint<< endl;        // 由于cout没有输出函数地址类型的重载,因此需要显示的将函数地址声明为指针类型才能有效输出。否则会默认输出boolean值
        cout<< "myPrint 函数指针:"<< (void*)myPrintPtr<< endl;     // 由于cout没有输出函数指针类型的重载,因此需要显示的将函数指针声明为指针类型才能有效输出。否则会默认输出boolean值
        cout<< "myPrint 函数指针地址:"<< &myPrintPtr<< endl;

        getResult(myPrint);         // 可以接受函数地址
        getResult(myPrintPtr);      // 可以接受函数指针
        return 0;
}

myPrint 函数地址:0x55b39f80097a
myPrint 函数指针:0x55b39f80097a
myPrint 函数指针地址:0x7ffd48cd26b0
myPrint
myPrint

注意,下列示例中,直接将函数声明为指针是不合法的:

void (*myPrint)()
{
       cout<< "myPrint\n";
       return;
}

函数的指针只是一个指针,不能像函数一样链接代码块。

函数的指针数组

void (*ptr[3])(void) = {func1, func2, func3};   // 定义一个包含三个函数地址的函数指针数组

上述代码可以使用 typedef 简化:

typedef void(*void_ptr)(void);
void_ptr pa[3] = {func1, func2, func3};

函数的 static 与 inline

  • static 静态函数。限制函数的作用域为本文件,不能够被其他文件调用
  • inline 内联函数。建议编译器将函数体嵌入调用他的地方,类似于宏定义的替换。这样可以使得函数在被调用时,程序不需要跳转到函数处,可以提高效率,但占用内存。inline 修饰的函数不能够先声明后定义,只能够直接进行定义。

引用左值和引用传参

  • 当将临时变量赋给引用时,该引用需要被const修饰
    double a = 10.0;
    const int & b = (int)a;   // (int)a 产生临时变量,一般来讲,右值表达式都将产生临时常量。临时常量无法被寻址,在左值处加入 const 修饰会让编译器创建一个无名常量记录临时常量的信息,该无名常量可以被寻址,但不可以被改变。
    
  • 以普通引用作为形参的函数不能接收常量作为实参传递
    #include <iostream>
    using namespace std;
    void test(int & a);
    int main()
    {
        const int a = 10;   // 常量
        test(a);            // 将常量传递给普通引用函数
        return 0;
    }
    void test(int & a)
    {
        cout<< a<< endl;
    }
    

    报错:test.cpp:10:8: error: binding reference of type ‘int&’ to ‘const int’ discards qualifiers

  • 当函数以常量引用作为形参时,可以接受非常量传参
  • 更多细节参考文章

C++11 的数组 for 循环

int a[] = {1, 2, 3, 4};
for(int b:a)
{
    cout<< b<< " ";
    ++b;
}
cout<< endl;

for(int b:a)
{
    cout<< b<< " ";
}
cout<< endl;

1 2 3 4
1 2 3 4
这里第一个for循环中的数组a中的每一个值相当于按值传递给b,b的地址是唯一的且不指向a数组中的值。修改b无法修改a数组。

int a[] = {1, 2, 3, 4};
for(int & b:a)
{
    cout<< b<< " ";
    ++b;
}
cout<< endl;

for(int b:a)
{
    cout<< b<< " ";
}
cout<< endl;

1 2 3 4
2 3 4 5
想要修改a数组,则需要在第一个for循环中,让a数组中的值传引用到b,此时b指向a数组中的值,修改b可以修改a数组。

64 位 Linux 操作系统中 C++ 中常见基本类型所占字节数

pointer: 8字节
char : 1字节
unsigned char : 1字节
char16_t : 2字节
short : 2字节
unsigned short : 2字节
char32_t : 4字节
w_char : 4字节
int : 4字节
unsigned int : 4字节
float : 4字节
long : 8字节
long long : 8字节
unsigned long (long) : 8字节
double : 8字节
long double : 16字节

字节数比指针大的,声明函数时按参传递会有优势。

C++11 类成员变量的初始化

默认成员初始化器

在声明变量时直接初始化

class Baby
{
public:	
	Baby(const std::string name) : _name_(name) {}
	Baby(int age) : _age_(age) {}
	
private:
	// 默认成员初始化, 在声明处就进行初始化。
	std::string _name_ = "Unknown";
	int _age_ = 0;
	int _weight_ = 0;
	int _height_ = 0;
};

成员变量初始化列表

class Baby
{
public:	
	// 采取成员初始化列表方式,大量成员变量在多个初始化列表中重复出现
	Baby(const std::string name):_name_(name), _age_(0), _weight_(0), _height_(0){}
	Baby(int age) :_name_("Unknown"), _age_(age), _weight_(0), _height_(0) {}
	// ...省略100个构造函数

	// 采取先默认构造,再在构造函数中赋值,有机会去除这类重复代码。
	Baby(const std::string name){
		init(name, 0, 0, 0);
	}
	Baby(int age){
		init("Unknown", age, 0, 0);
	}
	// ...省略100个构造函数

	void init(const std::string& name, int age, int weight, int height)
	{
		_name_ = name;
		_age_ = age;
		_weight_ = weight;
		_height_ = height;
	}

private:
	std::string _name_;
	int _age_;
	int _weight_;
	int _height_;
};

传统的,在构造函数函数体里面进行初始化,实际上是先将变量初始化为0再赋值。而列表初始化,是直接在为变量分配内存时就初始化为对应的值。效率更高。

委托构造函数

如果构造函数需要更多的重构,写起来会很麻烦,C++11 允许我们将这些重构委托给一个构造函数

class Baby
{
public:	
	Baby(const std::string& name, int age, int weight, int height) :
		_name_(name), _age_(age), _weight_(weight), _height_(height) {}
	Baby(const std::string name) : Baby(name, 0, 0, 0) {}	// 委托构造函数方式
	Baby(int age) : Baby("Unknown", age, 0, 0) {}	// 委托构造函数方式
	// ...省略100个构造函数

private:
	std::string _name_;
	int _age_;
	int _weight_;
	int _height_;
};

定义对象时使用列表初始化

在 C++11 中,类可以如同结构体一般,在定义对象时使用列表进行初始化。前提是我们的构造函数中有相对应的方法。

class Baby
{
public:	
	Baby(const std::string& name, int age, int weight, int height) :
		_name_(name), _age_(age), _weight_(weight), _height_(height) {}

private:
	std::string _name_;
	int _age_;
	int _weight_;
	int _height_;
};

int main()
{
    Baby baby = {"name", 10, 10, 10};   // 复制列表初始化
    Baby baby {"name", 10, 10, 10};     // 列表初始化
}

当构造函数没有被 explicit 修饰时,复制列表初始化与列表初始化都是合法的,但是当构造函数被 explicit 修饰时,被修饰的构造函数的类,不能发生相应的隐式类型转换,此时最好使用列表初始化而不使用复制列表初始化。

另外,类中成员变量的初始化顺序取决于他们在类中被声明的顺序。

友元可以访问类的私有变量

class Baby
{
private:
    std::string baby_name;
    int baby_age;
    double baby_weight;
public:
    Baby(const std::string & _baby_name, const int _baby_age, const double _baby_weight):baby_name(_baby_name), baby_age(_baby_age), baby_weight(_baby_weight){}
    inline void showName() const {std::cout<<"Name: "<<baby_name<<std::endl;}
    inline void showAge() const {std::cout<<"Age: "<< baby_age<<std::endl;}
    inline void showWeight() const {std::cout<<"Weight: "<<baby_weight<<std::endl;}
    inline void showAll() const {showName();showAge();showWeight();}
    friend std::ostream & operator<<(std::ostream & os, const Baby & baby); // 友元声明
};

std::ostream & operator<<(std::ostream & os, const Baby & baby) // 友元定义
{
    os<<"Name: "    <<baby.baby_name    <<std::endl     // 友元使用了私有变量
      <<"Age: "     <<baby.baby_age     <<std::endl     // 友元使用了私有变量
      <<"Weight: "  <<baby.baby_weight  <<std::endl;    // 友元使用了私有变量

    return os;
}

int main()
{
    Baby baby = {"name", 2, 15};
    baby.showAll();                 // 打印全部信息
    std::cout<<baby;                // 打印全部信息
    return 0;
}

示例中,类可以通过他自己的公共方法 showAll 来打印信息,也可以通过友元重载 << 运算符来打印信息。在友元的定义中,我们访问了类的私有变量。友元可以访问类的私有变量。