c++学习笔记
c++学习笔记
前言:打算过一遍书吧,不得不说这教材写的不错
1 指针和引用
引用的定义:int& b=a
通过该方式就定义了一个引用b,引用可以理解为变量名的别称,每一个变量名对应着一个内存地址,编译器会根据
变量名去查找内存地址,而每一个指针变量则存储着一个内存地址,这就是二者的区别
2 对象的创建
int main()
{
Myclass* pobj;
pobj=new Myclass();
delete pobj;
Myclass obj1=Myclass();
Myclass obj2;
return 0;
}
基本的实例化对象的方式就是上面这三种,动态发呢配对象时记得delete
3 普通内联函数和成员内联函数
对于代码量小且被频繁调用的函数,为了提高运行速度,可以将其设置为内联函数
通过将函数定义为内联函数,编译器会尝试在每次调用该函数时,将函数的代码直接插入到调用点,从而避免函数
调用的开销。
内联函数适用于代码量小且频繁调用的函数,可以减少函数调用的开销,提高程序性能。而对于代码量大或递归调
用的函数,内联化可能带来负面影响,因此不适用。
对于普通内联函数,只需要在函数声明前加上inline即可
对于成员内联函数而言,也是一样,编译器会将函数插入到调用的地方,但需要注意的是定义方式
像我们平时定义成员函数时直接将其写到类中,这样就构成了隐式声明内联函数,
#include <cstdio>
#include <iostream>
using namespace std;
class Myclass
{
public:
int x;
Myclass()
{
cout << "Constructor called" << endl;
}
~Myclass()
{
cout << "Destructor called" << endl;
}
};
int main()
{
Myclass* pobj;
pobj = new Myclass();
delete pobj;
Myclass obj1 = Myclass();
Myclass obj2;
return 0;
}
如果想要显示声明,可以这样
class Myclass
{
public:
int x;
Myclass();
~Myclass();
};
inline Myclass::Myclass()
{
cout << "Constructor called" << endl;
}
inline Myclass::~Myclass()
{
cout << "Destructor called" << endl;
}
在外部声明时加上inline关键字声明其为内联函数,如果不想让其为内联函数,则需要这样
#include <cstdio>
#include <iostream>
using namespace std;
class Myclass
{
public:
int x;
Myclass();
~Myclass();
};
// 在类定义外部定义构造函数
Myclass::Myclass()
{
cout << "Constructor called" << endl;
}
// 在类定义外部定义析构函数
Myclass::~Myclass()
{
cout << "Destructor called" << endl;
}
int main()
{
Myclass* pobj;
pobj = new Myclass();
delete pobj;
Myclass obj1 = Myclass();
Myclass obj2;
return 0;
}
4 拷贝构造函数
如果我们想将一个对象的值传递给另一个对象就得使用拷贝构造函数,对于声明的这个函数其定义为:
#include <cstdio>
#include <iostream>
using namespace std;
class Myclass
{
public:
int x;
Myclass()
{
cout << "Constructor called" << endl;
}
// 拷贝构造函数
Myclass(const Myclass &other)
{
cout << "Copy constructor called" << endl;
x = other.x;
}
~Myclass()
{
cout << "Destructor called" << endl;
}
};
int main()
{
Myclass obj1;
obj1.x = 10;
Myclass obj2 = obj1; // 调用拷贝构造函数
cout << "obj2.x = " << obj2.x << endl;
return 0;
}
如果不想其内联,也可也放在类的外部进行定义
其作用就是复制一个对象的值给另一个对象,想要调用该构造函数,可以和普通构造函数一样
Myclass obj1;
obj1.x=1;
Myclass obj2(obj1)//可以这样
Myclass obj3=Myclass(obj1)//也可也这样
这样和构造函数去调用
或者这样:
Myclass obj1;
obj1.x=1;
Myclass obj2-obj1;
这样隐式调用也会去调用拷贝构造函数,去将obj1的值给obj2
如果我们没有去定义拷贝构造函数,则会自动生成一个拷贝构造函数去完成这个功能
当我们将对象作为参数传入时,此时函数的形参是类的对象,也会使用拷贝构造函数去将实参的对象的值拷贝到临
时创建的类
同样,对于返回值是对象的函数也是一样,因为我们在函数中返回对象时,该对象的生命周期是在函数执行过程
中,当返回后就会消亡,所以函数内部得的对象是无法传到外面的,此时会创建一个临时对象去存返回值,会用
到拷贝构造函数,这个临时对象参加调用处的表达式求值后结束生命
注意: 我们可能会觉得默认的拷贝构造函数就可以实现复制了,我们为什么要自己写呢?默认的拷贝构造函数
将所有对象都复制过去,不具有选择性,而且都是浅复制,而我们自己写的可以选择复制什么,可以实现深复制
5 前向引用说明
如果我们在定义a类时在内部包含了b类的成员,同时在定义B类使用了a类的成员,此时肯定是无法正常编译
的,这种情况称作循环依赖,想要解决这种情况就要使用前向引用说明,即在使用那个未声明的类时,在前面说明
一下,具体的用法如下
class B;//前向引用说明
class A{
public:
void find(B b){
}
};
class B {
public:
void find(A a){
}
};
前向引用说明不是万能的,在提供一个完整的类定义之前,不能定义该类的对象,也不能在内联函数中使用该类的
对象
像这样
class B;//前向引用说明
class A{
public:
B b;
};
这种是不行的,所以两个类以彼此互为数据成员是不合法的
class B;//前向引用说明
class A{
public:
void find(B b){
b.x=1;//像这样也是不行的,B类未定义,所以不能使用b的属性
B& x=b;//像这样定义引用是可以的
B* a=&b;//这样是可以的
}
};
6 结构体的快速定义
如果一个结构体中全部数据成员都是公共成员,没有用户定义的构造函数,没有基类和虚函数,则直接可以通过以
下方式进行定义
类型名 变量名 ={初值1,初值2,....}
7 命名空间
命名空间和作用域息息相关
using 命名空间名::标识符名;
using namespace 命名空间名;
前一种形式将指定的标识符暴露再当前的作用域中,后者将指定命名空间的所有标识符暴露到当前的作用域中
namespace 命名空间名{
定义的函数,变量;
}
上面是定义一个命名空间的方法,也可以单纯在命名空间中声明变量和函数,在命名空间外声明
在命名空间外使用命名空间声明的变量和函数的方式
命名空间名:变量名/函数名()
存在两类特殊的命名空间,全局命名空间和匿名命名空间
8 生存周期
如果对象的生存周期和程序相同,就称其有静态生存周期,在命名空间作用域声明的对象都是居于静态生存周期的
如果在其他作用域下想要申请具有静态生存周期的对象或者其他变量,就需要使用static,在局部作用域声明的静态
变量并不会随着函数的调用次数而重新声明或者重置。
注意,如果我们在函数中定义
static int i=1;
在声明静态变量i的同时也进行了初始化,该操作只会在第一次调用该函数的时候被执行,以后无论调用多少次该
函数,也都只会存在第一次定义的这个i,并不会重复声明并初始化赋值,值也会随着操作的改变而改变,而不会
重复变成5
除了静态生存周期,必然存在动态生存周期,局部生存期对象诞生于声明点,结束于声明所在版块结束
9 静态成员
静态属性的定义和普通的类方法和属性
#include<iostream>
#include<cstdio>
using namespace std;
class Vas {
public:
static int i;
};
int Vas::i = 0; // 正确初始化静态成员
int main() {
Vas a;
cout << Vas::i<<a.i; // 正确访问静态成员
return 0;
}
在类内部只能声明静态属性,定义静态成员必须在外部类外部进行,调用的时候对象可以通过.来调用,
对象名.静态属性 对于使用类名的调用,则需要使用::
对于静态方法,则既可以在类外部定义,也可以在类的内部定义
#include <iostream>
#include <cstdio>
using namespace std;
class Vas
{
public:
static int i;
static void print()
{
cout << i << endl;
}
};
int Vas::i = 0; // 正确初始化静态成员
int main()
{
Vas a;
Vas::print();
a.print(); // 正确访问静态成员
return 0;
}
这是使用在外部定义的静态方法
#include <iostream>
#include <cstdio>
using namespace std;
class Vas
{
public:
static int i;
static void print();
};
void Vas::print()
{
cout << i << endl;
}
int Vas::i = 0; // 正确初始化静态成员
int main()
{
Vas a;
Vas::print();
a.print(); // 正确访问静态成员
return 0;
}
虽然静态方法和静态成员都是既可以通过对象调用,也可以通过类名调用,但一般习惯于通过类名调用
在静态方法内部是可以直接访问静态成员的,但对于非静态成员,是不能直接访问的,除非是通过参数传入的,或
者在静态方法内部定义的,故在静态方法中是无法使用this的。
10 友元函数
如果我们有两个点类,目前想要计算这两点之间的距离,直接写一个普通函数肯定是不行的,可行思路是创建一个
line类,通过构造函数将两个点类传入,将其作为自身的成员变量,从而可以去访问其私有属性,在Line类中写
一个距离计算函数来计算这个距离。但是如果我们不想使用这个line类呢?此时就需要使用到友元函数了,友元函
数通过在类中用friend来声明一个函数,来说明该函数与该类是朋友关系,进而在该函数内部可以访问该类的私有
成员,这个友元函数可以是类函数,也可以是普通函数,在对应类中的声明位置可以是Public下,也可以是其他访
问控制修饰符下,声明方式是friend 函数声明
具体例子为:
#include <iostream>
#include <cstdio>
using namespace std;
class Vas
{
public:
Vas(int a, int b) : a(a), b(b) {}
friend void add(Vas v);
private:
int a, b;
};
void add(Vas v)
{
cout << v.a + v.b;
}
int main()
{
add(Vas(1, 2));
return 0;
}
在上面这个类中,我们通过声明了友元函数成功计算出了两个私有成员的和
需要注意,当我们在声明一个普通函数为友元函数时,并不需要在声明友元函数前对其进行声明,但如果
我们想让一个类的方法为友元函数,此时就需要在声明友元函数前进行声明,下面是一个例子
#include<iostream>
#include<cstdio>
using namespace std;
class Vas;
class Va{
public:
void deal(Vas a);
};
class Vas {
public:
friend void Va::deal(Vas a);
private:
int x=2;
};
inline void Va::deal(Vas a){
cout<<a.x;
}
int main() {
Va().deal(Vas());
return 0;
}
11 友元类
友元类和友元函数差不多,在友元类中的所有函数都是对应的友元函数,声明一个友元类时并不需要类提前声明
我们看一下实例
#include<iostream>
#include<cstdio>
using namespace std;
class Vas {
public:
friend class Va;
private:
int x=2;
};
class Va{
public:
void deal(Vas a);
};
inline void Va::deal(Vas a){
cout<<a.x;
}
int main() {
Va().deal(Vas());
return 0;
}
对于友元函数和友元类,有几点需要被注意,即
1 友元关系是不能传递的
2 友元关系是单向的
3 友元关系不能被继承
12 共享数据的保护
我们之前学习的友元等数据共享一定意义上破坏了数据的安全性,对于急需要共享又需要保持值不变的数据
我们通过常量来进行维护
首先我们来学习常对象
常对象的定义为:
const 类名 对象名(初值);
因为常对象定义后属性无法更改,所以必须给他赋初始值,由于为了保证常对象的属性不被更改,所以限定常对象
不能调用其非常成员函数
接着我们来学习常成员函数
其定义方法为:类型说明符 函数名(参数表) const;
在定义和声明时都需要加上const
注意,如果将一个对象说明为常对象,则该常对象只能调用它的常成员函数,这是常成员位移的对外接口
无论是否通过常对象调用常成员函数,在常成员函数调用期间,目的函数都被视为常对象,因此常成员函数不能更
新目的的对象的数据成员,也不能在常成员函数中调用非常成员函数。
我们接着来学习常数据成员
一般来说,常数据成员定义时就需要进行初始化, 如果没有在定义时初始化,就只能在构造函数中利用初始化列
表进行初始化,还需要注意,虽然静态成员不能在类中定义,但对于静态常数据成员则可以在类中进行定义
#include<iostream>
#include<cstdio>
using namespace std;
class Vas {
public:
const int a;
Vas(int a):a(a){
}
};
int main() {
cout<<Vas(1).a;
return 0;
}
常引用
定义方式为:const 类型说明符& 引用名
对于常引用来说,所引用的对象不能被更新,如果常引用被当作形参,则不会对实参的值进行更改,还需要注意的是,非const的引用只能绑定在
13 C++程序的一般组织结构
一个c++项目一般由三部分组成,类定义文件,类实现文件,和类使用文件,类定义文件也就是.h的文件只包含类
的声明,不可以去分配空间,具体的分配空间在类实现文件中进行,通过这种方法大大提高了修改文件的效率
14 外部变量
如果一个变量不仅需要在定义其的源文件中使用,还需要再其他文件中使用,那么就需要定义其为外部变量,再命
名空间作用域中声明的变量默认是外部变量,如果在另一个文件中需要使用,那需要再用extern加以声明
例如
//文件1
int i=3;
//文件2
extern int i;
外部变量是可以在多个源文件实现共享的全局变量
使用extern 声明变量如果不给初值都是引用形声明,从别处引用过来,如果有初值的话则是定义性声明,引用形
声明可以有多个,但定义形声明只能有一个
但需要注意的是,在这种情况下,由于使用了外部变量,所以在进行连接时需要加上包含外部变量的文件,要不然
连接器不清楚外部变量具体在哪里
15 外部函数
所有类之外声明的函数,都是具有命名空间作用域的,如果没有特殊说明,那么这样的函数都可以在不同的编译单
元中调用,只要在调用之前进行引用声明即可
比如:
//文件1
#include <cstdio>
#include <iostream>
using namespace std;
void print() { cout << "Hello, World!" << endl; }
//文件2
#include "common.h"
#include <cstdio>
#include <iostream>
using namespace std;
extern void print();
int main() {
print();
system("pause");
return 0;
}
这样就实现了外部函数的调用

浙公网安备 33010602011771号