C+++++++++++++++++语言基础篇(一)

1.C++中的四种智能指针

智能指针的作用:

智能指针的作用是管理一个指针,避免程序员申请的空间在函数结束时忘记释放,造成内存泄漏的情况发生。智能指针是一个类,当超出了类的作用域,类会自动调用析构函数 析构函数详解,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时,自动释放内存空间,不需要手动释放内存空间。
常用接口:

T* get();
T& operator*();
T* operator->();
T* operator = (const T& val);
T* release();
void reset(T* ptr = nullptr);
  • T 是模板参数, 也就是传⼊的类型;
  • get() ⽤来获取 auto_ptr 封装在内部的指针, 也就是获取原⽣指针;
  • operator() 重载 , operator->() 重载了->, operator=()重载了=;
  • realease() 将 auto_ptr 封装在内部的指针置为 nullptr, 但并不会破坏指针所指向的内容, 函数返回的是内部指针置空之前的值;
  • 直接释放封装的内部指针所指向的内存, 如果指定了 ptr 的值, 则将内部指针初始化为该值(否则将其设置为nullptr;

四种智能指针

1、auto_ptr (c++98的方案,c11已抛弃)采用所有权模式;

auto_ptr<std::string> p1 (new string ("hello"));
auto_ptr<std::string> p2;
p2=p1;//auto_ptr不会报错

此时不会报错,P2剥夺了P1的所有权,但当程序运行时,访问p1会报错,所以auto_ptr的缺点是:存在潜在的内存崩溃问题。
2、unique_ptr(替换auto_ptr)
unique_ptr 实现独占式拥有或严格拥有概念,保证同⼀时间内只有⼀个智能指针可以指向该对象。它对于避免资源泄露特别有⽤。采⽤所有权模式,还是上⾯那个例⼦

unique_ptr<string> p3(new string(auto));//#4
unique_ptr<string> p4;//#5
p4 = p3//此时会报错

编译器认为 p4=p3 ⾮法,避免了 p3 不再指向有效数据的问题。因此, unique_ptr ⽐ auto_ptr 更安全。
3、shared_ptr(共享型,强引用)

  • shared_ptr 实现共享式拥有概念,多个智能指针可以指向相同对象,该对象和其相关资源会在“最后⼀个引⽤被销毁”时候释放。从名字 share 就可以看出了资源可以被多个指针共享,它使⽤计数机制来表明资源被⼏个指针共享。
  • 可以通过成员函数 use_count() 来查看资源的所有者个数,除了可以通过 new 来构造,还可以通过传⼊auto_ptr, unique_ptr,weak_ptr 来构造。当我们调⽤ release() 时,当前指针会释放资源所有权,计数减⼀。当计数等于 0 时,资源会被释放。
  • shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性 (auto_ptr 是独占的),在使⽤引⽤计数的机制上提供了可以共享所有权的智能指针。

4、waek_ptr(弱引用)

  • weak_ptr 是⼀种不控制对象⽣命周期的智能指针,它指向⼀个 shared_ptr 管理的对象。进⾏该对象的内存管理的是那个强引⽤的 shared_ptr。
  • weak_ptr 只是提供了对管理对象的⼀个访问⼿段。 weak_ptr 设计的⽬的是为配合shared_ptr ⽽引⼊的⼀种智能指针来协助 shared_ptr ⼯作,它只可以从⼀个 shared_ptr 或另⼀个 weak_ptr 对象构造,,它的构造和析构不会引起引⽤记数的增加或减少。
  • weak_ptr 是⽤来解决 shared_ptr 相互引⽤时的死锁问题,如果说两个 shared_ptr 相互引⽤,那么这两个指针的引⽤计数永远不可能下降为0,也就是资源永远不会释放。它是对对象的⼀种弱引⽤,不会增加对象的引⽤计数,和 shared_ptr 之间可以相互转化, shared_ptr 可以直接赋值给它,它可以通过调⽤ lock 函数来获得shared_ptr。
  • 当两个智能指针都是 shared_ptr 类型的时候,析构时两个资源引⽤计数会减⼀,但是两者引⽤计数还是为 1,导致跳出函数时资源没有被释放(的析构函数没有被调⽤),解决办法:把其中⼀个改为weak_ptr就可以。

2.C++中内存分配情况

  • 栈:由编译器管理分配和回收,存放局部变量和函数参数。
  • 堆:由程序员管理,需要手动new malloc delete free 进行分配和回收,空间较大,但可能会出现内存泄漏和空闲碎片的情况。
  • 全局/静态存储区:分为初始化和未初始化两个相邻区域,存储初始化和未初始化的全局变量和静态变量。
  • 常量存储区:存储常量,⼀般不允许修改。
  • 代码区:存放程序的⼆进制代码。

3.C++中的指针参数传递和引用参数传递

  • 指针参数传递本质上是值传递,它所传递的是⼀个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从⽽形成了实参的⼀个副本(替身)。值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进⾏的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。
  • 引⽤参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。
  • 引⽤传递和指针传递是不同的,虽然他们都是在被调函数栈空间上的⼀个局部变量,但是任何对于引⽤参数的处理都会通过⼀个间接寻址的⽅式操作到主调函数中的相关变量。⽽对于指针传递的参数,如果改变被调函数中的指针地址,它将应⽤不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就得使⽤指向指针的指针或者指针引⽤。
  • 从编译的⻆度来讲,程序在编译时分别将指针和引⽤添加到符号表上,符号表中记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,⽽引⽤在符号表上对应的地址值为引⽤对象的地址值(与实参名字不同,地址相同)。符号表⽣成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),⽽引⽤对象则不能修改。
  • 引用和指针的区别:参考链接

4.C++ 中const和static关键字(定义,用途)

static 作⽤:控制变量的存储⽅式和可⻅性。

  • 作⽤⼀:修饰局部变量: ⼀般情况下,对于局部变量在程序中是存放在栈区的,并且局部的⽣命周期在包含语句块执⾏结束时便结束了。但是如果⽤ static 关键字修饰的话,该变量便会存放在静态数据区,其⽣命周期会⼀直延续到整个程序执⾏结束。但是要注意的是,虽然⽤static 对局部变量进⾏修饰之后,其⽣命周期以及存储空间发⽣了变化,但其作⽤域并没有改变,作⽤域还是限制在其语句块。
  • 作⽤⼆:修饰全部变量:对于⼀个全局变量,它既可以在本⽂件中被访问到,也可以在同⼀个⼯程中其它源⽂件被访问(添加 extern进⾏声明即可)。⽤ static 对全局变量进⾏修饰改变了其作⽤域范围,由原来的整个⼯程可⻅变成了本⽂件可⻅。
  • 作⽤三:修饰函数:⽤ static 修饰函数,情况和修饰全局变量类似,也是改变了函数的作⽤域。
  • 作⽤四:修饰类:如果 C++ 中对类中的某个函数⽤ static 修饰,则表示该函数属于⼀个类⽽不是属于此类的任何特定对象;如果对类中的某个变量进⾏ static 修饰,则表示该变量以及所有的对象所有,存储空间中只存在⼀个副本,可以通过;类和对象去调⽤。(补充:静态⾮常量数据成员,其只能在类外定义和初始化,在类内仅是声明⽽已。)
  • 作⽤五:类成员/类函数声明 static
    • 函数体内 static 变量的作⽤范围为该函数体,不同于 auto 变量,该变量的内存只被分配⼀次,因此其值在下次调⽤时仍维持上次的值;
    • 在模块内的 static 全局变量可以被模块内所⽤函数访问,但不能被模块外其它函数访问;
    • 在模块内的 static 函数只可被这⼀模块内的其它函数调⽤,这个函数的使⽤范围被限制在声明它的模块内;
    • 在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有⼀份拷⻉;
    • 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因⽽只能访问类的 static 成员变量。
    • static 类对象必须要在类外进⾏初始化, static 修饰的变量先于对象存在,所以 static 修饰的变量要在类外初始化;
    • 由于 static 修饰的类成员属于类,不属于对象,因此 static 类成员函数是没有 this 指针,this 指针是指向本对象的指针,正因为没有 this 指针,所以 static 类成员函数不能访问⾮static 的类成员,只能访问 static修饰的类成员;
    • static 成员函数不能被 virtual 修饰, static 成员不属于任何对象或实例,所以加上 virtual没有任何实际意义;静态成员函数没有 this 指针,虚函数的实现是为每⼀个对象分配⼀个vptr 指针,⽽ vptr 是通过 this 指针调⽤的,所以不能为 virtual;虚函数的调⽤关系,this->vptr->ctable->virtual function。

const 关键字:含义及实现机制

  • const 修饰基本类型数据类型:基本数据类型,修饰符 const 可以⽤在类型说明符前,也可以⽤在类型说明符后,其结果是⼀样的。在使⽤这些常量的时候,只要不改变这些常量的值即可。
  • const 修饰指针变量和引⽤变量: 如果 const 位于⼩星星的左侧,则 const 就是⽤来修饰指针所指向的变量,即指针指向为常量;如果 const 位于⼩星星的右侧,则 const 就是修饰指针本身,即指针本身是常量。
  • const 应⽤到函数中: 作为参数的 const 修饰符:调⽤函数的时候,⽤相应的变量初始化const 常量,则在函数体中,按照 const 所修饰的部分进⾏常量化,保护了原对象的属性。[注意]:参数 const 通常⽤于参数为指针或引⽤的情况; 作为函数返回值的 const 修饰符:声明了返回值后, const 按照"修饰原则"进⾏修饰,起到相应的保护作⽤。
  • const 在类中的⽤法: const 成员变量,只在某个对象⽣命周期内是常量,⽽对于整个类⽽⾔是可以改变的。因为类可以创建多个对象,不同的对象其 const 数据成员值可以不同。所以不能在类的声明中初始化 const 数据成员,因为类的对象在没有创建时候,编译器不知道 const数据成员的值是什么。 const 数据成员的初始化只能在类的构造函数的初始化列表中进⾏。
  • const 成员函数: const 成员函数的主要⽬的是防⽌成员函数修改对象的内容。要注意, const关键字和 static 关键字对于成员函数来说是不能同时使⽤的,因为 static 关键字修饰静态成员函数不含有 this 指针,即不能实例化, const 成员函数⼜必须具体到某⼀个函数。
  • const 修饰类对象,定义常量对象:常量对象只能调⽤常量函数,别的成员函数都不能调⽤。
  • 补充: const 成员函数中如果实在想修改某个变量,可以使⽤ mutable 进⾏修饰。成员变量中如果想建⽴在整个类中都恒定的常量,应该⽤类中的枚举常量来实现或者 static const。

5、C和C++区别(函数、类、struct/class)

首先,C和C++在基本语句上基本没有多大区别。
C++有新增的语法和关键字,语法的区别有头文件的不同和命名空间不同,C++ 允许我们⾃⼰定义⾃⼰的空间, C 中不可以。关键字⽅⾯⽐如 C++ 与 C 动态管理内存的⽅式不同,C++ 中在 malloc 和 free 的基础上增加了 new 和 delete,⽽且 C++ 中在指针的基础上增加了引⽤的概念,关键字例如 C++中还增加了 auto, explicit 体现显示和隐式转换上的概念要求,还有 dynamic_cast 增加类型安全⽅⾯的内容。
函数⽅⾯ C++ 中有重载和虚函数的概念: C++ ⽀持函数重载⽽ C 不⽀持,是因为 C++ 函数的名字修饰与 C 不同, C++ 函数名字的修饰会将参数加在后⾯,例如, int func(int,double)经过名字修饰之后会成_func_int_double,⽽ C 中则会变成 _func,所以 C++ 中会⽀持不同参数调⽤不同函数。
C++ 还有虚函数概念,⽤以实现多态。
类⽅⾯, C 的 struct 和 C++ 的类也有很⼤不同: C++ 中的 struct 不仅可以有成员变量还可以成员函数,⽽且对于 struct 增加了权限访问的概念, struct 的默认成员访问权限和默认继承权限都是 public, C++ 中除了 struct 还有 class 表示类, struct 和 class 还有⼀点不同在于 class 的默认成员访问权限和默认继承权限都是 private。
C++ 中增加了模板还重⽤代码,提供了更加强⼤的 STL 标准库。
最后补充⼀点就是 C 是⼀种结构化的语⾔,重点在于算法和数据结构。 C 程序的设计⾸先考虑的是如何通过⼀个代码,⼀个过程对输⼊进⾏运算处理输出。⽽ C++ ⾸先考虑的是如何构造⼀个对象模型,让这个模型能够契合与之对应的问题领域,这样就能通过获取对象的状态信息得到输出。

    C 的 struct 更适合看成是⼀个数据结构的实现体,⽽ C++ 的 class 更适合看成是⼀个对象的实现体。

7、说一下C++里是怎么定义常量的?常量存放在内存的那个位置?

对于局部常量,存放在栈区:
对于全局常量,编译期一般不分配内存,放在符号表中,以提高访问效率;
字面值常量,比如字符串,放在常量区。

8、C++中重载和重写,重定义的区别

重载
翻译⾃ overload,是指同⼀可访问区内被声明的⼏个具有不同参数列表的同名函数,依赖于C++函数名字的修饰会将参数加在后⾯,可以是参数类型,个数,顺序的不同。根据参数列表决定调⽤哪个函数,重载不关⼼函数的返回类型。
C++中的重载和java中的差不多,都是在一个类中,方法名相同而参数不同的几个方法,但是不能靠返回类型来判断。例如:

class AA{
    
    
public:
	void print(){
    
    
	cout << "记得点赞,欢迎交流" << endl;
	}
	void print(int x){
    
    
		cout << "共同学习" << endl;
	}
};

重写
翻译⾃ override,派⽣类中重新定义⽗类中除了函数体外完全相同的虚函数,注意被重写的函数不能是 static 的,⼀定要是虚函数,且其他⼀定要完全相同。要注意,重写和被重写的函数是在不同的类当中的,重写函数的访问修饰符是可以不同的,尽管 virtual 中是 private 的,派⽣类中重写可以改为 public。
函数特征相同。但是具体实现不同,主要是在继承关系中出现的 。当我们对别人提供好的类的方法感觉不是太满意时,我们就可以通过继承这个类然后重写其方法改成我们需要的逻辑。

1、最重要的一点,重写是子类与父类之间的。

2、被重写的函数不能是 static 的。

3、函数三要素(函数名、函数参数、函数返回类型)完全一样

4、如果父类中有virtual关键字,这种父子之间的关系叫做虚函数重写,这也就是C++中的多态机制,和java自动转换不同的是,C++需要我们手动设置。

class AA
{
    
    
public:
    virtual void print(int x )
    {
    
    
        cout << "父类:" << x  << endl;
    }
};

class BB : public AA
{
    
    
public:
    virtual void print(int x)
    {
    
    
        cout << "子类:" << x  << endl;
    }
};

int main()
{
    
    
    AA *p = NULL;
    BB b; 
    p = &b;
    p->print(1);
}
这个输出结果取决于AA类中的void print(int x )函数前面是否加入virtual关键字,如果加了则调用子类的,否则父类

重定义(隐藏)
派⽣类重新定义⽗类中相同名字的⾮ virtual 函数,参数列表
和返回类型都可以不同,即⽗类中除了定义成 virtual 且完全相同的同名函数才
不会被派⽣类中的同名函数所隐藏(重定义)。

class AA{
    
    
public:
	void print(){
    
    
		cout << "父类,记得点赞,欢迎交流" << endl;
	}
};
class BB : public AA
{
    
    
public:
    void print(int x)//这叫重定义,此时A类中的print()被隐藏
    {
    
    
        cout << "子类:" << x  << endl;
    }
};

void main()
{
    
    
    int x = 1;
    BB b; //子类
    AA a; //父类
    a.print();//访问父类的print()
    b.print(x);//访问子类的print()
    b.AA::print();//访问父类的print()
    //b.print();error:函数参数太少
}

这种情况下print()和print(int x)叫作重定义,在重定义时,父类的print()方法被隐藏了,要想使用父类的方法必须通过::

9 介绍C++中所有的构造函数

类的对象被创建时,编译系统为对象分配内存空间,并⾃动调⽤构造函数,由构造函数完成成员的初始化⼯作。
即构造函数的作⽤:初始化对象的数据成员。
⽆参数构造函数: 即默认构造函数,如果没有明确写出⽆参数构造函数,编译器会⾃动⽣成默认的⽆参数构造函数,函数为空,什么也不做,如果不想使⽤⾃动⽣成的⽆参构造函数,必需要⾃⼰显示写出⼀个⽆参构造函数。
⼀般构造函数: 也称重载构造函数,⼀般构造函数可以有各种参数形式,⼀个类可以有多个⼀般构造函数,前提是参数的个数或者类型不同,创建对象时根据传⼊参数不同调⽤不同的构造函数。
拷⻉构造函数: 拷⻉构造函数的函数参数为对象本身的引⽤,⽤于根据⼀个已存在的对象复制出⼀个新的该类的对象,⼀般在函数中会将已存在的对象的数据成员的值⼀⼀复制到新创建的对象中。如果没有显示的写拷⻉构造函数,则系统会默认创建⼀个拷⻉构造函数,但当类中有指针成员时,最好不要使⽤编译器提供的默认的拷⻉构造函数,最好⾃⼰定义并且在函数中执⾏深拷⻉。
类型转换构造函数: 根据⼀个指定类型的对象创建⼀个本类的对象,也可以算是⼀般构造函数的⼀种,这⾥提出来,是想说有的时候不允许默认转换的话,要记得将其声明为 explict 的,来阻⽌⼀些隐式转换的发⽣。
赋值运算符的重载: 注意,这个类似拷⻉构造函数,将=右边的本类对象的值复制给=左边的对象,它不属于构造函数,=左右两边的对象必需已经被创建。如果没有显示的写赋值运算符的重载,系统也会⽣成默认的赋值运算符,做⼀些基本的拷⻉⼯作。
区分

A a1, A a2; a1 = a2;//调用赋值运算符
A a3 = a1 ;//调用拷贝构造函数,因为进行的是初始化工作,a3并为存在

10、C++的四种强制转换

C++ 的四种强制转换包括: static_cast, dynamic_cast, const_cast, reinterpret_cast

  • static_cast: 明确指出类型转换,⼀般建议将隐式转换都替换成显示转换,因为没有动态类型检查,上⾏转换(派⽣类->基类)安全,下⾏转换(基类->派⽣类) 不安全,所以主要执⾏⾮多态的转换操作;
  • dynamic_cast:专⻔⽤于派⽣类之间的转换, type-id 必须是类指针,类引⽤或 void*,对于下⾏转换是安全的,当类型不⼀致时,转换过来的是空指针,⽽static_cast,当类型不⼀致时,转换过来的是错误意义的指针,可能造成⾮法访问等问题。
  • const_cast:专⻔⽤于 const 属性的转换,去除 const 性质,或增加 const 性质, 是四个转换符中唯⼀⼀个可以操作常量的转换符。
  • reinterpret_cast:不到万不得已,不要使⽤这个转换符,⾼危操作。使⽤特点: 从底层对数据进⾏重新解释,依赖具体的平台,可移植性差; 可以将整形转换为指针,也可以把指针转换为数组;可以在指针和引⽤之间进⾏肆⽆忌惮的转换。

11、指针和引用的区别

指针和引⽤都是⼀种内存地址的概念,区别呢,指针是⼀个实体,引⽤只是⼀个别名。在程序编译的时候,将指针和引⽤添加到符号表中。指针它指向⼀块内存,指针的内容是所指向的内存的地址,在编译的时候,则是将“指针变量名-指针变量的地址”添加到符号表中,所以说,指针包含的内容是可以改变的,允许拷⻉和赋值,有 const 和⾮ const 区别,甚⾄可以为空, sizeof 指针得到的是指针类型的⼤⼩。

⽽对于引⽤来说,它只是⼀块内存的别名,在添加到符号表的时候,是将"引⽤变量名-引⽤对象的地址"添加到符号表中,符号表⼀经完成不能改变,所以引⽤必须⽽且只能在定义时被绑定到⼀块内存上,后续不能更改,也不能为空,也没有 const 和⾮ const 区别。

sizeof 引⽤得到代表对象的⼤⼩。⽽ sizeof 指针得到的是指针本身的⼤⼩。另外在参数传递中,指针需要被解引⽤后才可以对对象进⾏操作,⽽直接对引⽤进⾏的修改会直接作⽤到引⽤对象上。

作为参数时也不同,传指针的实质是传值,传递的值是指针的地址;传引⽤的实质是传地址,传递的是变量的地址

猜你喜欢

转载自blog.csdn.net/qq_43679351/article/details/124871124
今日推荐