Chapter 1 函数的高阶使用
1.1 函数的参数默认值
有默认值的参数,参数必须放在参数列表的末尾!!!
例如:
int add(int a, int b = 100){
retrun a + b;
}
// 在调用的时候,已经有默认值的参数可以赋值,也可以不赋值
cout << add(10, 20) << endl;
// 结果是30:给已有默认值的参数赋值,运行时按传参的数进行运算
cout << add(10) << endl;
// 结果是110
1.2 函数的重载(Overload)
如果在一个类中的多个函数满足以下两点要求,则这两个函数构成重载关系(overload):
- 函数名相同
- 参数不同(体现在数量不同,类型不同)
#include <iostream>
using namespace std;
void show(){
cout<<"没有参数"<<endl;
}
void show(int a){
cout<<"int:"<<a<<endl;
}
void show(double a){
cout<<"double:"<<a<<endl;
}
void show(string a){
cout<<"hello,"<<a<<endl;
}
void show(string a,string b){
cout<<"hello,"<<a<<" and "<<b<<endl;
}
int main()
{
show();//没有参数
show(2); //int:2 会选择完全匹配的 如果没有完全匹配如果可以转换也可以
show(2.1); //double:2.1
show("Tom"); //hello,Tom
show("Tom","Jerry");
}
注意!:重载与返回值无关,仅由函数名和参数决定
-
如何区分调用不同的方法?
由参数来区分
-
为什么要使用重载?
使程序更加灵活,使同名方法的参数选择更加灵活
-
注意:函数重载和默认值一起用时可能会导致二义性,要避免一起使用
1.3 内联函数
函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数,主调函数就会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回到主调函数,主调函数根据刚才的状态继续往下执行。一个 C/C++程序的执行过程可以认为是多个函数之间的相互调用过程,它们形成了一个或简单或复杂的调用链条,这个链条的起点是 main(),终点也是 main()。当 main() 调用完了所有的函数,它会返回一个值(例如return 0;)来结束自己的生命,从而结束整个程序。函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两条语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就就不容忽视。
为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数。
指定内联函数的方法很简单,只需要在函数定义处增加 inline
关键字,例如:
#include <iostream>
using namespace std;
//内联函数,交换两个数的值
inline void swap(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int m, n;
cin>>m>>n;
cout<<m<<", "<<n<<endl;
swap(&m, &n);
cout<<m<<", "<<n<<endl;
return 0;
}
-
注意,要在函数定义处添加
inline
关键字,在函数声明处添加inline
关键字虽然没有错,但这种做法是无效的,编译器会忽略函数声明处的inline
关键字。 -
当函数比较复杂时,函数调用的时空开销可以忽略,大部分的 CPU 时间都会花费在执行函数体代码上,所以我们一般只将那些短小的、频繁调用的函数声明为内联函数。
-
内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作的类的数据成员,所以应该尽量使用内联函数来取代宏代码
内联能提高函数的执行效率,那么为什么不把所有的函数都定义成内联函数呢?
-
内联不是万灵丹,它以代码膨胀(拷贝)为代价,仅仅省区了函数调用的开销,从而提高程序的执行效率。(开销指的是参数的压栈、跳转、退栈和返回操作)。
-
一方面,如果执行函数体内代码的时间比函数调用的开销大得多,那么
inline
效率收益会很小。 -
另一方面,每一处内联函数的调用都要拷贝代码,使程序的总代码量增大,消耗更多的内存空间。
以下情况不宜使用内联:
-
如果函数体内代码比较长,使用内联将导致可执行代码膨胀过大。
-
如果函数体内出现循环或者其他复杂的控制结构,那么执行函数体内代码的时间将比函数调用的开销大得多。
Chapter 2 引用(C++)与指针©
2.1 C++引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。一般在初始化变量时,初始值会被拷贝到新建的对象中。然而定义引用时,程序把引用和它的初始值绑定(bind)在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。
引用很容易与指针混淆,它们之间有三个主要的不同:
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
变量名称是变量附属在内存位置中的标签,可以把引用当成是变量附属在内存位置中的第二个标签
#include <iostream>
using namespace std;
int main ()
{
// 声明简单的变量
int i;
double d;
// 声明引用变量
int& r = i;
double& s = d;
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}
如果引用内容是纯数字,引用需要加const
。代表指向内容的值不可改,常引用不可以直接更改变量的值。变量值更改后,会改变引用的值,函数的参数可以const
修饰,不改变引用参数的值。
int &a = 50; // 错误
const int a = 50; // 正确
2.2 引用作为参数
C++将引用作为参数与C语言中指针类似,都是在函数声明和定义的时候在变量前加上&
,例如
#include <iostream>
using namespace std;
// 函数声明
void swap(int& x, int& y);
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值 */
swap(a, b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
// 函数定义
void swap(int& x, int& y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
2.3 引用作为返回值
通过使用引用来替代指针,会使 C++ 程序更容易阅读和维护。C++ 函数可以返回一个引用,方式与返回一个指针类似。当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues(int i) {
double& ref = vals[i];
return ref; // 返回第 i 个元素的引用,ref 是一个引用变量,ref 引用 vals[i]
}
// 要调用上面定义函数的主函数
int main ()
{
cout << "改变前的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
setValues(1) = 20.23; // 改变第 2 个元素 引用放在赋值语句的左边
setValues(3) = 70.8; // 改变第 4 个元素
cout << "改变后的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
return 0;
}
当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
Chapter 3 C++字符串(String类)
C++ 标准库提供了 string 类类型,支持C语言中定义的char数组型字符串的所有的操作。
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str1 = "runoob";
string str2 = "google";
string str3;
int len ;
// 复制 str1 到 str3
str3 = str1;
cout << "str3 : " << str3 << endl;
// 连接 str1 和 str2
str3 = str1 + str2; // 类python操作
cout << "str1 + str2 : " << str3 << endl;
// 连接后,str3 的总长度
len = str3.size();
cout << "str3.size() : " << len << endl;
return 0;
}
String类中常用方法:
length() //获取字符串的长度。
clear() //清除字符串中的所有字符。
empty() //判断字符串是否为空。
assign() //将一个字符串或字符数组赋值给当前字符串。
append() //在字符串末尾添加另一个字符串或字符数组。
insert() //在指定位置插入一个字符串或字符数组。
erase() //删除指定位置或指定范围内的字符。
replace() //替换指定位置或指定范围内的字符。
find() //在字符串中查找指定的字符或子字符串。
rfind() //从字符串末尾开始查找指定的字符或子字符串。
substr() //返回指定位置开始的子字符串。
c_str() //将字符串转换为C风格的字符串。
compare() //比较两个字符串。
operator[] //访问字符串中指定位置的字符。
at() //访问字符串中指定位置的字符,并进行越界检查。
字符串的遍历
-
for(int i = 0; i < str.length; i++)
:C风格,需要首尾长度和一个”遍历指针“ -
for each
:C++风格,不需要首尾长度,仅需要一个接收指针for(char ch:s){ cout<<ch<<" "; // 定义一个char类型的字符去接受s字符串的每一个字符并打印输出或执行其他操作,遇到'\0'结束 }
Chapter 4 C++类和对象
C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。
4.1 类的定义
定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作
类定义是以关键字 class
开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。
class Box
{
public: // public 确定了类成员的访问属性,在类对象作用域内,公共成员在类的外部是可访问的
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。
// 栈内存对象
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
// 堆内存对象
Box *Box3 = new Box; // 声明 Box3,类型为 Box
对象 Box1
和 Box2
都有它们各自的数据成员。类的对象的公共数据成员可以使用直接成员访问运算符 .
来访问
4.2 类成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 ::
来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline
标识符。
// 第一种定义方式:在类内部进行定义,此时函数定义为内联
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void)
{
return length * breadth * height;
}
};
// 第二种定义方式:在类的外部进行定义,需要在函数名前加上类名和范围解析运算符::
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数声明
double getVolume(void);
};
// 成员函数定义
double Box::getVolume(void)
{
return length * breadth * height;
}
4.3 类访问修饰符
防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public
、private
、protected
来指定的。关键字 public
、private
、protected
称为访问修饰符。
一个类可以有多个 public
、protected
或 private
标记区域。每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。**成员和类的默认访问修饰符是 private
**。
4.3.1 公有(public
)成员
公有成员在程序中类的外部是可访问的,可以不使用任何成员函数来设置和获取公有变量的值
#include <iostream>
using namespace std;
class Line
{
public:
double length;
void setLength( double len );
double getLength( void );
};
// 成员函数定义
double Line::getLength(void)
{
return length ;
}
void Line::setLength( double len )
{
length = len;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl; // 打印“Length of line : 6”
// 不使用成员函数设置长度
line.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of line : " << line.length <<endl; // 打印“Length of line : 10”
return 0;
}
4.3.2 私有(private
)成员
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
默认情况下,类的所有成员都是私有的。
实际操作中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数,这也是类封装的概念。
#include <iostream>
using namespace std;
class Box
{
public:
double length;
void setWidth( double wid );
double getWidth( void );
private:
double width;
};
// 成员函数定义
double Box::getWidth(void)
{
return width ;
}
void Box::setWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
Box box;
// 不使用成员函数设置长度
box.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of box : " << box.length <<endl;
// 不使用成员函数设置宽度
// box.width = 10.0; // Error: 因为 width 是私有的
box.setWidth(10.0); // 使用成员函数设置宽度
cout << "Width of box : " << box.getWidth() <<endl;
return 0;
}
4.3.3 受保护的(protected
)成员
protected
(受保护)成员变量或函数与私有成员十分相似,但有一点不同,protected
(受保护)成员在派生类(即子类)中是可访问的。直接上例子
#include <iostream>
using namespace std;
class Box
{
protected:
double width;
};
class SmallBox:Box // SmallBox 是派生类
{
public:
void setSmallWidth( double wid );
double getSmallWidth( void );
};
// 子类的成员函数
double SmallBox::getSmallWidth(void)
{
return width ;
}
void SmallBox::setSmallWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
SmallBox box;
// 使用成员函数设置宽度
box.setSmallWidth(5.0);
cout << "Width of box : "<< box.getSmallWidth() << endl;
return 0;
}
4.4 类的构造函数与析构函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值。
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(double len); // 这是构造函数
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line( double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line(10.0);
// 获取默认设置的长度
cout << "Length of line : " << line.getLength() <<endl;
// 再次设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
初始化列表:当我们定义一个类时,如果这个类有成员变量需要初始化,我们也可以使用初始化列表来进行初始化。举例如下:
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int x, int y) : var1(x), var2(y) {
cout << "Constructor called" << endl;
}
void printVars() {
cout << "var1 = " << var1 << endl;
cout << "var2 = " << var2 << endl;
}
private:
int var1;
int var2;
};
int main() {
MyClass obj(10, 20);
obj.printVars();
return 0;
}
在上面的示例中,我们定义了一个名为MyClass
的类,它有两个私有成员变量var1
和var2
,并在构造函数中使用初始化列表进行初始化。构造函数的参数分别是x
和y
,它们被传递给var1
和var2
进行初始化。
在main()
函数中,我们创建了一个名为obj
的MyClass
对象,并传递了参数10和20。然后,我们调用了obj
的printVars()
函数,该函数打印出了var1
和var2
的值。在创建MyClass
对象时,构造函数被调用,并使用初始化列表对var1
和var2
进行了初始化。这使得代码更加清晰和高效,同时也可以避免潜在的错误。
当我们使用一个已经存在的对象去初始化一个新对象时,或者将一个对象作为参数传递给一个函数时,C++会使用拷贝构造函数来创建一个新的对象。以下是一个简单的示例:
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int x, int y) : var1(x), var2(y) {
cout << "Constructor called" << endl;
}
MyClass(const MyClass& other) : var1(other.var1), var2(other.var2) {
cout << "Copy constructor called" << endl;
} // 拷贝构造函数
void printVars() {
cout << "var1 = " << var1 << endl;
cout << "var2 = " << var2 << endl;
}
private:
int var1;
int var2;
};
void printObj(MyClass obj) {
obj.printVars();
}
int main() {
MyClass obj1(10, 20);
MyClass obj2 = obj1; // using copy constructor
MyClass obj3(obj1); // using copy constructor
obj1.printVars();
obj2.printVars();
obj3.printVars();
printObj(obj1);
return 0;
}
在上面的示例中,我们定义了一个名为MyClass
的类,它有两个私有成员变量var1
和var2
,并在构造函数中进行了初始化。此外,我们还定义了一个拷贝构造函数MyClass(const MyClass& other)
,它将另一个MyClass
对象作为参数,并使用初始化列表将其成员变量复制到新对象中。
在main()
函数中,我们创建了三个MyClass
对象obj1
、obj2
和obj3
,其中obj2
和obj3
使用了拷贝构造函数来复制obj1
的值。然后,我们分别调用了这三个对象的printVars()
函数,以验证它们的成员变量是否正确初始化。
最后,我们定义了一个printObj()
函数,该函数接受一个MyClass
对象作为参数,并调用它的printVars()
函数。在main()
函数中,我们将obj1
作为参数传递给printObj()
函数,这将使用拷贝构造函数创建一个新的MyClass
对象,并将obj1
的值复制到该对象中。
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~
)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
正常栈空间的变量会随着程序的结束而结束,为什么还要使用析构函数进行释放资源?类中若含有指向堆区的指针变量,需要使用析构函数将该指针指向一片内存才可以,否则会出现段错误。
C++存在析构函数的主要原因是为了解决内存管理的问题。在C++中,程序员可以手动地管理对象的生命周期。通常情况下,当一个对象不再需要时,程序员需要显式地删除它。否则,这些对象会一直存在于内存中,直到程序结束。
析构函数是一种特殊的函数,它在对象被删除时自动调用。它的作用是在对象被删除之前执行一些清理工作,例如释放内存、关闭文件、断开网络连接等等。这样,程序员就不必手动地执行这些清理工作,从而避免了很多潜在的错误。
此外,析构函数还可以保证在对象被删除时,其成员变量也会被正确地销毁。例如,如果一个对象包含指向其他对象的指针,析构函数可以确保这些指针所指向的对象也会被正确地销毁,从而避免内存泄漏。
因此,C++中的析构函数是一种非常有用的特性,可以帮助程序员更好地管理对象的生命周期和内存管理,从而使程序更加健壮和可靠
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数声明
~Line(); // 这是析构函数声明
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
4.5 类的友元
类的友元函数是定义在类外部,但有权访问类的所有私有(private
)成员和保护(protected
)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数或类为一个类的友元,需要在类定义中该函数原型前使用关键字 friend
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
// 程序的主函数
int main( )
{
Box box;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth( box );
return 0;
}
4.6 this
指针
当你在 C++ 中创建一个对象时,它会在内存中分配一块空间来存储该对象的数据成员。this
指针用于指向当前正在被执行的成员函数所操作的对象,以便可以访问该对象的数据成员。下面是一个简单的例子:
#include <iostream>
class MyClass {
public:
void printAddress() {
std::cout << "The address of this object is: " << this << std::endl;
}
};
int main() {
MyClass obj1;
MyClass obj2;
obj1.printAddress();
obj2.printAddress();
return 0;
}
在这个例子中,我们定义了一个名为 MyClass
的类,它包含一个名为 printAddress
的公共成员函数。该函数打印出当前正在被执行的对象的地址。
在 main
函数中,我们创建了两个 MyClass
类型的对象 obj1
和 obj2
,然后调用它们的 printAddress
函数。由于 printAddress
函数使用了 this
指针来获取当前对象的地址,因此在调用 obj1.printAddress()
和 obj2.printAddress()
时,它们会输出不同的地址,因为它们是两个不同的对象。
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
bool compare(Box box)
{
return this->Volume() > box.Volume();
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
if(Box1.compare(Box2))
{
cout << "Box2 is smaller than Box1" <<endl;
}
else
{
cout << "Box2 is equal to or larger than Box1" <<endl;
}
return 0;
}
在这个例子中,我们定义了一个 Box
类,它包含两个公共成员函数 compare
和 volume
。
在 main
函数中,我们创建了两个 Box
类型对象 Box1
和 Box2
,调用compare函数
4.7 深拷贝与浅拷贝
C++中的深拷贝和浅拷贝是涉及到对象拷贝时的两个不同概念。如果用户未定义拷贝构造函数,则会调用默认拷贝构造函数,由于编译系统在我们没有自己定义拷贝构造函数时,会在拷贝对象时调用默认拷贝构造函数,进行的是浅拷贝!即对指针拷贝后会出现两个指针指向同一个内存空间。
-
所谓的浅拷贝,就是在拷贝地址函数中,直接完成属性值的拷贝,即地址拷贝
-
所谓的深拷贝,就是开辟一段新的空间,将原来空间里的值拷贝到新空间里去
#include <iostream>
#include <cstring>
class Person {
public:
string name;
int age;
Person(const char *name, int age);
~Person();
Person(const Person &p); // 拷贝构造函数
};
Person::Person(const char *name, int age) {
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
this->age = age;
}
Person::~Person() {
delete[] name;
}
Person::Person(const Person &p) {
this->name = new char[strlen(p.name) + 1];
strcpy(this->name, p.name);
this->age = p.age;
}
int main() {
Person p1("Tom", 18);
Person p2 = p1; // 浅拷贝
Person p3(p1); // 深拷贝
std::cout << "p1's name: " << p1.name << ", age: " << p1.age << std::endl;
std::cout << "p2's name: " << p2.name << ", age: " << p2.age << std::endl;
std::cout << "p3's name: " << p3.name << ", age: " << p3.age << std::endl;
// 修改p1的name
p1.name[0] = 'J';
std::cout << "p1's name: " << p1.name << ", age: " << p1.age << std::endl;
std::cout << "p2's name: " << p2.name << ", age: " << p2.age << std::endl;
std::cout << "p3's name: " << p3.name << ", age: " << p3.age << std::endl;
return 0;
}
4.8 命名空间
C++命名空间是一种将标识符(如变量名、函数名、类名等)封装在一起以避免命名冲突的机制。命名空间中的标识符可以是任何合法的C++标识符,包括变量、函数、类、结构体、枚举等。
例如,您可以在一个命名空间中定义一个名为"my_namespace"
的命名空间,并在其中定义一个名为"my_function"
的函数。那么,当您在另一个命名空间或全局命名空间中定义一个名为"my_function"
的函数时,编译器会自动将这两个函数区分开来,避免命名冲突。
namespace my_namespace {
void my_function() {
// ...
}
}
int main() {
// 调用my_namespace命名空间中的my_function函数
my_namespace::my_function();
return 0;
}
在上面的示例中,我们通过使用"my_namespace::my_function()"
来调用"my_function()"
函数。这样可以确保我们调用的是正确的函数,并避免了命名冲突的问题。
除了命名空间中的函数和变量外,命名空间还可以包含其他命名空间,从而形成嵌套的命名空间。
namespace my_namespace {
namespace nested_namespace {
void my_function() {
// ...
}
}
}
int main() {
// 调用嵌套在my_namespace中的nested_namespace命名空间中的my_function函数
my_namespace::nested_namespace::my_function();
return 0;
}
在上面的示例中,我们定义了一个名为"nested_namespace"
的命名空间,并在其中定义了一个名为"my_function"
的函数。我们可以通过使用"my_namespace::nested_namespace::my_function()"
来调用该函数。
C++的命名空间提供了一种方便而有效的方式来管理和组织代码中的标识符,从而避免了命名冲突的问题。
4.9 explicit
关键字
explicit
关键字,见名知意,explicit adj. 明确的
,C++中的explicit关键字用于指示构造函数不能被用于隐式转换。
假设我们有一个类Person
,其中有一个构造函数可以接收一个int
类型的参数,表示这个人的年龄。我们希望能够通过这个构造函数创建一个Person
对象,而且希望能够通过一个int
类型的变量隐式地创建一个Person
对象。
如果我们在构造函数前面加上了explicit
关键字,编译器就不会允许隐式地将一个int
类型的变量转换为Person
对象。这样做的好处是可以防止一些潜在的错误,比如误将一个int
类型的变量当做Person
对象传递给了某个函数。
#include <iostream>
class Person {
public:
explicit Person(int age) : m_age(age) {}
int getAge() const {
return m_age;
}
private:
int m_age;
};
void printPerson(const Person& person) {
std::cout << "This person's age is " << person.getAge() << std::endl;
}
int main() {
int age = 30;
//Person person = age; // 编译错误
Person person(age); // 必须显式地创建Person对象
printPerson(person);
return 0;
}
在上面的代码中,我们在Person
类的构造函数前面加上了explicit
关键字。在main
函数中,我们尝试使用一个int
类型的变量age
隐式地创建一个Person
对象,但是这会导致编译错误。我们必须显式地创建一个Person
对象,并将age
作为构造函数的参数传递进去。
使用explicit
关键字可以让我们在编写代码的时候更加谨慎,从而减少一些潜在的错误。
4.10 static
关键字
-
在函数内部使用
static
关键字:如果在函数内部使用static
关键字来声明一个变量,那么这个变量就会成为一个局部静态变量,它只会在第一次被调用时进行初始化,并且在后续调用时保持其值。例如:void myFunction() { static int counter = 0; counter++; std::cout << "The counter value is " << counter << std::endl; } int main() { myFunction(); // 输出 "The counter value is 1" myFunction(); // 输出 "The counter value is 2" myFunction(); // 输出 "The counter value is 3" return 0; }
-
在类内部使用
static
关键字:在类内部使用static
关键字来声明一个成员变量或成员函数,它将成为该类的静态成员,它们的值或行为独立于类的任何实例,并且可以通过类名直接访问,而不需要创建类的实例。例如:class MyClass { public: static int counter; // 声明一个静态成员变量 static void incrementCounter() { counter++; } // 声明一个静态成员函数 }; int MyClass::counter = 0; // 定义静态成员变量并初始化为 0 int main() { MyClass::incrementCounter(); MyClass::incrementCounter(); std::cout << "The counter value is " << MyClass::counter << std::endl; // 输出 "The counter value is 2" return 0; }
-
在文件内使用
static
关键字:在 C++ 中,文件作为编译单元的基本单元。在文件内部使用static
关键字来声明一个变量或函数,它将使该变量或函数仅在当前文件中可见,其他文件将无法访问它们。这种用法在限制函数或变量的作用域以及在多个文件中避免命名冲突方面非常有用。例如:// file1.cpp static int count = 0; // file2.cpp static void printCount() { std::cout << "The count value is " << count << std::endl; } int main() { count++; printCount(); // 编译错误,因为 printCount() 函数不可见 return 0; }
4.11 const
关键字
在 C++ 中,const 关键字用于修饰变量、函数参数和成员函数,以表示它们的值是不可修改的。下面是一些 const 关键字修饰的用法示例:
- 修饰变量:
const int a = 10; // a 是一个不可修改的常量
- 修饰函数参数:
void func(const int b) {
// 函数体中不能修改 b 的值
}
- 修饰成员函数:
class MyClass {
public:
void myFunc() const {
// 在成员函数中不能修改成员变量的值
}
private:
int myVar;
};
在上述示例中,const
关键字被用来指示变量、函数参数和成员函数是不可修改的。这有助于确保代码的安全性和可读性,防止在程序中无意中修改了变量或参数值,或者修改了成员变量的值。
4.12 继承
Chapter 5 vector向量
C++中的vector是一个非常有用的容器,它可以动态地存储一组元素,并且支持快速的随机访问和操作。vector是标准模板库(STL)的一部分,因此它具有很好的可移植性和通用性。
下面是一些vector的主要作用:
- 动态数组:vector可以动态地分配内存来存储元素,并且可以在运行时动态调整数组的大小。这使得它比C语言中的静态数组更灵活,因为静态数组在定义时需要指定数组大小。
- 快速随机访问:vector中的元素是在内存中连续存储的,因此可以通过指针算术来快速访问数组中的任何元素。这使得vector比其他STL容器如list和deque更适合需要快速随机访问的情况。
- 插入和删除元素:vector提供了多种方法来插入和删除元素,包括在数组末尾添加元素、在数组中间插入元素以及删除数组中的元素。这使得它比其他容器如链表更适合需要频繁插入和删除元素的情况。
- 向量算法:vector提供了许多有用的算法,例如排序、查找、二分查找、拷贝和反转。这使得它成为处理数据的有用工具,例如处理图像、音频和视频。
-
创建一个空vector:
#include <vector> using namespace std; vector<int> myVector; // 创建一个空的vector
-
向vector中添加数据(向栈的操作一样):
myVector.push_back(1); // 向vector中添加一个元素1 myVector.push_back(2); // 向vector中添加一个元素2 myVector.push_back(3); // 向vector中添加一个元素3
-
获取vector的大小:
int size = myVector.size(); // 获取vector的大小,即元素个数
-
访问vector中的元素:
int element = myVector[0]; // 访问vector中第一个元素
-
遍历vector中的所有元素:
for (int i = 0; i < myVector.size(); i++) { int element = myVector[i]; // 访问第i个元素 // 对元素进行操作 }
-
删除vector中的元素:
myVector.erase(myVector.begin() + 1); // 删除第二个元素
-
清空vector:
myVector.clear(); // 清空vector中的所有元素