1. C和C++中结构体的区别
C语言结构体中只能定义变量 在C++中,结构体内不仅可以定义变量,也可以定义函数。
类体中内容称为类的成员:
类中的变量称为类的属性或成员变量。
类中的函数称为类的方法或者成员函数。
2. 类的两种定义方式
1. 声明和定义全部放在类体中 需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2.类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::(做项目更通用)
3. 访问限定符
public,protected,private
1.public修饰的成员在类外可以直接被访问
2.protected和 private 修饰的成员在类外不能直接被访问(此处 protected 和 private 是类似的)
3.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4.如果后面没有访问限定符,作用域就到}即类结束。
5. class 的默认访问权限为 private , struct 为 public (因为 struct 要兼容 C )
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
C++中struct和class的区别
C ++需要兼容 C 语言,所以 C ++中 struct 可以当成结构体使用。另外 C ++中 struct 还可以用来定义类。和 class 定义类是一样的,区别是 struct 定义的类默认访问权限是 public , class 定义的类默认访问权限是 private 。注意:在继承和模板参数列表位置, struct 和 class 也有区别。
类的作用域和大小
类的作用域: 类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::作用域操作符指明成员属于哪个类域。
类的大小:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐 ,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象(占位,表示内存存在)。
C++类中隐含的this指针
当通过某个类类型的变量(string it,通过it)去访问该类的成员函数,会传一个this指针(就是&it),通过this指针就可以访问it的成员变量。
class Date
{
public:
void Init(int year, int month, int day) // void Init(Date*thia,int year, int month, int day)
{
_year = year; // 直接访问了成员变量
_month = month;
_day = day;
}
void print() //void print(Date*this)
{
cout << _year << _month << _day << endl; // cout <<this-> _year <<this-> _month << this->_day << endl;
}
private:
int _year;
int _month;
int _day;
};
void test8()
{
Date d1;
d1.Init(2025, 5, 16);
// d1.Init( &d1 , 2025 , 5 , 16 ) Date*this=&d1 ,编辑器会传一个this指针 没有显示
d1.print();
}
this指针是存在栈上面的,他是一个形参。
4. 构造函数
构造函数是一个特殊的成员函数,名字与类名相同 创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次。
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
如果类中没有显式定义(自己没有定义初始化数据)构造函数,则C++编译器会自动生成一个无参的默认构造函数 一旦用户显式定义编译器将不再生成。
我们没有显式定义构造函数,编译器生成无参默认构造函数
默认生成无参构造函数
1、针对内置类型的成员变量没有做处理。
2、针对自定义类型的成员变量,调用它的构造函数初始化。(内嵌其他的类作为其成员变量)
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。
调用默认构造函数
1.自己实现的无参构造函数 。
2.自己实现的全缺省构造函数。
3.不写,编译器自己生成。
特点: 不用传参数
5. 析构函数
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。 而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。(比如在堆上开辟了空间需要析构函数释放调)
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。 若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
析构函数的调用顺序
class A3
{
public:
~A3()
{
cout << "~A3" << endl;
}
};
class B2
{
public:
~B2()
{
cout << "~B2" << endl;
}
};
class D3
{
public:
~D3()
{
cout << "~D3" << endl;
}
};
class C2
{
public:
~C2()
{
cout << "~C2" << endl;
}
};
static B2 d4; // 5
A3 d1; // 4
void test8()
{
D3 d3; // 2
C2 c2; // 1
static B2 d2; // 3
// ~C ~D ~B ~A ~B ----> 后创建先析构,局部变量先析构,全局变量和静态变量看创建顺序
}
6. 拷贝构造函数
Date1(const Date1& d1)
{
_year = d1._year;
_month = d1._month;
_day = d1._day;
}
传参必须传引用
拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
若未显式定义,编译器会生成默认的拷贝构造函9. 数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
6. 浅拷贝的危害
当我们定义的类中的成员变量有指针数组时如下图
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* parr[] = { arr1,arr2,arr3 }; //相当于一个二维数组
将parr拷贝的时候,只是将arr1,arr2,arr3的地址拷贝了过去,并没有将值进行拷贝,新的临时变量tem[1],tmp[2],tmp[3],仍然指向原来空间,这种情况下
1. 当将parr释放之后,tmp[i]变为野指针
2. 如果是同一个类类型,一个空间可能调用两次析构函数,同一个空间不允许释放两次。
7. 运算符重载
运算符重载 C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。 函数名字为:关键字operator后面接需要重载的运算符符号。 函数原型:返回值类型 operator操作符(参数列表)
1. 不能通过连接其他符号来创建新的操作符:比如operator@。
2. 重载操作符必须有一个类类型参数。
3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义。
4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this。
5. .* :: sizeof ?: . 注意以上5个运算符不能重载。
赋值运算符只能重载成类的成员函数不能重载成全局函数。
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
8. 默认函数
9.初始化列表
三大类型变量
1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2.类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const 成员变量
自定义类型成员(且该类没有默认构造函数时)
class Date
{
public:
// 可以理解为初始化列表是对象的成员变量的定义的地方
Date(int year =1, int month = 1, int day = 1,int a=1,int b=2,int ref=1)
:_year(year)
,_month(month)
,_day(day)
,_a(a)
,_b(b)
,_ref(ref)
{}
Date(const Date& d)
:_a(d._a)
,_b(d._b)
,_ref(d._ref)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 成员变量的声明
int _year=1; // 声明时给缺省值 C++11 支持
int _month=1;
int _day=1;
A _a; // 没有默认构造函数 (需要自己传参)
const int _b; // const 修饰
int& _ref; //引用
};
成员变量在类中声明的次序就是初始化列表初始化的次序,与其在初始化列表的先后无关。
explicit和static
class Date1
{
public:
//加 explicit 可以阻止 隐式类型的转换 报错
/*explicit*/ Date1(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
//_num++;
}
Date1(const Date1& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
//_num++;
}
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//int Getnum()
//{
// return _num;
//}
private:
// 成员变量的声明
//static int _num; // 全局变量 放在静态区 但被访问限制
int _year;
int _month;
int _day;
};
void test2()
{
Date1 d1;
d1.print();
Date1 d2 =3; // 隐式类型的转换 等价于 构造了Date tmp(3) (只传了一个参数)----->经过缺省函数的补充tem.print()--> 3-1-1 再将tmp 拷贝给d2
d2.print();
//C++11 支持
Date1 d3 = { 1,2,3 };
Date d4 = (1, 2, 3); // 等价
d3.print();
}
static 成员
声明为 static 的类成员称为类的静态成员,用 static 修饰的成员变量,称之为静态成员变量;用
static 修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。(在类里面初始化,如果有多个对象,会将静态成员变量重复初始化)
传参和返回时对函数的调用
class A1
{
public:
A1()
{
++n;
}
A1(const A1& a)
{
++n;
}
static int GetN()
{
return n;
}
private:
static int n;
};
int A1::n = 0; // 定义
//A& f1(A& a)
A1 f1(A1 a) // 调用拷贝函数
{
return a;
}
A1 f2(A1 a)
{
return a;
}
A1 f3(A1& a)
{
return a;
}
A1 f4(A1& a)
{
return a;
}
A1& f5(A1& a)
{
return a;
}
A1& f6(A1& a)
{
return a;
}
void test3()
{
cout << A1::GetN ()<< endl;// 0
A1 a1; // 调用默认构造函数
A1 a2;
cout << A1::GetN() << endl;// 2
f1(a1); // 进去函数拷贝一次,返回传值拷贝一次 +2
f2(a2); // +2
cout << A1::GetN() << endl;// 6-2=4 -->+4
f3(a1); // 引用返回 返回没有拷贝 +1
f4(a2); // +1
cout << A1::GetN() << endl; // 8-6=2
f5(a1); // +0
f6(a2); // +0
cout << A1::GetN() << endl; // 8-8=0 // 出入函数没有对n 进行增加 //
}
实现一个类,计算程序中创建出了多少个类对象
class A2
{
public:
A2()
{
++_scount;
}
A2(const A2 & t)
{
++_scount;
}
~A2()
{
--_scount;
}
static int GetACount()
{
return _scount;
}
private:
static int _scount;
};
int A2::_scount = 0;
void TestA()
{
cout << A2::GetACount() << endl;
A2 a1, a2;
A2 a3(a1);
cout << A2::GetACount() << endl;
}
10.静态成员
1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。
2.静态成员变量必须在类外定义,定义时不添加 static 关键字,类中只是声明。
3.类静态成员即可用类名::静态成员或者对象.静态成员来访问。
4.静态成员函数没有隐藏的 this 指针,不能访问任何非静态成员。
5.静态成员也是类的成员,受 public 、 protected 、 private 访问限定符的限制
静态成员函数不可以调用非静态成员函数,非静态成员函数可以调用静态成员函数。
原因:因为静态成员函数没有this指针,无法访问类里面的任何成员和对象,非静态成员函数有this指针,可以调用类里面的所以成员函数(是public)。
class C
{
public:
void f3()
{
f4();
}
static void f4()
{
//f3(); // 报错 --> 没有this 指针 无法找到 f3 函数
}
private:
};
void test4()
{
// class C
}
11. 友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加 friend 关键字。
1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
2. 友元函数不能用 const 修饰,友元函数没有this指针,修饰没意义。
3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
4. 一个函数可以是多个类的友元函数
5. 友元函数的调用与普通函数的调用原理相同
class Date2
{
friend ostream& operator<<(ostream& out, const Date2& d); // 这样就可以在该函数中访问隐私变量
friend istream& operator>>(istream& in, Date2& d);
public:
private:
};
12. 拷贝构造函数拷贝时的优化
class GuYu
{
public:
GuYu()
{}
GuYu(const GuYu& g) // 要加const
{num++;}
static int num;
};
GuYu fun(GuYu g)
{
GuYu v(g);
GuYu w = v;
return w; //赋值和返回合二为一
}
int GuYu::num = 0;
void test9()
{
GuYu x;
GuYu y = fun(fun(x)); //x.fun(x)返回的是临时对象,不能用非const引用
//返回和传参合二为一//返回和给y赋值合二为一
cout << GuYu::num << endl;
}
num因该为9次拷贝,但输出的是5次,存在优化,将某些步骤合二为一。
1. 返回值优化(RVO):消除返回时的临时对象构造。
2. 命名返回值优化(NRVO):直接在返回位置构造对象。
3. 传递优化:某些情况下消除参数传递时的复制。(返回值又作为参数传参的时候)
在第二次传参的时候(第一次返回的参数),两次返回的时候和给y赋值的时候,都消除了拷贝复制。