C++: 16.03.04实验课总结
标签: C++ 实验课
by 小威威
1.缺省构造函数
缺省构造函数主要有两种类型:(1)无形参;(2)有形参。
class Student {
public:
Student(); // 无形参
Student(int a1, int b1 = 0, int c1 = 0); // 有形参
private:
int a;
int b;
int c;
};
当定义对象时,如:
Student st; // valid
Student st(); // invalid
还有另外一种定义方式:
Student st = Student(); // valid
虽然构造函数括号里没有参数,但是定义对象时不能加上空括号,这时构造函数特殊的地方。如果加上括号,编译器会识别成Student() st
,错误信息显示没有Student()这个类。
第二种定义对象的方式类似于一般的函数调用。
但是缺省的参数是有规定的。如上面的构造函数,只能有两种缺省类型:
Student (int a1, int b1 = 0, int c1 = 0); \\ valid
Student (int a1, int b1, int c1 = 0); \\ valid
Student (int a1 = 0, int b1 = 0, int c1 = 0); \\ invalid
Student (int a1 = 0, int b1 = 0, int c1); \\ invalid
Student (int a1 = 0, int b1, int c1 = 0); \\ invalid
Student (int a1 = 0, int b1, int c1); \\ invalid
即第一个参数不能缺参,不能间隔缺参,不能所有参数都缺省。第一,编译器无法识别你输入的形参是哪一个,第二,对于全部参数缺省,会导致调用函数产生歧义。因为你将三个参数都缺省,这就与没有参数的Student()
重复。一旦调用无参数,编译器不知道是调用哪个构造函数。
因此,如果要三个参数都缺参,必须删去Student()
,即是:
class Student {
public:
Student(int a1, int b1 = 0, int c1 = 0); // 无需定义Student()
private:
int a;
int b;
int c;
};
2.谷歌风格问题
(1)构造函数只有一个参数的时候,要在其声明的语句前加上explicit。不加也不会编译出错,只是不符合谷歌风格。
explicit Student(int a1);
(2)strcpy不符合谷歌风格,应使用strncpy或者snprintf。这里我要着重介绍一下snprintf的用法。
snprintf(str, size_max_str, key);
snprintf位于的头文件中,是C函数库里的函数。
第一个参数是目标字符串,也就是我们要复制过去的位置。
第二个参数是第一个参数也就是目标字符串的最大字节。
第三个参数就是我们要拷贝的内容。
snprintf很特别,编译器会得到key的字节数然后跟第二个参数比较。
如果key的字节数小于第二个参数,那么就直接把key的内容复制到str中并加上’\0’。
如果key的字节数大于等于第二个参数,那么就将key中的前(size_max_str - 1)字符复制到str中,并加上’\0’。这点是要值得注意的。
3.浅复制与深复制
浅复制通俗来讲就是系统定义的复制构造函数,即不会出现在我们的代码中。
深复制通俗来讲就是我们自己定义的复制构造函数,即会出现在我们的代码中。
当一个类没有指针成员,我们一般可以直接忽略复制构造函数,但是一旦有指针成员,就要注意了,浅复制可能会导致意想不到的后果。浅复制指针对象,只是将一个指针直接赋值给另外一个指针,即两个指针的内容是一样的,也就是说两个指针指向同一块内存。一旦析构函数中有delete语句,可能会导致内存二次释放,引发意想不到的后果。但是,对于深复制,是将指针指向的内容进行复制,使得两个指针不指向同一块内存而分别指向两块内容相同的内存。这样就不会因为delete而导致二次释放内存。更详细地来讲旧时我们在复制构造函数中用new语句在堆中重新分配一块内容来存储指针指向的对象,然后将内存的地址返回给复制的指针。
浅复制与深复制是一个很重要的问题,值得我们注意!
4.数字与字符串的转换
在这里我要介绍两种方法:第一,用snprintf函数;第二,通过stringstream进行转换。
先上代码:
# include <iostream>
# include <cstdio>
# include <string>
# include <sstream>
using namespace std;
int main(void) {
char str1[100];
char str2[100];
char str3[100];
int j = snprintf(str1, sizeof(str1), "Hello~"); \\ 复制
cout << "j: " << j << " " << "str1: " << str1 << endl;
j = snprintf(str2, sizeof(str2), "The word I want to say: %s", "Hello"); \\ 格式化
cout << "j: " << j << " " << "str2: " << str2 << endl;
j = snprintf(str3, sizeof(str3), "%d", 111); \\ 格式化(转化)
cout << "j: " << j << " " << "str3: " << str3 << endl;
stringstream st, st1;
st << 111;
string str4;
st >> str4;
cout << "str4: " << str4 << endl;
st1 << 121;
const char* str5;
str5 = st1.str().c_str();
// st1.str()将str1中的内容转化为string类型。str1.str().c_str()将string转化成cstring
cout << "str5: " << str5 << endl;
return 0;
}
在上文我有提到,snprintf可代替strcpy函数。接下来我将要讲解snprintf的第二个用法:就是字符串的格式化。
snprintf函数的格式如下:
int snprintf ( char * s, size_t n, const char * format, content );
用snprintf可以将数字转化成字符串类型,但是snprintf函数有个缺陷就是收到目标字符串的大小的影响。一旦要格式化的字符串大于目标字符串所占字节数,将会导致字符串的复制不完整!
因此,这种转化用stringstream对象来实现较好。
stringstream类是定义于<sstream>
的头文件内,它通过”<<” 与”>>”来实现内容的流入与流出。其实,stringstream对象就想中介,他存储着你输入的内容,然后输出到你想要转化的类型的对象中去,既不用考虑大小,代码写起来也比较简单。
但要注意的是:一个stringstream对象只能做一次中介。倘若要转换新的字符串,那么要定义一个新的stringstream对象。stringstream::clear函数是进行缓冲区重置,并不是清空缓冲区。如stringstream对象st:
st << 111;
st << 121;
st的输出结果仍是111
st << 111;
st.clear();
st << 121;
那么st的输出结果便是111121
4.返回对象与返回引用的区别
首先我们先来说明一下复制构造函数的执行条件:
(1)对象赋值操作。
// 假设有一个类为Box。
Box box1, box2;
box2 = box1; // 对象赋值操作
(2)函数调用。
// 假设有一个类为Box。
Box box1;
Box box2(box1);// 函数调用,传入对象作为参数
(3)对象作为函数返回值。(复制一个临时对象返回到调用函数处)
Box Change {
Box x1;
return x1; // 对象作为函数返回值
}
返回对象会执行复制构造函数,返回引用不会执行复制构造函数。
检验的方法很简单,在类的析构函数里输出内容,然后根据输出语句的数量就能作出判断。
以上内容皆为本人观点,欢迎大家提出批评和指导,我们一起探讨!