XuQi's Blog

  • 首页

  • 归档

C++编程指南

发表于 2019-05-30 更新于 2019-10-20

C++ 基础知识备忘

常用字符串函数

头文件
include<string.h>
1
2
3
4
strlen() // 实际字符串长度,不包括'\0'
strcpy(dest,src) // 复制字符串,包括'\0',如果src大于dest,crash
strncpy(dest,src,num) // 复制n个字符,不包括'\0',如果src大于num,只复制num个
const char *strstr(const char *str1, const char *str2); // 在str1中查找str2,返回找到的起始地址,如果没找到返回null

函数重载定义

C++特性,因为C++编译后的名字是带参数类型,所以相同函数名,参数不同(类型,个数,顺序)

当心隐式类型转换导致重载函数产生二义性

1
2
3
4
5
6
7
8
9
10
11
void output(int i)
{
cout << "int:" << i << endl;
}

void output(float i)
{
cout << "float" << i << endl;
}

output(0.5); //无法编译通过

全局函数和成员函数重名时,并非重载,如果调用全局函数需要::funtion

成员函数的重载,覆盖和隐藏

重载和覆盖
成员函数重载特征
  • 相同范围内(同一个类中)
  • 函数名相同
  • 参数不同
  • virtual 可有可无
  • 返回值可以不同
覆盖(override派生类覆盖基类)的特征
  • 不同范围
  • 函数名字相同
  • 参数相同
  • 基类必须有virtual
令人迷惑的隐藏
  • 如果派生类函数名和基类相同,但参数不同,无论是否有virtual 基类都被隐藏。

  • 如果派生类函数名和基类相同,参数也相同,基类没有virtual,基类被隐藏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Base 
{
public:
virtual void a(int i) {cout << "Base a";}
void b(int i) {cout << "Base b";}
void c(int i) {cout << "Base c";}
void d(int i) {cout << "Base d";}
virtual void f(int i) {cout << "Base f1";}
virtual void f(int i,int j) {cout << "Base f2";}
}

class Device:public Base
{
public:
virtual void a(int i) {cout << "Device a";}
void b(float i) {cout << "Device b";}
void c(int i) {cout << "Device c";}
void d(char *i) {cout << "Device d";}
virtual void f(int i,int j = 0) {cout << "Device f";} // 没有覆盖Base的f2,缺省不一样
}

int main()
{
Device d;
Base *bp = &d;
Device *dp = &d;

// good
bp->a(1); // call Device a
dp->a(1); // call Device a

// bad,depend on point type
bp->b(1.0); // call Base b
dp->b(1); // call Device b

// bad,depend on point type
bp->c(1); // call Base b
dp->c(1); // call Device b

// bad,depend on point type
dp->d(1); // error,can not call Base d

bp->f(1); // call Base f1
dp->f(1); // call Device f
}
参数的缺省值
  • 参数缺省只能出现在声明

  • 如果有多个参数,参数只能从后往前按个缺省

运算符重载
两种实现方式
  • 友元函数
  • 类成员函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base {
public:
Base(int i):m_value(i){};

int operator +(Base b) { // ;类成员函数实现+
return b.m_value + m_value;
}

friend int operator * (Base a,Base b); // 友元函数实现 *
int m_value;
};

int operator * (Base a,Base b)
{
return a.m_value * b.m_value;
}

coredump问题调查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <signal.h>
#include <execinfo.h>
#include <unistd.h>
#include <string.h>
#include <cstdlib>
using namespace std;

void recvSignal(int sig) {
void *buffer[1024];
int n = backtrace(buffer,1024);
char **symbols = backtrace_symbols(buffer,n);
for (int i = 0; i < n; i ++)
{
cout <<symbols[i]<<endl;
}
abort();
}
void fun1()
{
int *s = 0;
(*s) = 1;
}
int main() {
std::cout << "Hello, World!" << std::endl;
signal(SIGSEGV,recvSignal);
fun1();
while(1);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
xuqi@xuqi-VirtualBox:~$ g++ main.cpp 
xuqi@xuqi-VirtualBox:~$ ./a.out
Hello, World!
./a.out() [0x400a1a]
/lib/x86_64-linux-gnu/libc.so.6(+0x354b0) [0x7fa41205d4b0]
./a.out() [0x400ac9]
./a.out() [0x400b1a]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7fa412048830]
./a.out() [0x400919]
已放弃 (核心已转储)
xuqi@xuqi-VirtualBox:~$ addr2line -e a.out 0x400ac9 -f -i -C
fun1()
??:?

字节序列

端序 内存案例(0x44332211) 处理器家族
大端(Big Endian) 0x44 0x33 0x22 0x11 SUN SPARC/IBM PowerPC
小端(Little Endian) 0x11 0x22 0x33 0x44 Intel 80x86 系列

以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
大端Big-Endian: 低地址存放高位,如下:
高地址
---------------
buf[3] (0x78) -- 低位数据
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 低地址 高位数据
---------------
低地址

小端Little-Endian: 低地址存放低位,如下:
高地址
---------------
buf[3] (0x12) -- 高位数据
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低地址 低位数据
--------------
低地址

大端小端没有谁优谁劣,各自优势便是对方劣势:

小端模式 :强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。
大端模式 :符号位的判定固定为第一个字节,容易判断正负。

MSB:MoST Significant Bit ——- 最高有效位

LSB:Least Significant Bit ——- 最低有效位

1
2
3
4
5
6
7
8
9
10
11
12
13
bool IsBigEndian()
{
int a = 0x1234;
char b = *(char *)&a;
//通过将int强制类型转换成char单字节,通过判断起始存储位置。即等于 取b等于a的低地址部分
if( b == 0x12)
{
cout << "大端" <<endl;
return true;
}
cout <<"小端"<<endl;
return false;
}

Log输出格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <unistd.h>
#include <time.h>
#include <sys/syscall.h>

#define MY_DEBUG(fmt, args...) printf("%s %s[%d] " fmt "\n", timeString(), __FUNCTION__,__LINE__,##args)

char * timeString() {
struct timespec ts;
clock_gettime( CLOCK_REALTIME, &ts);
struct tm * timeinfo = localtime(&ts.tv_sec);
static char timeStr[20];
sprintf(timeStr, "%.2d:%.2d:%.2d.%.3ld", timeinfo->tm_mon + 1, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, ts.tv_nsec / 1000000);
return timeStr;
}
①args…

​ 第一个传给fmt, 第二个开始传给args…可为多个。

②##args
##args的意思,就是把args...中的多个参数,串连起来。
#fmt的话,就是把fmt传进来的内容以字符串形式输出

拷贝构造函数和赋值函数

1. 如果不写拷贝构造函数,编译器以位拷贝的方式,所以如果类中有指针变量,就会隐含错误。

隐含错误包括

  • 原有的内存没有被释放,造成内存泄漏。

  • 两个指针指向同一块内存,任何一方改动都会影响另一方。

  • 对象析构,数据被释放了两次。

2. 拷贝构造函数和赋值函数的区别
1
2
3
4
String a("hello");
String b("world");
String c = a; // 拷贝构造函数,最好写成String c(a);
c = b; // 调用赋值函数
3. 关键字explicit

可以禁止“单参数构造函数”被用于自动类型转换,没有explicit的话test s = 4;能编译通过

4. 派生函数应该是virtual 函数,否则派生类析构时,基类无法析构
5. 派生函数的赋值函数,需要对基类成员也做赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Derived & Derived::operate =(const Derived &other) 
{
//(1)检查自赋值
if(this == &other)
return *this;
//(2)对基类的数据成员重新赋值
Base::operate =(other); // 因为不能直接操作私有数据成员
//(3)对派生类的数据成员赋值
m_x = other.m_x;
m_y = other.m_y;
m_z = other.m_z;
//(4)返回本对象的引用
return *this;
}

模板学习之typename

模板的声明可以使用关键字class代替typename进行,这两种声明方式没有任何的区别,其意义是完全一样的

1
template<typename T> class Widget; // uses "typename"
1
template<class T> class Widget; // uses "typename"

但是typename还有另外的使用方法

1
2
3
4
5
6
7
8
9
10
11
12
template<typename C> // print 2nd element in
void print2nd(const C& container) // container;
{
 // this is not valid C++!
 if (container.size() >= 2) {
// 代码有问题
  C::const_iterator iter(container.begin()); // get iterator to 1st element
  ++iter; // move iter to 2nd element
  int value = *iter; // copy that element to an int
  std::cout << value; // print the int
 }
}

如果C::const_iterator是一个类型,则定义了一个C::const_iterator类型的指针x,而如果C::const_iterator是类型C的一个成员变量呢,如果x正好是一个全局变量呢,这个表达式就是两个变量的乘积了。此处产生歧义的原因就在于C::iterator意义不明确,它依赖于模板参数,我们将其称为嵌套依赖名字(nested dependent name),为了消除歧义,C++规定,对于嵌套依赖名字,统统不解释为类型,除非显示声明。这就是为什么说上面那段代码有问题,因为编译器不认为C::const_iterator是一个类型。那么如何显示声明C::iterator为一个类型呢,就是使用typename关键字!

1
2
3
4
5
6
7
8
9
10
11
template<typename C> // print 2nd element in
void print2nd(const C& container) // container;
{
 // this is not valid C++!
 if (container.size() >= 2) {
  typename C::const_iterator iter(container.begin()); // get iterator to 1st element
  ++iter; // move iter to 2nd element
  int value = *iter; // copy that element to an int
  std::cout << value; // print the int
 }
}

C++11 模板中的 using

C++ 11 后的类型别名或者模板别名

类型别名是指:之前定义的类型的引用(类似 typedef)

模板别名是指:模板类的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
例子:
  (1)using line_no = std::vector<string>::size_type;

    相当于:typedef vector<string>::size_type line_no;

  (2) // typedef void (*func)(int, int);
    using func = void (*) (int, int);  // the name 'func' now denotes a pointer to function

  (3)template<class CharT>

    using mystring = std::basic_string<CharT,std::char_traits<CharT>>;

    mystring<char> str;
# C++11
以太网芯片MAC和PHY的关系
C++常用新特性
  • 文章目录
  • 站点概览

XuQi

44 日志
30 标签
  1. 1. C++ 基础知识备忘
    1. 1.0.1. 常用字符串函数
      1. 1.0.1.0.1. 头文件
      2. 1.0.1.0.2. include<string.h>
  2. 1.0.2. 函数重载定义
    1. 1.0.2.1. 成员函数的重载,覆盖和隐藏
      1. 1.0.2.1.1. 重载和覆盖
        1. 1.0.2.1.1.1. 成员函数重载特征
        2. 1.0.2.1.1.2. 覆盖(override派生类覆盖基类)的特征
        3. 1.0.2.1.1.3. 令人迷惑的隐藏
        4. 1.0.2.1.1.4. 参数的缺省值
      2. 1.0.2.1.2. 运算符重载
        1. 1.0.2.1.2.1. 两种实现方式
  3. 1.0.3. coredump问题调查
  4. 1.0.4. 字节序列
  5. 1.0.5. Log输出格式化
    1. 1.0.5.0.1. ①args…
    2. 1.0.5.0.2. ②##args
  • 1.0.6. 拷贝构造函数和赋值函数
    1. 1.0.6.0.1. 1. 如果不写拷贝构造函数,编译器以位拷贝的方式,所以如果类中有指针变量,就会隐含错误。
    2. 1.0.6.0.2. 2. 拷贝构造函数和赋值函数的区别
    3. 1.0.6.0.3. 3. 关键字explicit
    4. 1.0.6.0.4. 4. 派生函数应该是virtual 函数,否则派生类析构时,基类无法析构
    5. 1.0.6.0.5. 5. 派生函数的赋值函数,需要对基类成员也做赋值
  • 1.0.7. 模板学习之typename
  • 1.0.8. C++11 模板中的 using
  • © 2019 XuQi
    由 Hexo 强力驱动 v3.9.0
    |
    主题 – NexT.Muse v7.3.0