c++类的访问权限与继承方式
- 公有成员在任何地方都可以被访问,包括类的外部和派生类。
- 受保护成员在类的内部和派生类中可以被访问,但在类的外部不可访问。
-
私有成员只能在类的内部访问,包括类的成员函数和友元函数,不允许在类的外部或派生类中访问。
#include <iostream>
#include <string>
using namespace std;
class Parent
{
// 公有成员在任何地方都可以被访问,包括类的外部和派生类。
public:
string name;
void info_init(string name,string car,int password){
this->name=name;
this->car=car;
this->password=password;
}
void print_info(){
cout<<"name:"<<name<<endl;
cout<<"car:"<<car<<endl;
cout<<"password:"<<password<<endl;
}
// 受保护成员在类的内部和派生类中可以被访问,但在类的外部不可访问。
protected:
string car;
// 私有成员只能在类的内部访问,包括类的成员函数和友元函数,不允许在类的外部或派生类中访问。
private:
int password;
};
int main()
{
Parent p1;
p1.info_init("jay","NIO",10086);
p1.print_info();
return 0;
}
多态的构成条件
1.必须通过基类的指针或者引用调用虚函数
2.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。重写的三同(函数名,参数,返回值)
3.派生类可不加virtual
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
virtual void buy_ticket()
{
cout << "成人--全价票" << endl;
}
};
class Child : public Parent
{
public:
void buy_ticket()
{
cout << "儿童--半价票" << endl;
}
};
void func(Parent &p)
{
p.buy_ticket();
}
int main()
{
Parent p;
Child c;
func(p);
func(c);
return 0;
}
virtual修饰析构函数
虚析构函数可以确保首先调用子类的析构函数, 然后调用父类的析构函数 ;这样可以避免在释放子类对象时出现资源泄漏的情况 ;
当使用父类指针指向一个子类对象时,如果要通过 delete 释放该指针指向的对象,如果是正常的析构函数, 没有使用 virtual 定义虚析构函数 , 则只会调用父类的析构函数, 子类的析构函数不会被调用到 ;
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
Parent()
{
cout << "Parent()" << endl;
}
virtual ~Parent()
{
cout << "~Parent()" << endl;
}
};
class Child : public Parent
{
public:
Child()
{
cout << "Child()" << endl;
}
~Child()
{
cout << "~Child()" << endl;
}
};
void func(Parent *p)
{
delete p;
}
int main()
{
// Parent* p1 = new Parent();
Child *p2 = new Child();
func(p2);
return 0;
}
c++11的override 和 final
- fianl:修饰虚函数,表示该虚函数不能再被重写
- 放在类的后面表示该类无法被继承,也就是阻止了从类的继承,放在虚函数后面该虚函数无法被重写,表示阻止虚函数的重载
- override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
初始化列表
初始化列表是一种在构造函数中初始化类成员变量的语法。初始化列表在构造函数体执行之前完成成员的初始化。这种方式有时候比在构造函数中使用赋值更高效,并且适用于那些成员变量是常量或者是引用的情况。
初始化列表用法
const成员变量、引用成员变量、没有默认构造函数的自定义类型成员只能在初始化列表初始化
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
string name;
// 使用初始化列表可以确保所有成员变量在构造函数体执行前都已初始化
//初始化列表能只能初始化一次,多次初始化会报错:
Parent(string name =NULL, int wealth = -1, int password = -1) : name(name), wealth(wealth), password(password)
{
cout << "Name: " << name << '\t' << "wealth: " << wealth << '\t' << "password: " << password << endl;
}
protected:
int wealth;
private:
int password;
};
int main()
{
Parent p("jay", 10000, 10086);
Parent p1("jj");
return 0;
}
友元三种实现方式
友元(friend)是一种机制,允许一个类或者函数访问另一个类的私有成员。通过将其他类或函数声明为友元,可以使它们在访问另一个类的私有成员时绕过访问限制。
- 全局函数做友元
- 类做友元
- 成员函数做友元
#include <iostream>
#include <string>
using namespace std;
class Parent
{
friend void globalFunc(Parent *p);//告诉编译器此函数为友元,可以访问类内私有成员
public:
string name="jay";
protected:
int wealth = 8848;
private:
int password = 10086;
};
//全局函数做友元
void globalFunc(Parent *p)
{
cout << p->name << endl;
cout << p->wealth << endl;
cout << p->password << endl;
}
int main()
{
Parent p;
globalFunc(&p);
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class Parent
{
friend class GrandParent;//告诉编译器此函数为友元,可以访问类内私有成员
public:
string name="jay";
protected:
int wealth = 8848;
private:
int password = 10086;
};
class GrandParent{
public:
void getInfo(Parent *p);
};
//全局函数做友元
void GrandParent::getInfo(Parent *p)
{
cout << p->name << endl;
cout << p->wealth << endl;
cout << p->password << endl;
}
int main()
{
Parent p;
GrandParent g;
g.getInfo(&p);
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class GrandParent
{
public:
void getInfo();
void getInfo1();
};
class Parent
{
friend void GrandParent::getInfo(); // 告诉编译器此函数为友元,可以访问类内私有成员
public:
string name = "jay";
protected:
int wealth = 8848;
private:
int password = 10086;
};
void GrandParent::getInfo()
{
Parent*p=new Parent;
cout << "getInfo(): " << p->name << " " << p->wealth << " " << p->password << endl;
}
void GrandParent::getInfo1()
{
Parent*p=new Parent;
// 无法访问
//cout << "getInfo1(): " << p->name << " " << p->wealth << " " << p->password << endl;
}
int main()
{
GrandParent g;
g.getInfo();
return 0;
}
C++几种构造函数
- 默认构造
- 有参构造
- 拷贝构造
- 移动构造函数:接受同一类的对象的右值引用作为参数,用于将资源从一个对象转移到另一个对象,通常用于提高效率。
#include <iostream>
#include <string>
using namespace std;
class MyClass
{
public:
string data;
// 默认构造函数:无参数的构造函数,如果类没有定义任何构造函数,则编译器会自动生成默认构造函数。
// 默认构造函数被用于创建对象时,如果没有提供参数,会被调用。
MyClass()
{
this->data = "默认构造函数";
cout << "默认构造函数" << endl;
}
// 参数化构造函数:带有参数的构造函数,用于接受特定参数来初始化对象的成员变量。
MyClass(string str) : data(str)
{
cout << "有参数构造函数" << endl;
}
// 拷贝构造函数:接受同一类的对象作为参数,用于以同一类对象的内容创建新对象。通常用于对象的深拷贝。
MyClass(const MyClass &obj) : data(obj.data)
{
cout << "拷贝构造函数" << endl;
}
~MyClass(){
cout<<"-----------------------"<<endl;
}
};
int main()
{
MyClass obj;
MyClass obj1("hello");
MyClass obj2(obj1);
return 0;
}
指针和引用
- 指针是一个变量,其值为另一个变量的地址;引用是一个别名,它相当于已经存在变量的另一个名字。
- 使用指针可以直接访问或修改内存中的数据。
- 指针可以被重新赋值指向不同的地址,甚至可以指向空值(
nullptr
);引用必须在定义时初始化,并且一旦初始化后不能再引用其他变量 - 指针需要解引用操作符
*
;引用可以直接访问原始变量的值,而不需要解引用操作符 - 指针本身占用内存空间;而引用不占用额外的内存空
#include <iostream>
#include <string>
using namespace std;
int main()
{
int x = 10;
int *p = &x; // 指针是一个变量,其值为另一个变量的地址
int &ref = x; // 引用是一个别名,它相当于已经存在变量的另一个名字;引用必须在定义时初始化,并且一旦初始化后不能再引用其他变量
cout << "x=" << x << " *p=" << *p << " ref=" << ref << " p=" << p << endl;
x = 20;
cout << "x=" << x << " *p=" << *p << " ref=" << ref << " p=" << p << endl;
*p = 30;
cout << "x=" << x << " *p=" << *p << " ref=" << ref << " p=" << p << endl;
ref = 40;
cout << "x=" << x << " *p=" << *p << " ref=" << ref << " p=" << p << endl;
return 0;
}
new/delete与malloc/free的异同
相同点
- 都可用于内存的动态申请和释放
不同点
- 前者是C++运算符,后者是C/C++语言标准库函数
- new自动计算要分配的空间大小,malloc需要手工计算
- new是类型安全的,返回完整的类型;malloc不是。
- new调用名为operator new的标准库函数分配足够空间并调用相关对象的构造函数,delete对指针所指对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。后者均没有相关调用
- 后者需要库文件支持,前者不用;new是封装了malloc,直接free不会报错,但是这只是释放内存,而不会析构对象
解释每个示例的含义
//声明了一个数组 p,数组中包含 10 个指向 int 类型的指针
int *p[10];
//一个指针 p,它指向一个包含 10 个 int 类型元素的数组。
int (*p)[10];
//声明了一个函数 p,该函数接受一个 int 类型的参数,并返回一个 int 类型的指针。
int *p(int);
//声明了一个指针 p,它指向一个函数,该函数接受一个 int 类型的参数,并返回一个 int 类型的值
int (*p)(int);
static在类中的作用
静态成员变量
- static + 类型+变量名后,那么该类 不论 创建了多少个 实例对象 , 这些对象都会共享 该 static " 静态成员变量 " ;
- 成员变量一旦加了static关键字后就会处在静态区的,类是处在栈区的, 所以静态的成员变量只能在类外去定义,类内只能是声明,而且不可赋值!!!
- 静态成员变量 的 生命周期 就是 程序的声明周期 ;静态成员变量在 应用启动时 初始化 , 在 进程结束时销毁 ;
静态成员变函数
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person()
{
this->var++;
this->s_var++;
cout << "Person(): var=" << this->var <<" s_var="<<this->s_var<<" constVar=" <<this->constVar<<endl;
}
~Person()
{
this->var--;
this->s_var--;
cout << "~Person(): var=" << this->var <<" s_var="<<this->s_var<<" constVar=" <<this->constVar<<endl;
}
static int s_var; // 必须在类内定义,类外初始化,static int s_var=0会报错
int var=0;//非静态变量可以类内声明并定义.但是不可外部定义
const int constVar=0;
static void staticFinc()
{
cout << s_var << endl;//静态成员函数没有隐藏的this指针,不能访问任何非静态成员
//cout << var << endl;
}
};
int Person::s_var = 0; // 在类外部初始化静态成员变量
//int Person::var = 0; // 不可在类外部初始化非静态成员变量
//int Person::constVar=100;
int main()
{
Person p;
Person p1;
return 0;
}
C++ class与struct区别
相同点
- 两者都拥有成员函数、公有和私有部分
- 任何可以使用class完成的工作,同样可以使用struct完成
不同点
- 两者中如果不对成员不指定公私有,struct默认是公有的,class则默认是私有的
- class默认是private继承,而struct默认是public继承
#include <iostream>
#include <string>
using namespace std;
struct Person
{
Person()
{
this->name="NULL";
this->age=-1;
this->password=-1;
cout << "Person()" << endl;
}
~Person(){
cout << "~Person()" << endl;
}
void print_info(){
cout<<"name:"<<name<<" age:"<<age<<" password:"<<password<<endl;
}
string name;
protected:
int age;
private:
int password;
};
struct Student:Person {
Student()
{
cout << "Student()" << endl;
}
~Student(){
cout << "~Student()" << endl;
}
};
int main()
{
Person p;
Student s;
p.name="person";//struct默认是public继承,因此可以访问公有成员
s.name="student";
p.print_info();
s.print_info();
return 0;
}
C++的顶层const和底层const
概念区分
- 顶层const:指的是const修饰的变量本身是一个常量,无法修改,指的是指针,就是*号的右边
- 底层const:指的是const修饰的变量所指向的对象是一个常量,指的是所指变量,就是*号的左边
int a = 10;int* const b1 = &a; //顶层const,b1本身是一个常量
const int* b2 = &a; //底层const,b2本身可变,所指的对象是常量
const int b3 = 20; //顶层const,b3是常量不可变
const int* const b4 = &a; //前一个const为底层,后一个为顶层,b4不可变
const int& b5 = a; //用于声明引用变量,都是底层const
拷贝初始化和直接初始化
在 C++ 中,初始化对象的方式可以分为两种:拷贝初始化(copy initialization)和直接初始化(direct initialization)。
1. 拷贝初始化(Copy Initialization):
- 使用等号=进行初始化的方式称为拷贝初始化。
- 在拷贝初始化中,编译器会尝试使用构造函数将右侧的值转换为左侧的类型,并将结果拷贝到左侧的对象中。这种初始化方式适用于将一个对象的值拷贝给另一个对象,或者通过一个值来初始化一个新的对象。
2. 直接初始化(Direct Initialization):
- 在初始化过程中直接使用圆括号()进行初始化的方式称为直接初始化。
- 在直接初始化中,编译器会选择合适的构造函数来初始化对象,而不需要进行额外的拷贝操作。
静态类型,动态类型;静态绑定,动态绑定
对C++中的静态类型,动态类型,静态绑定,动态绑定的理解_静态类型和动态类型,静态绑定和动态绑定-CSDN博客
- 静态类型:变量声明时候的类型,在编译阶段就可以确定了;
- 动态类型:指针或者引用所代表的内存中的对象的类型,在运行阶段才可以确定;
- 静态绑定:有人也叫为早绑定,早绑定什么呢?对象所调用的函数的地址,这个阶段是程序编译阶段就确定的了;
- 动态绑定:有人也叫晚绑定,也就是编译阶段时候不能确定对象调用函数的地址,需要程序运行到调用函数的阶段才可以确定;