C,C++tips大盘点 (那些年经常不注意的点)

1.关于运算符左结合与右结合

    许多操作符的优先级都是相同的时候 这时,操作符的结合性就开始发挥作用了。在表达式中如果有几个优先级相同的操作符,结合性就起仲裁的作用,由它决定哪个操作符先执行。

2.C++ explicit

作用:加在构造函数之前,防止隐式调用构造函数(话说我还是刚知道这样隐式调用构造函数)

class Circle  
{  
 public:  
     explicit Circle(double r) : R(r) {}  
     explicit Circle(int x, int y = 0) : X(x), Y(y) {}  
     explicit Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {}  
 private:  
     double R;  
     int    X;  
     int    Y;  
};  
   
int _tmain(int argc, _TCHAR* argv[])  
{  
//一下3句,都会报错  
Circle A = 1.23;   //这样调用就是隐式的调用了构造函数Circle(double r)
Circle B = 123;    //同上
Circle C = A;      //这个是拷贝构造函数
       
//只能用显示的方式调用了  
//未给拷贝构造函数加explicit之前可以这样  
      Circle A = Circle(1.23);  
      Circle B = Circle(123);  
      Circle C = A;  
 
//给拷贝构造函数加了explicit后只能这样了  
      Circle A(1.23);  
      Circle B(123);  
      Circle C(A);  
      return 0;  
 }

3.vector

数组是静态分配空间,一旦分配了空间的大小,就不可再改变了
vector是动态分配空间,随着元素的不断插入,它会按照自身的一套机制不断扩充自身的容量。

vector的扩充机制: 按照容器现在容量的一倍进行增长。vector容器分配的是一块连续的内存空间,每次容器的增长,并不是在原有连续的内存空间后再进行简单的叠加,而是重新申请一块更大的新内存,并把现有容器中的元素逐个复制过去,然后销毁旧的内存。这时原有指向旧内存空间的迭代器已经失效,所以当操作容器时,迭代器要及时更新

关于空间分配的方法
对于vector string 这两个顺序容器,包含capacity与reserve这两个方法
c.reserve(n)分配至少能容纳n个元素的空间。
c.capicity() 返回当前顺序容器的能容纳元素的大小(注意c.size()是返回已经存有多少个元素了)

vector.data() 返回元素类型的指针,指向第一个元素
int*a = it.data();

4. string

  1. string.substr(size_t pos = 0, size_t len = npos)
    返回一个string的字串从size_t开始 到len长度或者结尾
    pos
    第一个字符的位置被复制为子串。
    如果这是等于字符串的长度,该函数返回一个空字符串。
    如果这是大于字符串的长度,它会抛出out_of_range
    注意:第一个字符表示为值0(不是1)
  2. string 转char *的方式

值得注意的是转化之后其实是一个指向存储区域的指针,所以要转化以下。

data();

如:

string str=“abc”;
charp=(char)str.data();

c_str();

如:

string str=“adcd”;

char p=(char)str.c_str();

3.char*转化成string
const char *st = “hello”;
// 赋值转换
string st1 = st;

string s1(st, st + strlen(st));

4.string赋值
string s(cp,n) //s是char* cp指向的数组的前n个元素的拷贝
string s(ss,pos) //ss是string ss从pos开始的拷贝
string s(ss,pos,len) //s是string ss 从pos开始的len个长度的拷贝
5.string插入

  1. 注意一下插入位置是指向的前一个位置k
  2. 返回的是插入的第一个位置
  3. 只有使用迭代器的插入才能返回一个迭代器
  4. str.insert(6, str3, 3, 4); 从str第六个位置之后开始,插入str3[3]开始之后的四个符号。并且不返回迭代器
// inserting into a string
#include <iostream>
#include <string>

int main()
{
	std::string str = "to be question";
	std::string str2 = "the ";
	std::string str3 = "or not to be";
	std::string::iterator it;

	// used in the same order as described above:
	//我们可以看出 insert是从指定的位置后面一个位置插入,并且返回的是插入的位置
	str.insert(6, str2);     
	std::cout << str << '\n'; // to be (the )question
	str.insert(6, str3, 3, 4);             // to be (not )the question
	str.insert(10, "that is cool", 8);    // to be not (that is )the question
	str.insert(10, "to be ");            // to be not (to be )that is the question
	str.insert(15, 1, ':');               // to be not to be(:) that is the question
	it = str.insert(str.begin() + 5, ','); 
	std::cout << *it<<'\n';// to be(,) not to be: that is the question
	str.insert(str.end(), 3, '.');       // to be, not to be: that is the question(...)  
	it = str.insert(it + 2, str3.begin(), str3.begin() + 3); // (or )  注意这里的插入是从begin()位置开始到 最后一个位置之前
	std::cout << *it << '\n'; //o
	std::cout << str << '\n';
	return 0;
	
}

6.string删除
需要注意的是删除之后返回的迭代器是最后位置的下一个,emmmm或者说就是表示范围的第二个指针指向的位置

// string::erase
#include <iostream>
#include <string>

int main()
{
	std::string str("This is an example sentence.");
	std::string::iterator it;
	std::cout << str << '\n';
	// "This is an example sentence."
	str.erase(10, 8);                        //            ^^^^^^^^
	std::cout << str << '\n';
	// "This is an sentence."
	str.erase(str.begin() + 9);               //           ^
	std::cout << str << '\n';
	// "This is a sentence."
	it = str.erase(str.begin() + 5, str.end() - 9);
	std::cout << *(++it) << '\n';//       ^^^^^
	std::cout << str << '\n';
	// "This sentence."
	return 0;
}

7.string replace()
必须指定一个范围要插入的范围哈!

// replacing in a string
#include <iostream>
#include <string>

int main ()
{
  std::string base="this is a test string.";
  std::string str2="n example";
  std::string str3="sample phrase";
  std::string str4="useful.";

  // replace signatures used in the same order as described above:

  // Using positions:                 0123456789*123456789*12345
  std::string str=base;           // "this is a test string."
  str.replace(9,5,str2);          // "this is an example string." (1)
  str.replace(19,6,str3,7,6);     // "this is an example phrase." (2)
  str.replace(8,10,"just a");     // "this is just a phrase."     (3)
  str.replace(8,6,"a shorty",7);  // "this is a short phrase."    (4)
  str.replace(22,1,3,'!');        // "this is a short phrase!!!"  (5)

  // Using iterators:                                               0123456789*123456789*
  str.replace(str.begin(),str.end()-3,str3);                    // "sample phrase!!!"      (1)
  str.replace(str.begin(),str.begin()+6,"replace");             // "replace phrase!!!"     (3)
  str.replace(str.begin()+8,str.begin()+14,"is coolness",7);    // "replace is cool!!!"    (4)
  str.replace(str.begin()+12,str.end()-4,4,'o');                // "replace is cooool!!!"  (5)
  str.replace(str.begin()+11,str.end(),str4.begin(),str4.end());// "replace is useful."    (6)
  std::cout << str << '\n';
  return 0;
}

4.2cstring

有两个函数memcpy()跟memset()之前掌握的不要太好
memcpy(被拷贝的地址,要拷贝的地址,要拷贝的大小)用于拷贝变量
memset()用于重置数组之类的

/* memcpy example */
#include <stdio.h>
#include <string.h>

struct {
  char name[40];
  int age;
} person, person_copy;

int main ()
{
  char myname[] = "Pierre de Fermat";

  /* using memcpy to copy string: */
  memcpy ( person.name, myname, strlen(myname)+1 );
  person.age = 46;

  /* using memcpy to copy structure: */
  memcpy ( &person_copy, &person, sizeof(person) );

  printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );

  return 0;
}
/* memset example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] = "almost every programmer should know memset!";
  memset (str,'-',6);
  puts (str);
  return 0;
}

5 .list

之前一直没关注的一个容器,现在发现好重要,list是链表存放数据的,所以删除插入很快但是随机访问较慢,基本操作见list基本操作与测试
需要强调
1.sort函数
list的sort函数调用方式为 list.sort() 可以像vector那样重载类的<运算符或者自定义cmp函数 list.sort(cmp);

2.重载了比较运算符

3.可以合并两个list

//The contents of list1 : 2 5 9 9
//The contents of list3 : 6 6 6 6 6 6 6 6 6
list1.splice(++list1.begin(), list3); 
//第一个元素list1的迭代器  第二个元素要合并的位置
The contents of list1 : 2 6 6 6 6 6 6 6 6 6 5 9 9

6 .const函数

1.c++的一个机制,让该函数的权限为只读,也就是说它没法去改变非static成员变量的值。 const函数是可以修改static成员变量的。
2.同时,如果一个对象为const,它只有权利调用const函数,因为成员变量不能改变。

7. 构造函数,拷贝构造函数,析构函数

  1. 当类对象作为成员时:
    创建对象时,先初始化成员对象,再初始化对象本身
    (即先调用成员对象的构造函数,再执行本身的构造函数).
    删除对象时,先销毁对象本身,再销毁成员对象
    (即执行本身的析构函数,再调用成员对象的析构函数).
  2. 所以不可以显示调用构造函数,但是能显示调用析构函数
    在这里插入图片描述

3.默认的重载=与拷贝构造函数可能的错误

默认的拷贝构造函数与=运算符都是直接通过调用memcpy()直接拷贝获得的。

所以在有非静态指针的时候直接将地址拷贝了,在函数运行的时候作为参数传入但是结束之后被释放了原对象的指针也就被释放了。

所以要在有非静态指针的时候自己写一下拷贝构造函数与=运算符

matrix::matrix(const matrix& p)
{
	//	printf("\n copy constructor start");
	//	printf("\n rows = %d, cols = %d, elems = %d", p.rows, p.cols, p.elems);

	rows = p.rows;
	cols = p.cols;
	elems = new int[p.rows * p.cols];//注意这里必须重新开辟一个空间
	memcpy(elems, p.elems, sizeof(double) * p.rows * p.cols);
}

void matrix::operator = (matrix& p)
{
	//	printf("\n operator = start");

	rows = p.rows;
	cols = p.cols;
	elems = new int[p.rows * p.cols];
	memcpy(elems, p.elems, sizeof(double) * p.rows * p.cols);
}

8.文件

8.1文件流中的 rbbuf

本demo中,首先保存了cout的streambuf ——指向IO输出
之后通过
psbuf = filestr.rdbuf(); // get file’s streambuf
std::cout.rdbuf(psbuf); // assign streambuf to cout
将cout重定向到了filestr中所以输出都会到文件中
之后通过
cout.rdbuf(backup) 又将cout的方向指回了IO
用于算法测试的代码

fstream a("in.txt");
cin.rdbuf(a.rdbuf());

demo代码

 redirecting cout's output thrrough its stream buffer
#include <iostream>     // std::streambuf, std::cout
#include <fstream>      // std::ofstream

int main () {
  std::streambuf *psbuf, *backup;
  std::ofstream filestr;
  filestr.open ("test.txt");

  backup = std::cout.rdbuf();     // back up cout's streambuf

  psbuf = filestr.rdbuf();        // get file's streambuf
  std::cout.rdbuf(psbuf);         // assign streambuf to cout

  std::cout << "This is written to the file";

  std::cout.rdbuf(backup);        // restore cout's original streambuf

  filestr.close();

  return 0;
}

8.2int ungetc(int character, FILE * stream )

ungetc()可以将一个字符重新输入到文件流中,同时也可以应用于stdin;

/* ungetc example */
#include <stdio.h>

int main ()
{
  FILE * pFile;
  int c;
  char buffer [256];

  pFile = fopen ("myfile.txt","rt");
  if (pFile==NULL) perror ("Error opening file");
  else while (!feof (pFile)) {
    c=getc (pFile);
    if (c == EOF) break;
    if (c == '#') ungetc ('@',pFile);
    else ungetc (c,pFile);
    if (fgets (buffer,255,pFile) != NULL)
      fputs (buffer,stdout);
    else break;
  }
  return 0;
}

9.I/O

9.1 cin置位

当cin发生读取类型错误或者缓冲区内未读取完的情况时
goodbit:无错误
failbit:非致命的输入/输出错误,可挽回
置位
之后无法再通过cin读入,必须调用

s.clear(flags):清空状态标志位,并将给定的标志位flags置为1,返回void

将goodbit置位为1,并且cin.clear()等同于cin.clear(ios::goodbit);因为cin.clear()的默认参数是ios::goodbit,所以不需显示传递,故而你最常看到的就是:cin.clear()。

9.2 Cin清空缓冲区

istream &ignore( streamsize num=1, int delim=EOF );

cin.ignore(numeric_limits<std::streamsize>::max(),'\n'); //清除缓冲区的当前行

Thanks for

9.3 cout / cerr / clog

cout 是在终端显示器输出,cout流在内存中对应开辟了一个缓冲区,用来存放流中的数据,当向cout流插入一个endl,不论缓冲区是否漫了,都立即输出流中所有数据,然后插入一个换行符.

cerr 流对象是标准错误流,指定为和显示器关联,和cout作用差不多,cerr流中的信息只能在显示器输出,并且不经过缓冲直接输出

clog 流也是标准错误流,作用和cerr一样,区别在于cerr不经过缓冲区,直接向显示器输出信息,而clog中的信息存放在缓冲区,缓冲区满或者遇到endl时才输出.

10.重载操作符

10.1重载操作符返回

    操作符也是一个函数,在重载之后可以返回输入的变量的运算结果
   比如说 a+b 其实是调用了+(a,b) return 的就是+(int ,int)函数的结果。
这里有关于操作符是否返回引用的讲解
Thanks

10.2规定

赋值(=)、下标([])、调用(())和成员访问箭头(->)运算符必须是成员(函数)。

同一个操作符可以被重载多次,只要参数表不同(不能是:参数表相同而返回值类型不同).

[]重载用法
返回引用是因为经常需要改变所指向的值

	int& operator [] (unsigned int index){	// 重载下标操作符
		if (index >= size)
			throw IndexException("out of index exception.");
		
		return element[index];
	}

二维数组的[]重载,返回一个指针即可,这样就相当于从某个位置开始继续访问,加深对数组的理解。
参考

template <typename T>
class Matrix
{
public:
    Matrix(int row, int col)
        :m_row(row), m_col(col), m_data(nullptr)
    {
        m_data = new T[m_row * m_col];
    }
 
    ~Matrix()
    {
        if (m_data != nullptr)
        {
            delete[] m_data;
            m_data = nullptr;
        }
    }
    
     // 返回二维数组的第 k 行地址
    T * operator[](int k)
    {
        return &m_data[k * m_col];
    }
 
private:
    int m_row;
    int m_col;
    T *m_data;
};
Matrix<int> m2(3, 4);
m2[0][0] = 2;
m2[2][3] = 9;
cout << m2[0][0] << endl;          // 2
cout << m2[2][3] << endl;          // 9

10.3

11.引用

11.1引用的定义与使用需要注意:

  1. 引用定义的时必须初始化(必须指向已存在的对象)
  2. 引用定义后不能修改指向别的对象.

11.2引用作为返回值

  1. 普通非引用类型返回值的函数,系统要为每个返回值创建一个临时对象。
  2. 使用引用类型作为返回值,则系统不需为返回值创建新对象。

所以:

引用类型的函数返回值可以作为左值使用
返回局部变量栈上的引用会被释放内存
返回的是形参,并且是以引用方式传入的才不会被释放

12 函数指针

12.1基本函数指针概念

Thanks for
函数名本身就是一个指针可以通过
int add(int,int){}
int (*fp)(int, int) = add;
来赋值一个函数指针变量

12.2function类型

int add(int,int){}
function<int(int,int)> f1 = add;
可以实现这样的操作
map<string, function<int(int,int )>> binops = {{"+",add}}

13模板

13.1模板函数

  1. 模板中的函数参数是const的引用
  2. 当我们调用一个一般函数时,只要编译器掌握函数的声明即可,不需要掌握函数的定义,所以可以分.h文件里存放声明,.cpp文件里存放函数定义
    但是对于模板函数必须同时包含模板函数的声明以及定义
  3. 在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板实参

13.2模板类

  1. 一个类可以限定特定模板类的实例为它的友元
    这种情况下需要前置友元模板类或者模板函数的声明
    friend class BlobPtr< T >
  2. 可以让另一个类的每个实例都声明为自己的友元
    这种情况下不需要前置
    template< typename T> friend class Pal2;
    注意为了让所有实例都成为友元必须使用与类模板本身不同的模板参数
  3. friend T 这样Pal2< Foo > Foo就成了Pal2的友元
  4. 每个模板的实例对应一个static对象
    类内 static int a;
    在外部定义static 变量时 template< typename T> int Foo< T >:: a;

14 继承

1.派生类对基类对象的访问权

在这里插入图片描述

2.派生类对基类成员的继承性

在这里插入图片描述

3.改变继承成员的安全属性

class A{ public: 
			 int x, y; 
	         void f1();};
class B: class A{ public:
					   A::x; 	//注意:前面不要写int.
			           A::f1; }; // x,f1都为public类型

4.多继承

class C:public A, public B{/**/};

构造函数调用顺序与继承顺序相同,析构函数相反

class A{public: A(){ cout <<A().<< endl;}
		~A(){ cout <<~A().<< endl;}};
	class B{public:  B(){cout <<B().<< endl;}
		~B(){ cout <<~B().<< endl;}};
	class C: B, A{
	public: C(){ cout <<C().<< endl;}
		  ~C(){ cout <<~C().<< endl;}};
	main(){ C c; }

输出:B A C ~C ~B ~A

5.多态

  1. 派生类指针指向基类指针需要进行强制转化,基类指针指向派生类对象直接隐士转化即可。
    在这里插入图片描述

  2. 私有派生类的对象指针不能向上映射成基类指针;公有派生类的才行。 在这里插入图片描述

  3. 指向派生类的基类指针只能访问基类中声明的成员。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值