C++入门基础

C++入门基础

namespace专题讲座

namespace概念

所谓namespace,是指标识符的各种可见范围。Cpp标准程序库中的所有标识符都被定义于一个名为std的namespace中。

  1. <iostream>和<iostream.h>格式不一样,前者没有后缀,实际上,在你的编译器include文件夹里面可以看到,二者是两个文件,打开文件就会发现,里面的代码是不一样的。后缀为.h的头文件cpp标准已经明确提出不支持了,早些的实现将标准库功能定义在全局空间里,声明在带.h后缀的头文件里,cpp标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。 因此,
* 当使用<iostream.h>时,相当于在c中调用库函数,使用的是全局命名空间,也就是早期的cpp实现;
* 当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。
  1. 由于namespace的概念,使用Cpp标准程序库的任何标识符时,可以有三种选择:
    • 直接指定标识符。例如std::ostream而不是ostream。完整语句如下: std::cout << std::hex << 3.4 << std::endl;
    • 使用using关键字。 using std::cout; using std::endl; using std::cin; 以上程序可以写成 cout << std::hex << 3.4 << endl;
    • 最方便的就是使用using namespace std; 例如: using namespace std;这样命名空间std内定义的所有标识符都有效(曝光)。就好像它们被声明为全局变量一样。那么以上语句可以如下写: cout <<hex << 3.4 << endl;因为标准库非常的庞大,所以程序员在选择的类的名称或函数名 时就很有可能和标准库中的某个名字相同。所以为了避免这种情况所造成的名字冲突,就把标准库中的一切都被放在名字空间std中。但这又会带来了一个新问 题。无数原有的Cpp代码都依赖于使用了多年的伪标准库中的功能,他们都是在全局空间下的。所以就有了<iostream.h> 和<iostream>等等这样的头文件,一个是为了兼容以前的Cpp代码,一个是为了支持新的标准。命名空间std封装的是标准程序库的名称,标准程序库为了和以前的头文件区别,一般不加".h"

cpp命名空间定义及使用语法

  1. 在Cpp中,名称(name)可以是符号常量、变量、宏、函数、结构、枚举、类和对象等等。为了避免,
    在大规模程序的设计中,以及在程序员使用各种各样的Cpp库时,这些标识符的命名发生冲突,
    标准Cpp引入了关键字namespace(命名空间/名字空间/名称空间/名域),可以更好地控制标识符的作用域。

  2. std是cpp标准命名空间,cpp标准程序库中的所有标识符都被定义在std中,比如标准库中的类iostream、vector等都定义在该命名空间中,使用时要加上using声明(using namespace std) 或using指示(如std::string、std::vector<int>).::

  3. C中的命名空间
    在C语言中只有一个全局作用域
    C语言中所有的全局标识符共享同一个作用域
    标识符之间可能发生冲突
    Cpp中提出了命名空间的概念
    命名空间将全局作用域分成不同的部分
    不同命名空间中的标识符可以同名而不会发生冲突
    命名空间可以相互嵌套
    全局作用域也叫默认命名空间

    Cpp命名空间的定义:
    namespace name {  …  }
  1. Cpp命名空间的使用:
    使用整个命名空间:using namespace name;
    使用命名空间中的变量:using name::variable;
    使用默认命名空间中的变量:::variable
    默认情况下可以直接使用默 认命名空间中的所有标识符

cpp命名空间实践

#include "iostream"

namespace NamespaceA
{
     int a = 0;
}

namespace NamespaceB
{
    int a = 1;

    namespace NamespaceC
    {
        struct Teacher{
            char name[68];
            int age;
        };
    }
}

int main(){

    using namespace NamespaceA;
    using namespace NamespaceB;
    printf("namespaceA:%d \n",NamespaceA::a);
    printf("namespaceB:%d \n",NamespaceB::a);
    NamespaceB::NamespaceC::Teacher t = {"abc",1};
    printf("t.name:%s \n",t.name);
    printf("t.age:%d \n",t.age);    


    return 0;
}

打印结果如下:

namespaceA:0 
namespaceB:1 
t.name:abc 
t.age:1 

结论

  • 当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。若不引入using namespace std ,需要这样做。std::cout。

    
    using namespace std;
    
    int main(){
    
        cout<<"hello world!"<<endl;
    
        return 0;
    }
    
  • c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。

  • C++命名空间的定义: namespace name { … }

  • using namespace NameSpaceA;

  • namespce定义可嵌套。

C++中的语法增强

实用性增强

C语言中的变量都必须在作用域开始的位置定义!!C++中更强调语言的“实用性”,所有的变量都可以在需要使用时再定义。

int main(){

    int a = 0;
    printf("a:%d \n",a);
    int k;

    return 0;
}

register关键字增强

register关键字 请求编译器让变量a直接放在寄存器里面,速度快(不通过内存来存取变量,内存和CPU中来回运作速度会降低)
在C语言中 register修饰的变量 不能取地址,但是在c++里面做了内容增强:

  1. register关键字的变化
    register关键字请求“编译器”将局部变量存储于寄存器中
    C语言中无法取得register变量地址
    在C++中依然支持register关键字
    C++编译器有自己的优化方式,不使用register也可能做优化
    C++中可以取得register变量的地址

  2. C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效。

  3. 早期C语言编译器不会对代码进行优化,因此register变量是一个很好的补充。


int main(){

   register int  a = 0;
   printf("&a = %p \n",&a);
    return 0;
}

//打印结果 &a = 0x7fff586d7698 

函数检测增强

在C语言中,重复定义多个同名的全局变量是合法的。但在C++中,不允许定义多个同名的全局变量。C语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上

C++直接拒绝这种二义性的做法。

int g_var; 
int g_var = 1;
//err: previous definition is here

struct类型加强

C语言的struct定义了一组变量的集合,C编译器并不认为这是一种新的类型
C++中的struct是一个新类型的定义声明

#include "iostream"
using namespace std;

struct Student{
    char name[64];
    int age;
};

int main(){
    Student s1 = {"小红",18};
    Student s2 = {"小明",17};
    return 0;
}

C++中所有的变量和函数都必须有类型

C++中所有的变量和函数都必须有类型
C语言中的默认类型在C++中是不合法的
函数f的返回值是什么类型,参数又是什么类型?
函数g可以接受多少个参数?

void f(int i){
    printf("i = %d \n",i);
}

//该代码在C中可以正常编译通过,但是在C++中会报错
g(){
    return 10;
}

int main(){
    f(1);
    return 0;
}

总结:
在C语言中:
int f( );表示返回值为int,接受任意参数的函数
int f(void);表示返回值为int的无参函数
在C++中:
int f( )和int f(void)具有相同的意义,都表示返回值为int的无参函数

C++更加强调类型,任意的程序元素都必须显示指明类型

一、C语言register关键字—最快的关键字

register:这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率。注意是尽可能,不是绝对。你想想,一个CPU 的寄存器也就那么几个或几十个,你要是定义了很多很多register 变量,它累死也可能不能全部把这些变量放入寄存器吧,轮也可能轮不到你。

1. 皇帝身边的小太监----寄存器

  不知道什么是寄存器?那见过太监没有?没有?其实我也没有。没见过不要紧,见过就麻烦大了。大家都看过古装戏,那些皇帝们要阅读奏章的时候,大臣总是先将奏章交给皇帝旁边的小太监,小太监呢再交给皇帝同志处理。这个小太监只是个中转站,并无别的功能。

  好,那我们再联想到我们的CPU。CPU 不就是我们的皇帝同志么?大臣就相当于我们的内存,数据从他这拿出来。那小太监就是我们的寄存器了(这里先不考虑CPU 的高速缓存区)。数据从内存里拿出来先放到寄存器,然后CPU 再从寄存器里读取数据来处理,处理完后同样把数据通过寄存器存放到内存里,CPU 不直接和内存打交道。这里要说明的一点是:小太监是主动的从大臣手里接过奏章,然后主动的交给皇帝同志,但寄存器没这么自觉,它从不主动干什么事。一个皇帝可能有好些小太监,那么一个CPU 也可以有很多寄存器,不同型号的CPU 拥有寄存器的数量不一样。

  为啥要这么麻烦啊?速度!就是因为速度。寄存器其实就是一块一块小的存储空间,只不过其存取速度要比内存快得多。进水楼台先得月嘛,它离CPU 很近,CPU 一伸手就拿到数据了,比在那么大的一块内存里去寻找某个地址上的数据是不是快多了?那有人问既然它速度那么快,那我们的内存硬盘都改成寄存器得了呗。我要说的是:你真有钱!

2.举例

  register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。例如下面的内存块拷贝代码:

  #ifdef NOSTRUCTASSIGN
  memcpy (d, s, l)
  {
        register char *d;
      register char *s;
      register int i;
      while (i--)
          *d++ = *s++;
  }
  #endif
3.使用register 修饰符的注意点

  但是使用register修饰符有几点限制。

  首先,register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。

  其次,因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。

  由于寄存器的数量有限,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。

  在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。

  早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。

二、bool类型专题

C++中的布尔类型
  • C++在C语言的基本类型系统之上增加了bool
  • C++中的bool可取的值只有true和false
  • 理论上bool只占用一个字节,如果多个bool变量定义在一起,可能会各占一个bit,这取决于编译器的实现
  • true代表真值,编译器内部用1来表示
  • false代表非真值,编译器内部用0来表示
  • bool类型只有true(非0)和false(0)两个值
    C++编译器会在赋值时将非0值转换为true,0值转换为false
int main(){

    int a;
    bool b = true;
    printf("b = %d,sizeof(b) = %lu \n",b,sizeof(b));
    //b = 1,sizeof(b) = 1 
    a = 4;
    b = 5;
    printf("a = %d ,b = %d \n",a,b);
    //a = 4 ,b = 1 

    a = -4;
    a = b;
    printf("a = %d ,b = %d \n",a,b);
    //a = 1 ,b = 1 

    a = 10;
    b = a;
    printf("a = %d ,b = %d \n",a,b);
    //a = 10 ,b = 1 

    b = 0;
    printf("b = %d \n",b);
    //b = 0 

    return 0;
}
/*
打印结果:
b = 1,sizeof(b) = 1 
a = 4 ,b = 1 
a = 1 ,b = 1 
a = 10 ,b = 1 
b = 0
*/

三、三目运算符

int main(){

    int a = 10;
    int b = 20;
    //此处在.c文件中是无法编译通过的
    (a < b ? a : b) = 30;

    //返回一个最小数 并且给最小数赋值成3
    //三目运算符是一个表达式 ,表达式不可能做左值
    printf("a = %d ,b = %d \n",a,b);
    //a = 30 ,b = 20 

    return 0;
}
  1. C语言返回变量的值 C++语言是返回变量本身
  2. C语言中的三目运算符返回的是变量值,不能作为左值使用。C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方。
  3. 注意:三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用
    (a < b ? 1 : b )= 30;
  4. C语言如何支持类似C++的特性?
    变量的本质是内存空间的别名,是一个标号。

问题的实质:
c语言返回的是变量的值,C++语言返回的是变量本身

C/C++中的Const专题

一、const基础知识(用法、含义、好处)

int main(){

    //第一个第二个意思一样 代表一个常整形数
    const int a = 10;
    int const b = 10;
    //第三个 c是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本身可以修改)
    const int * c ;
    int z = 10;

    c = &z;
    //err: *c = 20;
    z = 20;
    printf("z = %d \n",z);
    //第四个 d 常指针(指针变量不能被修改,但是它所指向内存空间可以被修改)
    int * const d  = &z;
    z = 100;
    z = 200;
    z = 300;
    printf("z = %d \n",z);
    int x = 10;
    //第五个 e一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)
    const int * const e  = &x;
    //eer:*e = 400
    printf("e = %d \n",e);
    return 0;
    //初级理解:const是定义常量==》const意味着只读
}
const好处

合理的利用const:

  1. 指针做函数参数,可以有效的提高代码可读性,减少bug;
  2. 清楚的分清参数的输入和输出特性
struct Teacher{
    char name[64];
    int age;
};

//const修饰指针所指内存空间不能被修改
void printfTeacher(const Teacher *p){
    printf("teacher.age: %d \n",p->age);
    //err:p->age = 100;
}

//const修饰指针 p变量不能被修改
void printfTeacher2( Teacher * const p){
    Teacher t = {"xiaohong",18};
    //err:p = &t;
    //p = NULL;
}
//const修饰指针 p变量不能被修改
void printfTeacher3(const Teacher * const p){
    //Teacher t = {"xiaohong",18};
    //err:p = &t;
   //p = NULL;
    printf("p:age:%d \n", p->age);
}

int main(){

    Teacher t = {"liming",10};
    printfTeacher3(&t);
    return 0;
}


二、C中的“冒牌货”

int main(){
    const int a = 10;
    int *p = (int*)&a; 
    *p = 12;
    printf("a===>%d\n", a);
    printf("*p===>%d\n", *p);
    //*p是10 。。。。说明p和&a绑定的很死。。。
    //*p是11.。。。。说明*p所指的内存空间和&a,不一样。。。
    printf("&a: %d \n", &a);
    printf("p: %d \n", p);

    return 0;
}

/*结果
a===>10
*p===>12
&a: 1435678472 
p: 1435678472 
*/

解释:
C++编译器对const常量的处理
当碰见常量声明时,在符号表中放入常量 =
问题:那有如何解释取地址
编译过程中若发现使用常量则直接以符号表中的值替换
编译过程中若发现对const使用了extern或者&操作符,则给对应的常量分配存储空间(兼容C)

const是常量

在C++中,const修饰的是一个真正的常量,而不是C中变量的只读

int main(){
    const int a = 1;
    const int b = 2;
    int array[a+b] = {0};
    int i = 0;
    for(i = 0;i < (a + b);i++){
        printf("array[%d] = %d \n",i,array[i]);
    }
    return 0;
}

/*
array[0] = 0 
array[1] = 0 
array[2] = 0 
*/
联系const和#define的区别

对比加深

C++中的const常量类似于宏定义
const int c = 5; ≈ #define c 5
C++中的const常量在与宏定义不同
const常量是由编译器处理的,提供类型检查和作用域检查
宏定义由预处理器处理,单纯的文本替换

void fun1(){
    #define a 10
    const int b  = 20;
};

void fun2(){
    printf("a = %d \n",a);
    //err:printf("b = %d \n",b);
};

int main(){
    fun1();
    fun2();
    return 0;
}
//结果:在func1中定义的a可以在main里面使用,b不可以

引用专题

一.引用(普通引用)

0.变量名回顾

变量名实质上是一段连续存储空间的别名,是一个标号(门牌号)
程序中通过变量来申请并命名内存空间
通过变量的名字可以使用存储空间

1.引用是C++的概念,属于C++编译器对C的扩展

问题:C中可以编译通过吗?

.c
int main(){
   
     int a = 10;
     int * const b = &a;
     *b = 11;

    return 0;
}

.cpp
int main()
{
    int a = 0;
    int &b = a; 
   b = 11;  
    return 0;
}

结论:请不要用C的语法考虑 b=11

2.引用概念
  1. 在C++中新增加了引用的概念
  2. 引用可以看作一个已定义变量的别名
  3. 引用的语法:Type& name = var;
  4. 引用做函数参数那?(引用作为函数参数声明时不进行初始化)
int main(){

    int a = 10;
    int &b = a;
    b = 11;
    cout<<"b-->"<<a<<endl;
    printf("a:%d \n",a);
    printf("b:%d \n",b);
    printf("&a:%d \n",&a);
    printf("&b:%d \n",&b);
    /*
    b-->11
    a:11 
    b:11 
    &a:1545963256 
    &b:1545963256 
    */
    
    return 0;
}
3. 引用的意义
  • 引用作为其它变量的别名而存在,因此在一些场合可以代替指针
  • 引用相对于指针来说具有更好的可读性和实用性
dome1
int swap1(int &a,int &b){
    int t = a;
    a = b;
    b = t;
    return 0; 
}
int swap2(int *a,int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
    return 0;
}

int main(){
    int a = 10;
    int b = 20;
    int c = 30;
    int d = 40;
    swap1(a,b);
    swap2(&c,&d);
    printf("a:%d,b%d,c:%d,d:%d",a,b,c,d);
    //a:20,b10,c:40,d:30
    return 0;
}

dome2
struct Tearcher{
    char name[64];
    int age;
};
int getTearcher(Tearcher **myp){

    Tearcher *p = (Tearcher*)malloc(sizeof(Tearcher));
    if(p == NULL){return -1;};
    memset(p,0,sizeof(Tearcher));
    p->age = 30;
    *myp = p;
    return 0;
}

//此处奇怪的写法实际上是:传递指针的引用
int getTearcherRe(Tearcher* &myp){
     myp = (Tearcher*)malloc(sizeof(Tearcher));
     if(myp == NULL){return -1;};
     myp->age  = 40;
     memset(p,0,sizeof(Tearcher));
     return 0;
}

int main(){
    Tearcher *p = NULL;
    //getTearcher(&p); // log:age:30 
    getTearcherRe(p);  //log:age:40
   printf("age:%d \n",p->age);
    return 0;
}

4.普通引用有自己的空间吗?
struct Student{
    int &a;
    int &b;
};


int main(){
    printf("sizeof(Student):%lu \n",sizeof(Student));
    //sizeof(Student):16 (mac64位机)
    return 0;
}

引用是一个有地址,引用是常量:char * const p

答案:有

5.引用的本质
  1. 引用在C++中的内部实现是一个常指针
    Type& name <---> Type* const name
  2. C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
  3. 从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏
    void func(int &a){a = 5;}
    void func(int * const a){*a = 5} 
6.引用作为返回值时候的对比
//static修饰变量的时候,变量是一个状态变量
int j(){
    static int a = 10;
    a++;
    printf("a:%d \n",a);
    return a;
}
int& j1(){
    static int a = 10;
    a++;
    printf("a:%d \n",a);
    return a;
}
int* j2(){
    static int a = 10;
    a++;
    printf("a:%d \n",a);
    return &a;
}

int main(){
     // 1. j() = 100;//该函数运算结果是一个数值,没有内存地址,不能当左值
    j();
  
    //2 .当被调用的函数当左值的时候,必须返回一个引用
    j1() = 100;

    //3.此处相当于我们程序员手工的打造 做左值的条件
    *(j2()) = 200;
    j2();
    return 0;
}
int getA(){
    int a ;
    a = 10;
    return a;
}
//基础类型a返回的时候,也会有一个副本,即返回的不是函数内的a(需要出栈),而是一个副本
int& getB(){
    int a;
    a = 10;
    return a;
}
int* getC(){
    int a;
    a = 10;
    return &a;
}
int main(){

    int a1 = 0;
    int a2 = 0;
    a1 = getA();
    a2 = getB();
    //这里是没有办法用引用接到 函数返回值返回来的内存地址的
    int &a3 = getB();
    printf("a1:%d \n",a1);
    printf("a2:%d \n",a2);
    printf("a3:%d \n",a3);
   // a1:10 
   // a2:10 
   // a3:0 

    return 0;
}

  1. 请仔细对比间接赋值成立的三个条件
    • 定义两个变量 (一个实参一个形参)
    • 建立关联 实参取地址传给形参
    • *p形参去间接的修改实参的值
6.C++引用注意

  当函数返回值为引用时,若返回栈变量,不能成为其它引用的初始值,不能作为左值使用
  若返回静态变量或全局变量,可以成为其他引用的初始值,即可作为右值使用,也可作为左值使用
  C++链式编程中,经常用到引用,运算符重载专题

7.结论

引用在实现上,只不过是把:间接赋值成立的三个条件的后两步和二为一。
当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)

二.常引用

const引用为难点,使用的时候应该特别注意。

1.const引用(使用变量初始化const引用)

在C++中可以声明const引用const Type& name = var;const引用让变量拥有只读属性


int main(){

    int a = 10;
    const int &b = a;
    // b = 11; //err:不允许随意修改常量引用
    int *p = (int *)&b;
    *p = 11;

    cout<<"b--->"<<a<<endl;
    printf("a:%d\n", a);
    printf("b:%d\n", b);
    printf("&a:%d\n", &a);
    printf("&b:%d\n", &b);
    /*
    b--->11
    a:11
    b:11
    &a:1345795832
    &b:1345795832
    */
    return 0;
}

2.const引用(使用字面量常量初始化const引用)

分两种情况:
1、用变量对const引用初始化,const引用分配内存空间了吗?//不分配
2、用常量对const引用初始化,const引用分配内存空间了吗?//分配

int main(){
    const int &a = 10;
   // int &b = 10;//err: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
    int *p = (int*)&a;
    *p = 12;
    printf("a:%d \n",a);
    return 0;
}

  • 当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名
  • 使用常量对const引用初始化后将生成一个只读变量
3.结论const引用

const& 相当于 const int * const e
*普通引用 相当于 int const e1

函数专题

一.函数基础知识

1.内联函数
  • inline关键字必须和函数实现放在一块
  • inline是一个请求,告诉编译进行内联编译
inline void printA(){
    printf("test");
}

int main(){

    printA();
    /*
    实际上是讲printA函数大括号的内容直接拷贝过来
    {
     printf("test");
    }
    */

    return 0;
}

带参数的宏和普通函数区别

    #define MYFUNC(a,b)  ((a) < (b) ? (a) : (b))

inline int myFunc(int a,int b){
    return a < b ? a : b;
}

int main(){

    int a = 1;
    int b = 3;
    //不要让头疼兄弟++i,i++来做函数参数
     int c = myFunc(++a,b); //c: 3 
    // int c = MYFUNC(++a,b); // ((++a) < (b) ? (++a) : (b))
    printf("a = %d\n", a); //2  //3
    printf("b = %d\n", b); //3  //3
    printf("c = %d\n", c); //2  //3
    //要注意,宏是整个内容的替换,所以++a会被执行两次
    return 0;
}

2.函数的扩展
  • 函数参数位置可以加上等号配置默认参数,如果不传递参数的时候则按照函数参数默认参数传递
  • 在默认参数规则 ,如果默认参数出现,那么右边的都必须有默认参数
  • 有占位参数的时候,在函数调用的时候需要传入参数
  • 占位参数如果有默认值的时候,可以选择传递或者不传递参数
//默认参数
void printAB(int x = 3 ){
    printf("x = %d \n",x);
}
//在默认参数规则 ,如果默认参数出现,那么右边的都必须有默认参数
void printABC(int x,int y,int a = 1,int b = 2,int c = 3){
    printf("x = %d \n",x);
}
//占位参数
int func (int x, int y, int )
{
    return x + y;
}

int func2(int a, int b, int = 0)
{
    return a + b;
}

int main(){
    printAB(10);
    printAB();
    func(1,2,3);
   //func(1,2);//err:必须要有三个参数
   //-----------
   //如果默认参数和占位参数在一起,都能调用起来
    func2(1,2);
    func2(1,2,3);

    return 0;
}

3.函数重载

函数重载:函数名称一样,但是参数数量不一样,类型不一样,参数的顺序也不一样

void myprint(int a)
{
    printf("a:%d \n", a);
}

void myprint(int a, char *p)
{
    printf("a:%d \n", a);
}

void myprint(char *p, int a)
{
    printf("a:%d \n", a);
}


void myprint(double a)
{
    printf("a:%d \n", a);
}
int main(){
    myprint(1);
    myprint(2,"3");
    myprint("4",5);
    return 0;
}
4.重载中存在的二义性错误
int func(int a,int b ,int c = 0){
    printf("a:%d ", a);
    return 0;
}

int func(int a,int b ){
    printf("a:%d ", a);
    return 0;
}

int main(){
    int c = 0;
    //c = func(1,2);//call to 'func' is ambiguous 调用函数不能存在二义性报错
    c = func(1,2,3);
  
    return 0;
}

5.函数重载遇上函数指针

首先复习一下如何定义一个类型,主要是通过:typedef type name;

//定义一个数组类型
typedef int MYTYPEArray[10];
MYTYPEArray a;// int a[10];
//定义一个数组类型指针类型
typedef int (*MyArrayP)[10];
MyArrayP myarrayp = NULL;
//int (*myp)[10];//让编译器分配4个字节的内存,该变量为指针,指向一个数组
//定义一个类型,这个类型是函数类型
typedef int(*PFUNC)(int a);
typedef int(*PFUNC2)(const  char * p);

以上即是定义一个类型的方法 ,也包括了定义函数类型,那么我们应该如何使用它呢?

int func(int x){
    return x;
}

int func(int x,int y){
    return x+y;
}

int func(const char* p){
    printf("char:%s",p);
    return strlen(p);
}
//定义一个类型,这个类型是函数类型
typedef int(*PFUNC)(int a);
typedef int(*PFUNC2)(const  char * p);

int main(){
  int c = 0;
  {
    PFUNC p = func;
    c = p(1);
  }
  printf("c:%d \n",c);

  {
    PFUNC2 myp2 = func;
    myp2("abcde");
  }
    return 0;
}
    /*打印结果
    c:1 
    char:abcde
    */

此处可以直观的看出我们使用函数指针指向某个函数的时候,也用指针来调用函数。

C++类专题

最基本的类的定义

其实C++中定义一个类,和其他面向对象语言也特别相似,学习起来也特别快,关键字是:class + 类名{};。类可以是空类。引入代码让大家更好的理解:

类是把属性和方法封装
我们抽象了一个类,用类去定义对象。类是一个数据类型,类是抽象的。对象是一个具体的变量。占用内存空间。类做函数参数的时候,类封装了属性和方法,在被调用函数里面, 不但可以使用属性,而且可以使用方法(成员函数);
面向过程编程加工的是:函数
面向对象编程加工的是:类

基本类的实例
class Cube{
public: 
    int getA(){
        return m_a;
    }
    int getB(){
        return m_b;
    }
    int getC(){
        return m_c;
    }
    void setABC(int a = 0,int b = 0,int c = 0){
        m_a = a;
        m_b = b;
        m_c = c;
    }
    void setA(int a)
    {
        m_a = a;
    }
    void setB(int b)
    {
        m_b = b;
    }
    void setC(int c)
    {
        m_c = c;
        
    }
public:
    int getV(){
        m_v =  m_a * m_b * m_c;
        return m_v;
    }

    int getS(){
        m_s = 2*(m_a*m_b + m_b*m_c + m_a*m_c);
        return m_s;
    }

private:
    int m_a;
    int m_b;
    int m_c;
    int m_v;
    int m_s;
};

int main(){

    Cube c1;
    c1.setA(3);
    c1.setB(3);
    c1.setC(3);
    cout<<"c1的面积是:"<<c1.getV()<<endl;
    cout<<"c1的周长是:"<<c1.getS()<<endl;
    // c1的面积是:27
    // c1的周长是:54

    Cube c2;
    c2.setABC(5,5,5);
    cout<<"c2的面积是:"<<c2.getV()<<endl;
    cout<<"c2的周长是:"<<c2.getS()<<endl;
    // c2的面积是:125
    // c2的周长是:150

    return 0;
}

访问权限

public/protected/private是访问权限控制,有接触过的同学应该比较清楚,一个类可以有成员变量,成员属性组
成。
类的访问控制,三个关键字

  • public 成员变量和成员函数可以在类的内部和外界访问和调用
  • protected 成员变量和成员函数可以在子类的内部和外界访问和调用
  • private 成员变量和成员函数只能在类的内部被访问和调用
类的内存四区理解

另外,我们可以用sizeof打印一下Cube的大小

int main(){
    printf("class:siezof(cube)->%lu",sizeof(Cube));
    //class:siezof(cube)->20
    return 0;
}

可以发现刚刚那个Cube类的大小是20,五个成员属性,一个int 是4个字节,合计20。这里可以看出来,其实函数并没有和成员变量放在一起,而是放在了代码区。
然后,我后面在成员遍历里面添加了一个char* p的属性。那么,大家会觉得这次的sizeof会等于多少呢?

class Cube{
......
private:
    int m_a;
    int m_b;
    int m_c;
    int m_v;
    int m_s;
    char * p;
};

int main(){
    printf("class:siezof(cube)->%lu",sizeof(int));
    //class:siezof(cube)->?
    return 0;
}

实际上这个时候打印的是32,同学们可以试一下。可是指针在32系统下不是4个字节,64的系统也只是8个字节,为何会出现 20 + 8 = 32 呢?后来我再里面添加了一个int d属性,打印的依然是32。

我查了一下资料,大致是说,计算机为了内存的连续可读性,有时候宁愿空着4个字节不用,也要把它空出来提高效率和内存连续性。所以在我们认为应该打印28的时候,实际上系统给我们的内存优化,直接占了8位。

类的真正形态
  • 在用struct定义类时,所有成员的默认属性为public
  • Union 默认所有的属性为public 而且不可修改。
  • 在用class定义类时,所有成员的默认属性为private

C++中的构造和析构专题讲座

构造和析构基础

在函数内声明了某个类的时候,会自动调用该类的空参数构造函数,该参数没有显示写明返回值,在函数出栈以后,系统会自动调用该类的析构函数,我们可以在析构函数里面释放内存。

class Test{
public: 

    //无参数构造函数
    Test(){
        p = (char*)malloc(100);
        strcpy(p,"abc");
        cout<<"我是构造函数,自动被调用了"<<endl;
    }

    ~Test(){
        cout<<"我是析构函数,自动被调用了"<<endl;
        if(p != NULL){
            free(p);
        }
    }
   
private:
    int a;
    char * p ;
    
};
//演示类的生命周期
void ObjectPlay(){
    Test t1,t2;
    cout<<"展示t1.t2的生命周期"<<endl;
}

int main(){
    ObjectPlay();    
    return 0;
    /*
    我是构造函数,自动被调用了
    我是构造函数,自动被调用了
    展示t1.t2的生命周期
    我是析构函数,自动被调用了
    我是析构函数,自动被调用了
    */
}
类的构造函数三种形式

以下是多参数和单参数的对比:

class Test{
public: 
    //无参数构造函数
    Test(){}

    //带参数的构造函数
    Test(int mya){
        a = mya;
    }

    //复制构造函数
    Test(const Test & obj){
        ;
    }
private:
    int a;
    int b;
};
int main(){
    //三种实例化方法
    Test t1(1);//c++默认调用有参构造函数 自动调用
    Test t2 = 2;//c++默认调用有参构造函数 自动调用
    Test t3 = Test(3);//我们程序员手动调用构造函数
    return 0;
}

class Test
{
public:
    Test() //无参构造函数 默认构造函数
    {
        p = (char *)malloc(100);
        strcpy(p, "11111");
        cout<<"我是构造函数,自动被调用了"<<endl;
    }

    Test(int _a=0, int _b=0) //无参构造函数 默认构造函数
    {
        p = (char *)malloc(100);
        strcpy(p, "11111");
        a = _a;
        b = _b;
        cout<<"我是构造函数,自动被调用了"<<endl;
    }


    ~Test()
    {
        cout<<"我是析构函数,自动被调用了"<<endl;
        if (p != NULL)
        {
            free(p);
        }

    }
protected:
private:
    int a;
    int b;
    char *p ;
};
//单独搭建一个舞台
void ObjPlay()
{
    Test t1(1, 2);
    Test t2 = (1, 2);
    Test t3 = Test(3, 4);
    cout<<"展示类的生命周期"<<endl;
}

int main(){
    ObjPlay();
    /*
    我是构造函数,自动被调用了
    我是构造函数,自动被调用了
    我是构造函数,自动被调用了
    展示类的生命周期
    我是析构函数,自动被调用了
    我是析构函数,自动被调用了
    我是析构函数,自动被调用了
    */
    return 0;
}

拷贝构造函数的四种情景

一般情况有四种选择来调用复制构造函数:

  • 情景一:AA a2 = a1 即用 = 赋值等于符号
  • 情景二: AA a2(a1) 用另外一个类变量作为参数获得新的函数
class AA{
public:
    AA(){
        cout<<"我是构造函数AA(),自动被调用了"<<endl;
    }
    AA(int _a){
        a = _a;
        cout<<"我是构造函数AA(int _a),自动被调用了"<<endl;
    }
    AA(const AA &obj2){
        a = obj2.a + 10;
        cout<<"我也是构造函数,我是通过另外一个对象obj2,来初始化我自己"<<endl;
    }

    ~AA(){
        cout<<"我是析构函数,自动被调用了"<<endl;
    }
    void getA(){
        printf("a :  %d \n",a);
    }
private:
    int a;
};

void ObjPlay01(){
    //定义一个变量
    AA a1(10);

    //赋值构造函数的第一个应用场景
    //我用对象1 初始化 对象2 
    AA a2 = a1; //定义变量并初始化
    a1.getA();
    a2.getA();
    printf("__________ \n");
    a2 = a1;//用a1来=号给a2 编译器给我们提供的浅copy
    a1.getA();
    a2.getA();
    /*
    我是构造函数AA(int _a),自动被调用了
    我也是构造函数,我是通过另外一个对象obj2,来初始化我自己
    a :  10 
    a :  20 
    __________ 
    a :  10 
    a :  10 
    我是析构函数,自动被调用了
    我是析构函数,自动被调用了
    */
}
//单独搭建一个舞台
void ObjPlay02()
{
    AA a1(10); //变量定义

    //赋值构造函数的第一个应用场景
    //我用对象1 初始化 对象2 
    AA a2(a1); //定义变量并初始化

    //a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
    a2.getA();
    /*
    我是构造函数AA(int _a),自动被调用了
    我也是构造函数,我是通过另外一个对象obj2,来初始化我自己
    a :  20 
    我是析构函数,自动被调用了
    我是析构函数,自动被调用了
    */
}


int main(){
    //ObjPlay01();
    ObjPlay02();
    return 0;
}

  • 情景三:Location(const Location & p)该函数会在被当做实参的时候被调用,拷贝一份到执行函数里面,不影响原函数
    注意:如果你写了copy构造函数,那么编译器不会在提供无参构造函数;如果你写了有参或者无参构造函数,那么编译器也不会提供无参构造函数
class Location{
    public:
    Location(){}
    Location(int _x ,int _y){
        x = _x;
        y = _x;
        cout<<"构造函数"<<endl;
    }
    Location(const Location & p){
        x = p.x;
        y = p.y;
        cout<<"复制构造函数"<<endl;
    }
    int getX(){
        return x;
    }
    int getY(){
        return y;
    }

private:
    int x;
    int y;
};

void f(Location p){
    cout << "Funtion:" << p.getX() << "," << p.getY() << endl ;
}

void objPlaying(){
    Location A(1,2);
    cout << "------上面执行的是构造函数,下面是复制构造函数------" << endl ;
    f(A);
}

int main(){
    objPlaying();
    /*
    构造函数
    ------上面执行的是构造函数,下面是复制构造函数------
    复制构造函数
    Funtion:1,1
    */
    return 0;
}

  • 情景四:函数返回值为类,这个时候函数销毁后,类是否被析构,分两种情况。

类结构和原来一致,不再复制黏贴,以下是第四种情况的代码以及分析

Location g(){
    Location A(1,2);
    return A;
}

void objPlaying2(){
    //第一种情况:先声明,再用函数接过来
    /*
    Location A;
    A = g();
    cout << "------这个时候已经执行了一次析构函数,将函数内的形参析构------" << endl ;
    
    打印结果:
    构造函数
    析构函数
    ------这个时候已经执行了一次析构函数,将函数内的形参析构------
    析构函数
    */

    //第二种情况,直接将返回值接过来
    //如果返回的匿名对象,来初始化另外一个同类型的类对象,那么匿名对象会直接转成新的对象。
    //匿名对象的去和留,关键看,返回时如何接过来。
    Location B =  g();
    cout << "------此时还未执行析构函数,会将函数自己接过来------" << endl ;

    /*
    构造函数
    ------此时还未执行析构函数,会将函数自己接过来------
    析构函数
    */
    
}

int main(){
    objPlaying2();
    
    return 0;
}


多个对象初始化问题的研究

  此处主要是分析了,如果一个对象里面含有另外一个对象,那么另外一个对象要查看是否使用默认构造函数,如果没有使用默认的无参构造函数的时候,就要在类对象的初始化列表里面进行对另外一个对象的初始化。如:B():A(10),A(20)
  成员变量的初始化顺序与声明的顺序(属性列表顺序)相关,与在初始化列表中的顺序无关

使用初始化列表出现原因:

  1. 如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错。

  2. 类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值.当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。

class A{
public:
    A(int _a){
        a = _a;
    }
    void printA(){
        printf("A a:%d \n",a);
    }
private: 
    int a;
};
//构造函数的初始化列表产生原因
class B{
public: 
    B():mya(12),mya2(13){
        ;
    }
    B(int x,int y):mya(y),mya2(100){
        b = x;
    }
    
private: 
    int b;
    A mya;
    A mya2;
};

int main(){
 
    A a1(10);
    B b1(10,20);
    
    return 0;
}

构造和析构的调用顺序(成员属性里面包含了另外一个类的时候)

class ABCD 
{
public:
    ABCD(int a, int b, int c)
    {
        this->a = a;
        this->b = b;
        this->c = c;
        printf("ABCD() construct, a:%d,b:%d,c:%d  \n", this->a, this->b, this->c);
    }
    ~ABCD()
    {
        printf("~ABCD() construct,a:%d,b:%d,c:%d  \n", this->a, this->b, this->c);
    }
    int getA() 
    {
        return this->a;
    }
protected:
private:
    int a;
    int b;
    int c;
};
class MyE
{
public:
    MyE():abcd1(1,2,3),abcd2(4,5,6),m(100)
    {
        cout<<"MyE()"<<endl;
    }
    ~MyE()
    {
        cout<<"~MyE()"<<endl;
    }
    MyE(const MyE & obj):abcd1(7,8,9),abcd2(10,11,12),m(100)
    {
        printf("MyE(const MyE & obj)\n");
    }

protected:
    //private:
public:
    ABCD abcd1; //c++编译器不知道如何构造abc1
    ABCD abcd2;
    const int m;

};

int doThing(MyE mye){
    printf("doThing() mye1.abc1.a:%d \n", mye.abcd1.getA()); 
    return 0;
}

int run(){
    MyE mye;
    doThing(mye);

}


int main(){
    run();
    return 0;
}

调试代码路上,下面我们对打印结果进行分析:

ABCD() construct, a:1,b:2,c:3  成员列表中的第一个属性初始化
ABCD() construct, a:4,b:5,c:6  成员列表中的第二个属性初始化
MyE()                          调用类本身的构造函数
ABCD() construct, a:7,b:8,c:9  预备调用doThing函数,类变量作为形参传递,调用复制构造函数前,第一个属性初始化
ABCD() construct, a:10,b:11,c:12  预备调用doThing函数,类变量作为形参传递,调用复制构造函数前,第二个属性初始化
MyE(const MyE & obj) 正式调用复制构造
doThing() mye1.abc1.a:7 正式调用doThing函数
~MyE() doThing函数结束,销毁构造函数的MyE
~ABCD() construct,a:10,b:11,c:12  doThing函数结束,销毁构造函数的MyE,销毁第二个属性
~ABCD() construct,a:7,b:8,c:9  doThing函数结束,销毁构造函数的MyE,销毁第一个属性
~MyE() run函数结束,
~ABCD() construct,a:4,b:5,c:6  run函数结束,销毁构造函数的MyE,销毁第二个属性
~ABCD() construct,a:1,b:2,c:3  run函数结束,销毁构造函数的MyE,销毁第一个属性

New Delete基础专题

  1. new delete 操作符号是c++的关键字,相当于C语言中的malloc和free函数

  2. new 在堆上分配内存;delete 分配基础类型 、分配数组类型、分配对象

基础类型,数组,类的使用方法

基础类型的使用:

int main(){
    int *p = (int*)malloc(sizeof(int));
    free(p);

    int *p2 = new int;//和上面的函数相等
    *p2 = 101;
    printf("*p: %d",*p2);
    delete p2;
//分配内存的同时,初始化
    int *p3 = new int(100);
    delete p3;

    return 0;
}

new数组类型的使用:

//new 数组
int main(){
  int *p = (int* )malloc(10*sizeof(int));//int a[10];
  p[0] = 1;
  free(p);

  int *p2 = new int[10];
   p2[0] = 1;
   p2[1] = 2;
   delete [] p2;
   return 0;
} 

类的使用:


class Test{
public: 
    Test(int _a ,int _b){
        a = _a;
        b = _b;
        cout<<"构造函数,我被调用了"<<endl;
    }
    ~Test(){
        cout<<"析构函数,我被调用了"<<endl;
    }

    int getA(){
        return a;
    }
private:
    int a;
    int b;
};

int getTestObj(Test **myp){
    Test*p = new Test(1,2);
    *myp = p;
    return 0;
}

int main(){
    // 分配内存的两种方式
    // 第一种:在栈区分配内存
    // Test t1(1,2);

    //第二种:在堆区分配内存:使用new关键字会返回一个内存的首地址
    //new 操作符会自动调用该类的构造函数
    //delete 自动调用析构函数
    // 相当于我们程序员可以手工控制类的对象的生命周期
    Test *p = new Test(1,2);
    cout<<p->getA()<<endl;
    delete p;
    Test*p2 = NULL;
    cout<<"------"<<endl;
    getTestObj(&p2);
    delete p2;
    {
        //malloc不会调用这个类的构造函数
        Test*p3 = (Test *)malloc(sizeof(Test)); 
        delete p3;

    }
    return 0;
}

static关键字专题

static关键字其实也算是比较常见的,在其他语言中,我们把定义了static的函数称为类函数,static的变量称为类成员变量。static所标识的函数和成员变量和类是否实例化无关。我们最常见的设计模式单例模式(保持实例只有一份),工厂模式(简化获取实例的代码,类方法调用获得实例)都经常应用该关键字。

class Test{
public: 
    Test(int _a ,int _b){
        a = _a;
        b = _b;
        cout<<"构造函数,我被调用了"<<endl;
    }
    ~Test(){
        cout<<"析构函数,我被调用了"<<endl;
    }

    static void getC(){
        c++;
        cout<<c<<endl;
    }

    //在类的静态数据成员函数中,是不能调用具体的对象的变量的属性
    static void getMem(){
        // cout<<a<<endl;
    }
private:
    int a;
    int b;  
    // static修饰的变量,是属于类,,所有的对象都能共享用。
    static int c;
};

int Test::c = 10;//err
int main(){
  
    Test t1(1,2);
    Test t2(2,3);
    t1.getC();
    t2.getC();
    return 0;
}

cplusplus对象管理模型初探

这部分的内容之前有讲过,主要是三个int变量占的内存空间是12 ,但我们在尝试加多一个static关键和两个成员函数以后,我们的大小依然为12,由此可以引发我们的思考--static所标记的成员变量和函数并没有直接和类对象模型绑定在一起,而是有着自己独立的区域。

C++类对象中的成员变量和成员函数是分开存储的
成员变量:
普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式
静态成员变量:存储于全局数据区中
成员函数:存储于代码段中。

问题出来了:很多对象公用一块代码?代码是如何区分具体对象的那?

换句话说:int getK() const { return k; },代码是如何区分,具体obj1、obj2、obj3对象的k值?

C++编译器对普通成员函数的内部处理
总结

  1. C++类对象中的成员变量和成员函数是分开存储的。C语言中的内存四区模型仍然有效!
    2.C++中类的普通成员函数都隐式包含一个指向当前对象的this指针。
    3.静态成员函数、成员变量属于类
    静态成员函数与普通成员函数的区别
    静态成员函数不包含指向具体对象的指针
    普通成员函数包含一个指向具体对象的指针
class A{
public: 
    int i;//4
    int j;//4
    int k;//4
};//12

class B{
public: 
    int i;//4
    int j;//4
    int k;//4
    static int m;//4
public:
    int getK() const { return k; } //4
    void setK(int val) { k = val; } //4
};//12

int main(){
    printf("sizeof(A):%lu \n",sizeof(A));
    printf("sizeof(B):%lu \n",sizeof(B));
    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,447评论 1 51
  • 第一章 计算机与C++编程简介 C++程序6个阶段编程 ->预处理->编译->连接->装入->执行1.程序在编译器...
    rogertan30阅读 3,488评论 0 1
  • 重新系统学习下C++;但是还是少了好多知识点;socket;unix;stl;boost等; C++ 教程 | 菜...
    kakukeme阅读 19,422评论 0 50
  • 注:这是第三遍读《C语言深度解剖》,想想好像自从大学开始就没读完过几本书,其中谭浩强的那本《C语言程序设计(第四版...
    HavenXie阅读 1,667评论 1 6
  • 还有最近比较忙。有点顾不上。如果有我有什么做得不好,记得及时跟我说。也发生了许多意外状况,相信各位也有。 ...
    泽龙Shawn阅读 127评论 0 0