C/C++一些概念

继承

继承,将客观世界中的一般与特殊的关系模型化的过程。即基类抽象出共同特性子类表达其差别

概述:子类继承共性增加自身特殊性

举例:昆虫-->(有翅类,无翅类)-->(有翅类(飞蛾,苍蝇,...),无翅类(...))

多态性

所谓多态,是指一个名字,有多种语义;或一个相同界面,有多种实现

举例:刹车-->(鼓式刹车,盘式刹车,...)

C++中的体现:函数重载、虚函数与纯虚函数(这两者的不同实现称为重写或覆盖

PS:对于函数重载,若函数调用(界面)与哪一个函数体(函数实现)相匹配,是在编译时确定,则称为早期匹配。如果是在运行时动态进行,则称为晚期匹配。一般来说,早期匹配执行速度快,晚期匹配提供灵活性和高度的问题抽象。

一般情况:

晚期匹配:虚函数与纯虚函数(都具有相同的函数界面,即相同的函数原型

早期匹配:普通函数重载(通过参数列表的差异性确定函数体

注意:

在类中:重写子类和父类之间的关系,是垂直关系重载是同一个类中方法之间的关系,是水平关系

定义上:重载:是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数;覆盖(也叫重写):是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样。

模板(类属机制)

模板的核心是将类型参数化。

实际中有这样一些程序,从它们的逻辑功能(或算法)看,彼此是相同(如求两个数的大小)的,所不同的主要是处理对象(数据)的类型(如整型、浮点型)。如果提供具有相同逻辑功能的程序正文(保存相同性),然后将数据类型作为参数传递(指出不同性),这就是类属机制的思想,又称为参数化模板

类中的关键字this

this是一个隐含的指针,不能被显式声明,它只是一个形参,一个局部变量,它在任何一个非静态成员函数里都存在

作用:调用类成员时,告诉系统发起此调用的对象是谁。如:

string  x;  //声明一个string类的对象x
string  y;  //声明一个string类的对象y
x.get_length();   //get_length()函数中的this告诉系统调用此函数的对象是x,不是y或者其它对象
y.get_length();   //get_length()函数中的this告诉系统调用此函数的对象是y,不是x或者其它对象

指针

指针是指其值为内存地址变量,用这个指针所存放的地址值来指向某个数据变量的存储单元;

ps: 数组名、函数名是一个指针常量(常指针),故不能采用a++格式(int  a[]={1,2,3,4}),只能采用a+i的形式代表第i个元素地址

函数原型

形式: 返回类型  函数名(参数表),如 

int fun(int a, int b)或者int fun(int,int)

(1)确定函数返回类型

(2)确定函数参数个数以及类型、参数按顺序排列(函数重载的根据)

ps:只要编译能区分参数变元的类型,就可以重载一个函数名。

注意:多个同名函数的原型不能只有返回类型不同,而函数名和参数表完全相同

引用

引用是给对象取一个别名,主要有三个用途

(1)独立引用

格式:Type&  another_name = Type_var ; 即对类型Type进行引用 ,引用别名是another_name,被引用的对象为Type_var,如

引用必须初始化,并且一旦初始化不可更改引用对象

//变量引用
int i=0;
//j是对i的引用,即j是i的别名。注意,引用必须初始化,并且一旦初始化不可更改引用对象
int & j=i; 
//对j进行操作,就是对i进行操作,故i=6
j=6;     

//对指针的引用
int r=0;
int & r1=r;
int* p=&r1;  //对r1取地址,取的就是r的地址,故p指向r
int & r2=r1; //r2是r1的引用,r1是r的引用,故r2也是r的引用
int *q;
int & qr=q;  //qr是指针q的引用,即别名
int b;
qr=&b; //qr指向b,即q指向b

PS:当被引用是常量,如1时,C++新版本用 const int & r=1(早期版本为int & r=1),这样使得语义更明确

结论:独立引用就是一个存储单元的别名

(2)作为参数传递

C与C++都采用传值call by value方式进行参数传递。当一个函数需要对传入的参数进行修改时,C语言通过将参数明确声明为指针实现该目的,如

//交换两个变量的值,传入连个变量的地址,用指针实现
void swap(int * a,int *b){
    int t = *a;
    *a = *b;
    *b = t;
}

C++中允许C的上一个语法,但通过使用引用参数来实现一种更加清晰的方法,如

//交换两个变量的值,通过传入变量的引用实现
void swap(int & a,int & b){
    int t = a;
    a = b;
    b = t;
}

(3)作为返回类型

若一个函数要求返回引用,则return后面应该是一个引用。除了独立引用外,还可以是:

数组元素、static变量、指针所指向的对象(即*p)、结构的分量、联合的分量

结论:函数返回引用时,返回的必须是全局静态数据区的地址,即该引用的对象必须是全局变量或静态变量

PS:函数返回引用,实际上返回的是某个存储单元,这意味着函数调用甚至可以出现在赋值号的左边,即对返回的引用赋值。

int & get_int(int* p){
    return *p;
}

int main(){
    int i=1;
    int j;
    j = get_int(&i) + 5;   //j=i+5=6
    get_int(&i) = 10;      //函数返回i的引用,故i=10
    cout<< i << " "<<j<<endl;  //输出将为“10 6”
    return 0;
}

指针与引用的区别

(1)指针是一个对象的地址,是对所指对象的间接引用;引用是一个对象的别名,是对对象的直接引用。修改引用就是对引用的对象的修改。

(2)引用是一个对象的别名,因此只能始终指向初始化时被指定的对象,相当于指针常量;而指针可以被重新赋值,修改指针来指向另一个对象。

(3)指针可以不初始化,引用必须初始化

内部函数与外部函数

函数的内外之分是针对函数所在的源文件而言,若函数只能在源文件中被调用,则该函数为内部函数,若能被外部其它源文件调用,则为外部函数

(1)函数本质上都是全局的,定义一个函数的目的是为了被另外的函数调用。故若不加特别声明,函数都属于外部函数。

(2)有些函数不想让除源文件内函数外的其它函数调用,故需要指定某些函数为内部函数,指定的关键字为static,故内部函数也称为静态函数;这样使得在不同的文件中可定义同名的内部函数,在多人分工开发时,往往需要这类机制来避免函数名重复。

格式

内部函数格式: static  type fun_name(..),如  static int  fun(int a, int b)

外部函数格式:extern type fun_name(..),如  extern int  fun(int a, int b) ,其中extern关键字可以省略,故默认情况下不含static关键字的函数都是外部函数

注意:在外部文件调用本文件的外部函数时,需要对此函数做声明,且要包含extern关键字(也可省略),如下

//  test1.cpp
int fun(inta,int b){
    ....
}

//test2.cpp

extern int fun(int a,int b);  //调用其它文件的外部函数时要提前声明,并且需要加extern关键字

int main(){
    int i=0,j=6;
    fun(i,j);
    return 0;
}

PS:调用其它文件中的函数的另一个便捷方法是使用头文件的包含

注:函数的准确调用,无二义性的保证是依靠函数原型的声明

函数指针

对于函数来说,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针

可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,则可通过该指针调用该函数。例如:

(1)函数指针定义格式:函数返回类型  (*指针名)(函数参数列表),如下

                                                  int  (*p)   (int,int);   

(2)函数指针调用函数格式: (*p)(参数);如

int fun(int a,int b){...}  //示例函数
int (*p) (int,int);   //定义函数指针
p=fun;     //将函数入口地址赋给函数指针
int i=0,j=8;
(*p)(i,j) ;   //调用fun函数

p可指向返回类型为整型,参数为两个整型的函数,函数不唯一,可更换指向的函数,只要符合要求

想要通过函数指针调用函数时,需要先将函数入口地址赋给函数指针,函数的入口地址为函数名,故格式如下

int fun(int a, int b){....}
int fun_1(int m, int n){....}

int (*p) (int,int);  //定义指向函数  int (int int)的指针
int i=0,j=9;
p=fun;       //fun函数入口地址赋给p,不能包含参数,如 p=fun(i,j),
(*p)(i,j);   //通过函数指针p调用fun函数

p=fun_1;    //将P指向fun_1函数
(*p)(i,j);  //调用fun_1函数

PS:

(a)、*p两侧的括号不能省略,因为()的优先级高于*,若不加括号则 int * p(int,int);就变成了 int * (p(int,int));即变成了一个函数的声明。

(b)、p是指向函数的指针变量,它只能指向函数的入口处,不能指向函数中间的某一条指令,故p+1、*(p+1)不能指向下一条指令,类似的表达式无意义。

(3)函数指针作为函数参数

函数指针作为参数,处理流程和普通变量作为参数的操作一致,如

#include<iostream>​
using namespace std;
//示例
int min_int(int a,int b){
    return a>b ? b:a;
}
float min_float(float a,float b){
    return a>b ? b:a;
}
void fun(int (*p) (int,int),int (*q)(float,float)){
    int i=0,j=7;
    float m=6,n=4;
    (*p)(i,j);     //调用p指向的函数
    (*q)(m,n);     //调用q指向的函数
}
int main(){
    int (*p1) (int,int);   //声明函数指针
    p1 = min_int;          //指向函数
    float (*p2) (float,float);
    p2 = min_float;
    fun(p1,p2);
    return 0;
}

PS: 函数指针作为参数的场景,一般在程序中有多个函数,要根据不同条件执行不同的函数时,如

//求两数小值
int min(int a,int b){
  return a>b ? b:a;
}
//求两数大值
int max(int a,int b){
  return a>b ? a:b;
}

int main(){
    int (*p) (int,int);   //声明函数指针
    int flag = 1,result;
    int m = 80,n = 69;
    cout <<"请输入你的选择,1--求最大值,其他数值代表求最小值"<<endl;
    cin >>flag;
    if(flag == 1){
       p = max;
    }else{ 
       p = min; 
    }
    result = (*p)(m,n);
    return 0;
}

函数库(静态库与动态库)

库:源代码的二进制文件,根据加载的形式,分静态库和动态库(共享库)

windows下:静态库后缀为lib,动态库后缀为dll

      linux下:静态库后缀为a,动态库后缀为so

二者的不同点在于代码被载入的时刻不同。

静态库:静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。(以空间换时间)

动态库:共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。(以时间换空间)

PS:可执行程序在执行的时候如何定位共享库文件,

当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径,此时就需要系统动态载入器(dynamiclinker/loader),对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的 DT_RPATH段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目录。找到库文件后将其载入内存。

以上部分参考自:书籍《C++语言教程(第三版)》

以下部分转载于:https://www.cnblogs.com/wjcoding/p/10955017.html

浅拷贝和深拷贝

C++ 拷贝构造函数分为浅拷贝和深拷贝两种,浅拷贝和深拷贝主要区别就是复制指针时是否重新创建内存空间

如果没有创建内存只赋值地址为浅拷贝,创建新内存把值全部拷贝一份就是深拷贝。浅拷贝在类里面有指针成员的情况下只会复制指针的地址,会导致两个成员指针指向同一块内存,这样在要是分别delete释放时就会出现问题,因此需要用深拷贝。

类的默认拷贝构造函数

当创建一个类对象时,都会调用其构造函数初始化对象。当需要将一个对象来对新创建的对象进行初始化时,若用户未自定义拷贝构造函数,系统将会调用默认的拷贝构造函数,该默认拷贝构造函数执行的是一个浅拷贝。因为该拷贝构造函数执行时对两个对象进行的是逐域赋值,如下:

#include <iostream> 
using namespace std;
class Student
{
    private:
        int num;
        char *name;
    public:
        Student();
        ~Student();
 
};
Student::Student()
{
    name = new char(20);
    cout << "Student" << endl;
}
Student::~Student()
{
    cout << "~Student " << (int)name << endl;
    delete name;
    name = NULL;
}
int main()
{
    {// 花括号让s1和s2变成局部对象,方便测试
        Student s1;
        Student s2(s1);// 复制对象,内部实际进行的是 s2.num= s1.num;s2.name = s1.name;
    }
    system("pause");
    return 0;
}

执行结果:调用一次构造函数,调用两次析构函数,两个对象的指针成员所指内存相同,这会导致什么问题呢?name指针被分配一次内存,但是程序结束时该内存却被释放了两次,会导致崩溃!

这是由于编译系统在我们没有自己定义拷贝构造函数时,会在拷贝对象时调用默认拷贝构造函数,进行的是浅拷贝!即对指针name拷贝后会出现两个指针指向同一个内存空间。

所以,在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。添加了自己定义拷贝构造函数的例子:

#include <iostream> 
using namespace std;
class Student
{
    private:
        int num;
        char *name;
    public:
        Student();
        ~Student();
        Student(const Student &s);//自定义拷贝构造函数,const防止对象被改变
 
};
Student::Student()
{
    name = new char(20);
    cout << "Student" << endl;
}
Student::~Student()
{
    cout << "~Student " << (int)name << endl;
    delete name;
    name = NULL;
}
Student::Student(const Student &s)
{
    name = new char(20);         //为新对象重新分配动态内存地址
    memcpy(name, s.name, strlen(s.name));
    cout << "copy Student" << endl;
}
int main()
{
    {// 花括号让s1和s2变成局部对象,方便测试
        Student s1;
        Student s2(s1);
    }
    system("pause");
    return 0;
}

执行结果:调用一次构造函数,一次自定义拷贝构造函数,两次析构函数。两个对象的指针成员所指内存不同。
总结:浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
PS:
当对象中存在指针成员时,除了在复制对象时需要考虑自定义拷贝构造函数,还应该考虑以下两种情形:
1.当函数的参数为对象时,实参传递给形参的实际上是实参的一个拷贝对象,系统自动通过拷贝构造函数实现;
2.当函数的返回值为一个对象时,该对象实际上是函数内对象的一个拷贝,用于返回函数调用处。

3.浅拷贝带来上述问题的本质在于析构函数释放多次堆内存,使用std::shared_ptr,可以完美解决这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值