C++笔记


前言

个人学习笔记


一、命名空间

       为了解决在同一作用域中有名字冲突,C++引入了命名空间,通过作用域限定符,解决名字相同实体定义在名称空间中的变量或者函数)之间的冲突,其中实体的可见域是从实体创建到该名称空间结束。在名称空间外,该实体是不可见的。命名空间中可以定义变量、常量、函数(可以是声明或定义)、结构体、类、命名空间等, C++中命名空间是通过using编译指令,若对某命名空间不熟悉,可能会定义与之相同名字的实体,造成冲突,所以建议通过using声明机制。

 //C++的头文件都是用模板进行编写,所以不加.h
 //而模板有一个特点就是必须知道所有实现之后才能正常编译
#include <iostream>

//using namespace std;//命名空间,using编译指令,
                      //它的作用就是把命名空间中的实体一次性全部引出来,
                      //可能导致自己定义的实体与std里面的实体冲突

//#if 0
//using声明机制
using std::cout;//一次只将命名空间中的一个实体引出来,需要哪个引出哪个,
                //并且不会定义相同名字的实体产生二义性
using std::endl;
using std::string;
//#endif
namespace exampleA//带命名空间的函数声明
{
	void display();
}
//命名空间可以扩展(自定义命名空间可以扩展;标准命名空间也可以扩展)
namespace exampleA
{
    int var = 10;
    const char a[] = "hello,world";
    struct Mystruct
    {
        int number = 100;
        void test();//函数声明
    };
    //函数的申明是可以有多次,而函数的定义只能有一次
    void display()//函数定义
    {
        cout << "exampleA::display()" << endl;
    }
    namespace exampleAA
    {
        int var1 = 1;
        void display()
        {
            cout << "exampleA::exampleAA::display()" << endl;
        }
    }
    class student
    {
    public:
        student()
        :_name(nullptr)
        ,_age(0)
        {
            cout << "student()" << endl;
        }
        ~student()
        {
            cout << "~student()" << endl;
        }
        void add() const
        {
        	/*_age = 1;*/ //error
			cout << "age = " << _age <<endl;
        }
    private:
        string _name;
        int _age;
    };
}
int main(int argv, char *args[])
{
    exampleA::display();//::作用域限定符,即使自定义的实体与std命名空间中的实体冲突,也没有问题
    exampleA::exampleAA::display();
    return 0;
}

二、const关键字(常量不可以赋值

       C语言中定义常量通过define,如#define MAX 10,宏定义发生的时机在预处理阶段,如果有bug只能到运行的时候才能发现;C++定义常量通过const关键字,const可以修饰变量,变量就称为一个常量,常量在定义的时候必须要进行初始化,const可以在类型前也可以在类型后,如const int number = 10 与int const number = 10二者等价,其次const发生时机在编译阶段,如果有bug,就会立即报出来,相比宏定义要安全一些。

2.1 const修饰变量与指针

#include <iostream>
using namespace std;
void test()
{
    const int a = 10;
    int var = 20;
    const int *p = &var;//常量指针
    *p = 1;//错误,通过p指针无法修改其所指内容的值 
    p = nullptr;//正确,可以改变p指针的指向

    int const *p1 = &var;//常量指针
    *p1 = 2;//错误,通过p1指针无法修改其所指内容的值 
    p1 = nullptr;//正确,可以改变p1指针的指向

    int * const p3 = &var;//指针常量
    *p3 = 3;//正确,可以通过p3指针修改其所指内容的值
    p3 = nullptr;//错误,不可以改变p3指针的指向

    const int * const p4 = &var;//双const,两者皆不能修改
    *p4 = 4;//错误,不能通过p4指针修改其所指内容的值
    p4 = nullptr;//错误,不可以改p4指针的指向
}
int main()
{
    std::cout << "Hello world" << std::endl;
    return 0;
}

2.2 const修饰成员函数与对象

       const修饰成员函数时一般放在函数最后,如void add() const,对于不改变数据成员的成员函数都要在后面加const,const关键字对成员函数的行为作了更加明确的限定:有const 修饰的成员函数,只能读取成员变量,不能改变成员变量;没有const修饰的成员函数,则可读可写。
       const修饰对象时一般放最前面,如const student std(student为一个类),有以下几个特点:

  1. const修饰对象,该对象为常对象,不能修改对象的值。
  2. 可以调用const成员函数,不可以调用非const成员函数。

       由指针常量与常量指针,又可以联想到以下概念的区别(以int型为例)。

名称形式含义
常量指针const int * p不可以通过*p修改指针指向地址的内容,可以改变指针的指向
指针常量int * const p可以通过*p修改指针指向地址的内容,不可以改变指针的指向
函数指针int (*pfunc)(int)指向返回类型为int,参数为int型函数的指针
指针函数int* pfunc(int)一个返回类型是int*的函数
数组指针int (*pArray)[]指向一个int型数组
指针数组int* pArray[]一个名为pArray的数组,里面存放的是int*指针

三、malloc/free与new/delete区别

相同点

  1. 都是用来申请堆空间;
  2. 必须成对出现,不成对出现会造成内存泄漏。

不同点

  1. malloc/free是c语言的库函数,new/delete是C++的关键字(运算符或表达式);
  2. malloc只能申请原始的堆空间,new申请堆空间并进行初始化
  3. new/delete能对对象进行构造和析构函数的调用,进而对内存进行更加详细的工作,而 malloc /free不能。
#include <iostream>
#include <stdlib.h>// malloc/free
#include <string.h>// memset
#include <stdio.h>//  printf
using namespace std;
//malloc/free与new/delete的区别
//相同点:1、都是用来申请堆空间;2、必须成对出现,不成对出现会造成内存泄漏
//不同点:1、malloc/free是c语言的库函数,new/delete是C++的关键字(运算符或表达式)
//  2、malloc之恩那个申请原始的堆空间,new申请堆空间并进行初始化
void test1()
{
    int *pInt = (int *)malloc(sizeof(int));//1、申请堆空间
    memset(pInt, 0, sizeof(int));//2、初始化(清零)
    *pInt = 10;//3、赋值
    printf("*p = %d\n", *pInt);
    printf("&p = %p\n", &pInt);
    printf("p = %p\n", pInt);
    free(pInt);//4、回收堆空间
}
void test2()
{
    int *pInt = new int(10);//1、申请堆空间,并进行初始化,最后赋值10
    cout << "pInt = " << *pInt << endl;
    delete pInt;//2、回收堆空间

    cout << endl;
    int *pArray = new int[10]();//1、申请堆空间,并进行初始化
    for(size_t idx = 0; idx < 10; ++idx)
    {
        pArray[idx] = idx;
    }
    delete [] pArray;//2、释放堆空间[]
}
int main()
{
    test1();
    test2();
    return 0;
}

       由内存泄漏又可以联想到以下概念:

名称含义
内存泄漏程序在申请内存后,无法释放已申请的内存空间(最终会导致内存溢出)
内存溢出可用的内存均被占用,无法申请内存的情况
内存踩踏访问了不属于自己的地址
空悬指针指的是一个指针,当它指向的对象已经被释放的时候而自身却没有被置为null的时候
野指针没有进行初始化的指针,一个指针没有初始化的时候会一通乱指,这个时候就类似于空悬指针

四、引用

       变量其实质是一段连续内存空间的别名,而引用是变量的别名,或者可以说一段连续内存空间的引用是变量,需要注意的是引用必须初始化,且一经绑定后,就不可以改变其指向,C++中的引用本质上是一种被限制的指针,是一个指针常量,所以引用是占据内存空间的,大小就是一个指针的大小,引用的提出是为了减少指针的使用。
       单纯的给某变量取别名是没有任何意义,引用的目的主要用于在函数参数传递中,解决大块数据或对象的传递效率和空间不如意的问题。用引用传递函数的参数,能保证参数传递中不产生副本,提高传递的效率,且通过const的使用,保证了引用传递的安全性。
       在64位系统中,一个int型指针占8个字节,通过指针可以间接对变量操作,但需要对指针变量分配内存空间,相比引用效率低且可读性较差;而引用就是变量别名,对引用操作就是对变量本身进行操作,所以效率较高。引用一般有2种用法:一是引用作为函数参数;二是引用作为函数的返回值。

4.1 引用作为函数参数

#include <iostream>
using namespace std;
void swap(int x, int y)//值传递
//系统会在内存中开辟空间用来存储形参变量,并将实参变量的值拷贝给形参变量,
//即形参变量只是实参变量的副本而已,即int x = a,int y = b
{
    int tmp = x;
    x = y;
    y = tmp;
}
void swap1(int *x, int *y)//地址传递,即int *x = &a, int *y = &b
//需要为形参指针变量在内存中分配空间,通过指针变量间接对变量本身进行操作
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}
void swap2(int &x, int &y)//即int &x = a, int &y = b
//与操作变量本身是一样的,引用就是变量别名
{
    int tmp = x;
    x = y;
    y = tmp;
}
int main()
{
    int a = 3,b = 4;
    cout << "a =" << a << ",b = " << b << endl;
    swap(a, b);
    cout << "a =" << a << ",b = " << b << endl;
    swap1(&a, &b);
    cout << "a =" << a << ",b = " << b << endl;
    swap2(a, b);
    cout << "a =" << a << ",b = " << b << endl;
    return 0;
}

4.2 引用作为函数返回值

       当以引用作为函数的返回值时,返回的变量其生命周期一定是要大于函数的生命周期的,即当函数执行完毕时,返回的变量还存在,也就是说引用作为函数的返回值时,不可以返回局部变量的引用,因为局部变量会在函数返回后被销毁,因此被返回的引用就成为 了"无所指"的引用,程序会进入未知状态。不要返回堆空间引用,因为堆空间引用所在的空间无法释放,会造成内存泄漏,除非有内存回收的机制。

#include <iostream>
using namespace std;
int arr[10] = {1,2,3,4};
int &getdata()
{
    return arr[0];
}
void test2()
{
    cout << "getdata() = " << getdata() << endl;
    getdata() = 100;
    cout << "getdata() = " << getdata() << endl;
    cout << "arr[0] = " << arr[0] << endl;
}
//不要返回局部变量的引用
int &func()
{
    int var = 100;
    return var;//生命周期已经结束
}
//函数返回引用的前提:实体的生命周期一定要大于函数的生命周期

//建议不要返回堆空间的引用,除非有内存回收的机制
int &getHeapdata()
{
    int *number = new int(10);//堆空间的生命周期会一直持续,直到释放
    return *number;
}
void test3()
{
	int a = 3, b = 4;
	int c = a + getHeapdata() + b;//有内存泄漏的隐患
	cout << "c = " << c << endl;

	int &ref = getHeapdata();//用引用接收
	delete &ref;//删除引用所指向的堆空间
}
int main()
{
	test2();
	test3();
	return 0;
}

五、C++强制转换

       传统C语言的强制转换有很多缺点,因为它可以在任意类型之间进行转换,比如可以把一个指向const对象的指针转换成指向非const对象的指针,把一个指向基类对象的指针转换成一个指向派生类对象的指针,显然这二者差别很大,还有就是c风格的转换不容易查找,它由一个括号加上一个标识符组成,而这样的代码在C++程序到处都是。为了克服这些缺点,C++引进了4个新的类型转换操作符,分别是:

  1. static_cast
  2. const_cast
  3. dynamic_cast
  4. reinterpret_cast
#include <iostream>
using namespace std;
void test()
{
    int iNumber = 10;
    float fNumber = 12.34;
    iNumber = (int)fNumber;//c语言强制转换
    iNumber = int(fNumber);

    iNumber = static_cast<int>(fNumber);
    cout << "iNumber = " << iNumber << endl;


    void *pret = malloc(sizeof(int));
    int *pInt = static_cast<int *>(pret);//void *型指针转换为int *型指针
    delete pInt;
    pInt = nullptr;//指针置为空
}
void test2()
{
    const int number = 100;
    /* int *p = &number;//error */
    int *p = const_cast<int *>(&number);
    cout << "*p = " << *p << endl;

    cout << endl;
    *p = 200;//c++中未定义的行为
    cout << "number = " << number << endl;
    cout << "*p = " << *p << endl;
    
    printf("&number = %p\n", &number);
    printf("&p = %p\n", p);
}
int main()
{
    test();
    test2();
    return 0;
}

5.1 static_cast用法

       1)用于基本数据类型之间的转换,如把int转换成char,把int转换成enum 。
       2)把void *指针转换成目标类型的指针,但不安全。
       3)把任何类型的表达式转换成void类型。
       4)用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。

5.2 const_cast用法

       修改变量或对象的const属性,把常量属性移除。

5.3 dynamic_cast用法

       主要用于基类和派生类间的转换,尤其是向下转型的用法中。

5.4 reinterpret_cast用法

       用来处理无关类型之间的转换,任意指针(或引用)类型之间的转换,以及指针与足够大的整数类型之间的转换。错误的使用reinterpret_cast容易导致程序不安全,只有将转换后的类型值转换回到其原始类型,才是正确使用reinterpret_cast的方式。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一门linux下c++通讯架构实战课程,针对c/c++语言已经掌握的很熟并希望进一步深造以将来用c++在linux下从事网络通讯领域/网络服务器的开发和架构工作。这门课程学习难度颇高但也有着极其优渥的薪水(最少30K月薪,最高可达60-80K月薪),这门课程,会先从nginx源码的分析和讲解开始,逐步开始书写属于自己的高性能服务器框架代码,完善个人代码库,这些,将会是您日后能取得高薪的重要筹码。本课程原计划带着大家逐行写代码,但因为代码实在过于复杂和精细,带着写代码可能会造成每节课至少要4~5小时的超长时间,所以老师会在课前先写好代码,主要的时间花费在逐行讲解这些代码上,这一点望同学们周知。如果你觉得非要老师领着写代码才行的话,老师会觉得你当前可能学习本门课程会比较吃力,请不要购买本课程,以免听不懂课程并给老师差评,差评也会非常影响老师课程的销售并造成其他同学的误解。 这门课程要求您具备下面的技能:(1)对c/c++语言掌握的非常熟练,语言本身已经不是继续学习的障碍,并不要求您一定熟悉网络或者linux;(2)对网络通讯架构领域有兴趣、勇于挑战这个高难度的开发领域并期望用大量的付出换取高薪;在这门课程中,实现了一个完整的项目,其中包括通讯框架和业务逻辑框架,浓缩总结起来包括如下几点:(1)项目本身是一个极完整的多线程高并发的服务器程序;(2)按照包头包体格式正确的接收客户端发送过来的数据包, 完美解决收包时的数据粘包问题;(3)根据收到的包的不同来执行不同的业务处理逻辑;(4)把业务处理产生的结果数据包正确返回给客户端;本项目用到的主要开发技术和特色包括:(1)epoll高并发通讯技术,用到的触发模式是epoll中的水平触发模式【LT】;(2)自己写了一套线程池来处理业务逻辑,调用适当的业务逻辑处理函数处理业务并返回给客户端处理结果;(3)线程之间的同步技术包括互斥量,信号量等等;(4)连接池中连接的延迟回收技术,这是整个项目中的精华技术,极大程度上消除诸多导致服务器程序工作不稳定的因素;(5)专门处理数据发送的一整套数据发送逻辑以及对应的发送线程;(6)其他次要技术,包括信号、日志打印、fork()子进程、守护进程等等;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值