C++ 学习笔记 (一)

2023-08-04

C++标准化组织
  https://isocpp.org/std/status
  http://open-std.org/JTC1/SC22/WG21/

why C++王者归来?

  https://coolshell.cn/articles/6548.html


方法论

学任何知识点,都可以从三个方面来考虑?
> 是什么? what
> 怎么实现的? how
> 为什么? why

> 学习的深度

> 讲出来
> 听得懂


内存泄漏
野指针
内存踩踏


malloc用法
malloc底层怎么实现的? ==> 系统调用
为什么?


常量和宏的区别

#include <iostream>
using std::cout;
using std::endl;

//编辑 --> 预处理(预编译) --> 编译 --> 汇编 --> 链接 --> 可执行程序
//      -E        -S      as   -o
//
//概念 ==》沟通
//
//怎么表示学会了一个知识点?
// 完成任务 --> 讲出来 --> 别人还能够听的懂
//
//
//宏定义与const的区别?(概念题是最容易丢分)
//1. 发生时机不一样: 宏定义发生在预处理时,const关键字发生编译时

//2. 宏定义仅仅只做了字符串的替换,没有类型检查; const关键字有类型

检查,可以提前发现错误

//3. const关键字更推荐使用; 因为使用const关键字可以减小犯错误的概率

#define NUMBER 1024

void test0()
{
  cout << "NUMBER = " << NUMBER << endl;
}

void test1()
{
  const int number = 1;
  cout << ">> number = " << number << endl;

  //const int number2;//error, 必须要进行初始化
}

//常量指针 指针常量
//
// int(*)[5] int *[5]
//数组指针 指针数组
//
//int(*p)() int* p()
//函数指针 指针函数

void test2()
{
  int number = 10;
  int number2 = 100;
  int * p1 = &number;
  cout << "*p1 = " << *p1 << endl;
  cout << "&p1 = " << &p1 << endl;

  const int * p2 = &number;//常量指针(pointer to const)
  cout << "*p2 = " << *p2 << endl;
  cout << "&p2 = " << &p2 << endl;
  //*p2 = 100;//error 不能通过该指针修改所指变量的值
  p2 = &number2;//可以改变其指向
  cout << "*p2 = " << *p2 << endl;

  int const * p3 = &number;
  //*p3 = 1000;//error
  p3 = &number2;//可以改变其指向

  int * const p4 = &number;//指针常量(const pointer)
  *p4 = 1000;//可以通过该指针改变所指变量的值
  //p4 = &number2;//error 不能改变指向

  //两者都不能修改
  const int * const p5 = &number;
}

int main(void)
{
  //test0();
  //test1();
  test2();

  return 0;
}


Hello.cc

#include <stdio.h> //c标准头文件带有.h后缀
#include <iostream> //C++标准头文件没有后缀.h
//是用模板实现的, 必须要知道其实现

using namespace std;//命名空间

int main(int argc, char ** argv)
{
  printf("hello,world!\n");//标准库函数
  cout << "hello,world!" << endl;//cout 对象, 输出流运算符
  // operator<<(cout, "hello,world!");
  return 0;
}


命名空间namespace

#include <iostream>
using namespace std;//using编译指令, 它会一次性把std空间中
//的所有实体全部引进来
//
//要求:熟悉空间中的实体
//目前来说,不推荐使用

void wd_display();
void tls_display();

void cout()
{

}

namespace wd
{
  void display()
  {
    cout << "wd::display() " << endl;
  }

}//end of namespace wd

namespace tls
{

  void display()
  {
    cout << "tls::display() " << endl;
  }

}//end of namespace tls

int main(void)
{
  wd::display();//:: 作用域限定符, 这是完整形式
  tls::display();

  return 0;
}

namespace2

#include <iostream>

using std::cout;// using声明机制, 简化操作, 不会把所有的实体引进来
using std::endl;

void wd_display();
void tls_display();

struct Example
{
  int x;
  int y;

};

//命名空间在一个文件之中可以出现多次
//相当于一个黑洞
namespace wd
{
  int number = 10;
  void show();//声明
}//end of namespace wd

namespace tls
{

  void display()
  {
    cout << "tls::display() " << endl;
    wd::show();
  }

}//end of namespace tls

namespace wd
{
  void display()
  {
    cout << "wd::display() " << endl;
    tls::display();
  }

  void show()//实现
  {
    cout << "wd::show()" << endl;
    //wd::display();//加上就会发生递归调用:
  }

}//end of namespace wd

int main(void)
{
  wd::display();//:: 作用域限定符
  tls::display();

  cout << "wd::number = " << wd::number << endl;

  return 0;
}

namespace3

//代码风格的限定 ==> code review 代码走查

//#include "myhead.h" //自定义头文件放在C头文件的上面

#include <stdio.h> //头文件的顺序: C的头文件放在C++头文件的前面
#include <stdlib.h>

#include <iostream>

using std::cout;//选择区域
using std::endl;

int number = 10;

namespace wd
{
  int number = 100;

  namespace lwh
  {

    void display()
    {
      cout << "wd::lwh::display()" << endl;
    }
  }//end of namespace lwh

}//end of namespace wd

namespace tls
{
  int number = 1000;

  void display(int number)//形参number会屏蔽其他的number
  {
    cout << "number = " << number << endl;
    cout << "wd::number = " << wd::number << endl;
    cout << "tls::number = " << tls::number << endl;
    cout << "全局变量 number = " << ::number << endl;//匿名命名空间
  }

}//end of namespace tls

int test0(void)
{
  using wd::lwh::display;
  display();
  return 0;
}

int main(void)
{
  //display();
  test0();
  return 0;
}


new / delete malloc / free

#include <stdlib.h>
#include <string.h>
#include <iostream>
using std::cout;
using std::endl;

//malloc/free与new/delete表达式的区别?
//相同点: 都是用来申请堆空间
//
//不同点:
// 1. malloc/free是库函数; new/delete是表达式
// 2. malloc开空间时,并不会进行初始化;new表达式是可以进行初始化

void test0()
{
  int * p0;

  int * p1 = (int *)malloc(sizeof(int));//系统调用
  ::memset(p1, 0, sizeof(int));
  //bzero();
  *p1 = 10;
  printf("*p = %d\n", *p1);

  free(p1);

  int * p2 = (int *)malloc(sizeof(int) * 10);

  free(p2);
}

void test1()
{
  int * p1 = new int(1);
  cout << "*p1 = " << *p1 << endl;

  delete p1;

  int * p2 = new int[10]();//对于数组的申请需要加上[]
  //加上小括号是有初始化
  //不加小括号是不会进行初始化
  delete [] p2;
}

int main(void)
{
  test0();
  test1();
  return 0;
}


函数重载

#include <stdio.h>
#include <iostream>
using std::cout;
using std::endl;

//C++语言支持函数重载
//实现原理: 名字改编(name mangling)
//具体步骤: 当函数名称相同时,会根据
//函数参数的类型、个数、顺序进行改编

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

int add(int x, int y, int z)
{
  return x + y + z;
}

double add(double x, double y)
{
  return x + y;
}

double add(int x, double y)
{
  return x + y;
}

double add(double x, int y)
{
  return x + y;
}

int main(void)
{
  int a = 3, b = 4, c = 5;
  double d1 = 1.1, d2 = 2.2;
  cout << "add(a, b) = " << add(a, b) << endl;
  cout << "add(a, b, c) = " << add(a, b, c) << endl;
  cout << "add(d1, d2) = " << add(d1, d2) << endl;

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

  return 0;
}


指针和引用

#include <iostream>
using std::cout;
using std::endl;

//引用于指针的区别?

//相同点: 引用底层的实现还是指针, 引用于指针都有"地址"的概念
//
//不同点:
// 1. 引用是一个变量的别名,必须要进行初始化
// 指针是一个独立的实体,可以不进行初始化
// 2. 引用不是一个独立的实体
// 3. 引用一经绑定之后,就不会就再改变其指向;
// 指针是很灵活的,可以改变指向

void test0()
{
  int number = 1;
  //引用是变量的别名
  int & ref = number;
  cout << "ref = " << ref << endl;
  cout << "number = " << number << endl;

  ref = 10;
  cout << ">> 修改引用之后:" << endl;
  cout << "ref = " << ref << endl;
  cout << "number = " << number << endl;
  cout << "&ref = " << &ref << endl
    << "&number = " << &number << endl;

  //int & ref2;//error 引用不能单独存在,必须要绑定到一个实体
  //引用必须要进行初始化
}

//值传递 ==> 复制
#if 0
void swap(int x, int y)
{
  int temp = x;
  x = y;
  y = temp;
}
#endif

//地址传递 ==> 值传递 ==> 复制
void swap(int * px, int * py)
{
  int temp = *px;
  *px = *py;
  *py = temp;
}

//引用作为函数的参数存在
// 引用传递 ==> 没有复制的开销,
//可以提高程序的执行效率,
//传递参数的方式更加直观,自然
void swap(int & x, int & y)
{
  int temp = x;
  x = y;
  y = temp;
}

void test1()
{
  int a = 3, b = 4;
  cout << "a = " << a << ", b = " << b << endl;
  swap(a, b);
  //swap(&a, &b);
  cout << "a = " << a << ", b = " << b << endl;
}

//引用还可以作为返回值存在

int array[5] = {1, 2, 3, 4, 5};

int func1()
{
  int number = 5;
  return number;//会进行复制
}

int & getNumber(int idx)
{
  return array[idx];//如果返回值是引用,不会进行复制
}

//不能返回一个局部变量的引用
int & func2()
{  //当函数执行完毕时,局部变量生命周期已经结束
  int number = 5;
  return number;
}

//不要轻易返回一个堆空间变量的引用
//除非已经有了内存回收的策略
int & func3()
{
  int * p3 = new int(100);
  return *p3;
}

void test2()
{
  int idx = 0;
  cout << "array[idx] = " << getNumber(idx) << endl;
  &getNumber(1);

  getNumber(idx) = 10;//左值:就是可以取地址
  cout << "array[idx] = " << getNumber(idx) << endl;

  //&func1();//error 右值(临时变量): 不可以取地址
  //func1() = 100;//error

  //int & ref = func2();
  //cout << "ref = " << ref << endl;//error

  int & ref2 = func3();
  cout << "ref2 = " << ref2 << endl;
  delete &ref2;

  int a = 3, b = 4, c = 5;

  c = a + func3() + b + c;//执行该表达式之后,就会发生内存泄漏
  cout << "c = " << c << endl;
}

int main(void)
{
  test0();
  test1();
  test2();
  return 0;
}


external C 编译

//对于C源码不希望按C++方式进行调用(不进行名字改编),
// 按C的方式进行调用
//
//
//C的代码要放在C++中运行(C与C++混合编程)

#ifdef __cplusplus //该宏只会在C++的编译器中定义
extern "C"
{ //都会用C的方式进行调用
#endif

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

#ifdef __cplusplus
}//end of extern "C"
#endif


简答题:
1. const关键字与宏定义的区别是什么?

2. 引用于指针的区别是什么?

3. malloc的底层实现是怎样的?free是怎么回收内存的?

4. new/delete与malloc/free的区别是什么?


inline函数

//inline函数的功能与带参数的宏定义效果一样

//尽量用inline函数替换带参数的宏定义

#pragma once

//inline函数可以有声明和实现,但是必须在同一文件
//inline函数不能分成头文件和实现文件
inline int add(int x, int y)
{ //一般不要放循环语句
return x + y;
}

//......


C++ 类型转换

#include <iostream>
using std::cout;
using std::endl;

void test0()
{
  double val1 = 1.11;
  int val2 = (int)val1;
  int val3 = int(val1);

  cout << "val2 = " << val2 << endl;
  cout << "val3 = " << val3 << endl;
}

void test1()
{
  double val1 = 1.11;
  int val2 = static_cast<int>(val1);
  cout << "val2 = " << val2 << endl;

  int * p1 = static_cast<int*>(malloc(sizeof(int)));
  *p1 = 10;
  free(p1);

  //const_cast/dynamic_cast/reinterpret_cast
}

int main(void)
{
  //test0();
  test1();
  return 0;
}


struct 在C++中

struct Example
{
  //struct的默认访问权限是Public
  Example(int x)
  : _ix(_iy)
  , _iy(x)
  {
  }
  void setX(int ix)
  { _ix = ix; }

  void setY(int iy)
  { _iy = iy; }

  void print()
  {
    cout << "(" << _ix << "," << _iy << ")" << endl;
  }

private:
  int _ix;
  int _iy;
};


C++中的string

#include <string.h>

#include <string>
#include <iostream>
using std::cout;
using std::endl;

void test0()
{
  const char * pstr1 = "hello";// 文字常量区, 只读的
  const char * pstr2 = "world";//C风格字符串
  //*pstr1 = 'X';//error 不能进行修改

  //空间是否足够
  char * ptmp = new char[strlen(pstr1) + strlen(pstr2) + 1]();
  strcpy(ptmp, pstr1);
  strcat(ptmp, pstr2);
  cout << "ptmp = " << ptmp << endl;

  delete [] ptmp;

  //获取字符串的长度
  //strlen ==> 时间复杂度是O(N)

}

void test1()
{
  //把C风格字符串转换成C++风格的字符串
  std::string s1 = "hello";
  std::string s2 = "world";

  //做字符串的拼接
  std::string s3 = s1 + s2;
  cout << "s3 = " << s3 << endl;
  s3 += "aaa";
  cout << "s3 = " << s3 << endl;

  //获取字符串的长度
  cout << "s3'size = " << s3.size() << endl;
  cout << "s3'length = " << s3.length() << endl;

  //把C++风格字符串转换成C风格字符串
  const char * pstr = s3.c_str();
  const char * pstr2 = s3.data();
  cout << "pstr = " << pstr << endl;
  cout << "pstr2 = " << pstr2 << endl;

  //遍历元素
  for(size_t idx = 0; idx != s3.size(); ++idx)
  {
    cout << s3[idx] << endl;//数组的下标进行访问
  }

  //&s3; 代表的是std::string类型的对象的首地址

  s3.append(3, 'j');
  cout << "s3 = " << s3 << endl;
  s3.append(s1);
  cout << "s3 = " << s3 << endl;
  s3.append("wangdao");
  cout << "s3 = " << s3 << endl;
  s3.append("shenzhen", 4);
  cout << "s3 = " << s3 << endl;

  //查找元素的用法
  size_t pos = s3.find("world");
  cout << "pos = " << pos << endl;
  std::string s4 = s3.substr(pos, 5);
  cout << "s4 = " << s4 << endl;

}

int main(void)
{
  test0();
  test1();
  return 0;
}


构造函数和析构函数

#include <iostream>
using std::cout;
using std::endl;

class Point
{
public:
  #if 1
  //当没有显式定义构造函数时,系统会自动提供一个
  //默认构造函数
  Point()
  {
    _ix = 0;
    _iy = 0;
    cout << "Point()" << endl;
  }
  #endif

#if 1
//构造函数可以重载
//
//如果显式定义了有参构造函数时,系统就
//不会再自动提供默认构造函数
//
//如果还希望通过默认构造函数创建对象,则必须
//显式提供一个默认构造函数
//
  Point(int ix, int iy)
  {
    _ix = ix;
    _iy = iy;
    cout << "Point(int,int)" << endl;
  }

  

  Point(int ix = 0, int iy = 0)
  : _ix(ix) //对于数据成员的初始化,都要放在初始化表达式之中进行
  , _iy(iy)
  {
    //_ix = ix;//赋值语句
    //_iy = iy;
    cout << "Point(int=0,int=0)" << endl;
  }

#endif

  void print()
  {
    cout << "(" << _ix
    << "," <<_iy
    << ")" << endl;
  }

// 析构函数没有返回值,函数名与类名相同,同时还需要在前面
// 加上一个波浪号,与构造函数进行区分;
// 析构函数没有形参, 析构函数只有一个
//
// 当一个对象的生命周期结束的时候,会自动调用析构函数
//

  //系统提供的析构函数
  ~Point()
  {}

#if 0
  ~Point()
  {
    cout << "~Point()" << endl;
  }
#endif

private:
  int _ix;
  int _iy;
};

int main(void)
{
  //Point pt1(1, 2);
  //pt1.print();

  Point pt2;//调用无参(默认)构造函数
  pt2.print();

  return 0;
}

#include <iostream>
using std::cout;
using std::endl;

class Point
{
public:
  Point(int ix = 0, int iy = 0)
  : _ix(ix)
  , _iy(iy)
  {
    cout << "Point(int=0,int=0)" << endl;
  }

//系统提供的复制构造函数
//问题1: 形参中的引用符号不能删除,如果删除,
//会造成复制构造函数无穷递归调用,直到栈溢出,程序崩溃
//
//问题2: 形参中的const关键字可以去掉不?
// 不可以去掉,如果去掉,当传递过来的参数是右值时,就会
// 报错
//Point(const Point rhs):
//Point(Point & rhs)
Point(const Point & rhs) //复制构造函数形式是固定的
: _ix(rhs._ix)
, _iy(rhs._iy)
{
  cout << "Point(const Point & )" << endl;
}

void print()
{
  cout << "(" << _ix
  << "," <<_iy
  << ")" << endl;
}

private:
  int _ix;
  int _iy;
};

//形参与实参都是对象(值传递),会调用复制构造函数
void func1(Point pt)
{
  cout << "pt = ";
  pt.print();
}

//函数的返回值是对象时,执行return语句
//也会调用复制构造函数;但一般情况下,会
//被编译器优化掉;如要要看到完整形式,需要
//在编译时 加上编译选项 -fno-elide-constructors
Point func2()
{
  Point pt(11, 12);
  cout << ">> pt = ";
  pt.print();
  return pt;
}

int main(void)
{
  int a = 3;
  int b = a;

  //int & ref = 1;//字面值常量, 非const引用不能绑定到右值
  const int & ref2 = 1;// const引用可以绑定到右值
  cout << "ref2 = " << ref2 << endl;

  Point pt1(1, 2);
  cout << "pt1 = ";
  pt1.print();

  //Point(const Point rhs);
  // const Point rhs = pt1;
  // Point(const Point rhs);
  // const Point rhs = pt1;
  Point pt2 = pt1;//调用复制(拷贝)构造函数
  cout << "pt2 = ";
  pt2.print();
  cout << "----" << endl << endl;

  func1(pt2);
  cout << "----" << endl << endl;

  func2();//临时对象, 右值

  Point pt3 = func2();//Point(Point & rhs);
  //非const引用无法绑定到右值
  //Point & rhs = func2();
  pt3.print();
  cout << "----" << endl << endl;
  func2().print();

  return 0;
}


程序内存区域

#include <stdio.h>
#include <string.h>

#include <iostream>
using std::cout;
using std::endl;

int a = 1;//全局静态区
char * p1;//全局变量如果没有显式进行初始化,
//系统也会设置默认值

int test0(void)
{
  int b = 2;
  const char * p2 = "hello,world";//文字常量区
  char str[] = "hello,world";//栈区
  //str = 0x1000;//str 常量

  char * p3;//野指针

  p3 = new char[20]();//堆区

  strcpy(p3, "hello,world");

  static int c = 100;//局部静态变量, 全局/静态区
  ++c;

  printf("sizeof(str) = %lu\n", sizeof(str));
  printf("sizeof(p2) = %lu\n", strlen(p2));

  printf("&a = %p\n", &a);
  printf("&b = %p\n", &b);
  printf("p1 = %p\n", p1);
  printf("&p1 = %p\n", &p1);
  printf("p2 = %p\n", p2);
  printf("&p2 = %p\n", &p2);
  printf("str = %p\n", str);
  printf("p3 = %p\n", p3);
  //*p3 = '1';//error 野指针
  printf("&p3 = %p\n", &p3);
  printf("&c = %p\n", &c);
  printf("address(hello,world) = %p\n", "hello,world");
  printf("address(test0) = %p\n", &test0);//程序代码区

  return 0;
}

int main(void)
{
  test0();
  return 0;
}


默认参数

#include <stdio.h>
#include <iostream>
using std::cout;
using std::endl;

//默认参数/缺省参数

//一旦某个参数被设置了默认值,后面的所有的参数都要设置默认值
//
//默认参数的设置只能从右到左的顺序进行
int add(int x = 0, int y = 0)
{
  return x + y;
}

int add(int x, int y, int z)
{
  return x + y + z;
}

int main(void)
{
  int a = 3, b = 4, c = 5;
  cout << "add(a) = " << add(a) << endl;
  cout << "add(a, b) = " << add(a, b) << endl;
  cout << "add(a, b, c) = " << add(a, b, c) << endl;

  return 0;
}


拷贝构造函数 构造函数析构函数 深拷贝浅拷贝

#include <string.h>
#include <iostream>
using std::cout;
using std::endl;

//代码风格是非常重要的

//如果是自定义类类型,都要大写首字母
class Computer
{
public://public成员可以在类之外访问
//public成员称为类对外的接口、功能、服务

//对成员函数采用驼峰方式进行命名
void setBrand(const char * brand)
{
  strcpy(_brand, brand);
}

void setPrice(double price)
{
  _price = price;
}

//protected://保护成员不能在类之外访问
void print()
{
  cout << "brand:" << _brand << endl
  << "price:" << _price << endl;
}

//私有成员尽量放到类的底部
private://私有的成员不能在类之外访问
  char _brand[20];//brand_ / m_brand
  double _price;
};

int main(void)
{
  int a;

  Computer pc1;
  pc1.setBrand("Thinkpad");
  pc1.setPrice(8888);
  pc1.print();

  //pc1._price = 1000;//error

  return 0;
}

//类的声明
class Computer
{//class的默认访问权限是private的
public:
  void setBrand(const char * brand);
  void setPrice(double price);
  void print();

//通过private关键字表现封装的特性
private://类对象占据的空间只与数据成员有关,
//成员函数存储在程序代码区, 不会占据对象的空间
  char _brand[20];
  double _price;
};

#include <string.h>
#include <iostream>
using std::cout;
using std::endl;

class Computer
{
public:
Computer(const char * brand, double price)
: _brand(new char[strlen(brand) + 1]()) //浅拷贝, 只传地址
, _price(price)
{
  strcpy(_brand, brand);
  cout << "Computer(const char *, double)" << endl;
}

void print()
{
  cout << " brand:" << _brand << endl
  << " price:" << _price << endl;
}

//对象销毁的过程中会自动调用析构函数
//
//问题: 执行了析构函数就是销毁了对象

#if 0
void release()
{
  delete [] _brand;
}
#endif

~Computer()
{ //析构函数的作用:是用来回收对象申请的资源
  if(_brand) {
    delete [] _brand;
    _brand = nullptr;//NULL
  }
  cout << "~Computer()" << endl;
}

private:
  char * _brand;
  double _price;
};

//Computer pc2("Xiaomi", 7500);

int test0(void)
{
  {
    Computer pc1("MateBook", 6666);
    cout << ">> pc1: " << endl;
    pc1.print();
  }

  cout << ">> pc2: " << endl;
  // pc2.print();

  //堆空间的对象的销毁,需要手动执行
  Computer * pc3 = new Computer("Thinkpad", 7777);
  cout << ">> pc3: " << endl;
  pc3->print();

  delete pc3;//不要忘了, 执行delete表达式的过程中,就会调用析构函数

  static Computer pc4("Macbook pro", 20000);
  cout << ">> pc4: " << endl;
  pc4.print();

  return 0;
}

void test1()
{
  Computer * pc3 = new Computer("Thinkpad", 7777);
  cout << ">> pc3: " << endl;
  pc3->print();
  pc3->~Computer();//该语句被执行之后,对象是没有被销毁的
  delete pc3;
  }

void test2()
{
  Computer pc1("MateBook", 6666);
  cout << ">> pc1: " << endl;
  pc1.print();
  pc1.~Computer();//析构函数可以主动调用,但不推荐这样做
  //析构函数应该让其自动执行
  //pc1.release();
}

int main(void)
{
  //test1();
  test2();
  return 0;
}

//系统提供的 已经不能满足需求
#if 0
Computer(const Computer & rhs)
: _brand(rhs._brand)//浅拷贝, 只传地址
, _price(rhs._price)
{
  cout << "Computer(const Computer&)" << endl;
}
#endif
Computer(const Computer & rhs)
: _brand(new char[strlen(rhs._brand) + 1]())
, _price(rhs._price)
{
  strcpy(_brand, rhs._brand);
  cout << "Computer(const Computer&)" << endl;
}

C++ 学习笔记 (一)的相关教程结束。