C点(三)G

前面基本总结介绍了C/C++的基础点,现在再总结下需要注意的高级点。

一.C++模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。

每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector <int>vector <string>

您可以使用模板来定义函数和类,接下来让我们一起来看看如何使用。

函数模板

模板函数定义的一般形式如下所示:

template <class type> ret-type func-name(parameter list)
{
   // 函数的主体
}  

在这里,type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。

下面是函数模板的实例,返回两个数种的最大值:

#include &lt;iostream&gt; #include &lt;string&gt; using namespace std; template &lt;typename T&gt; inline T const&amp; Max (T const&amp; a, T const&amp; b) { return a &lt; b ? b:a; } int main () { int i = 39; int j = 20; cout &lt;&lt; "Max(i, j): " &lt;&lt; Max(i, j) &lt;&lt; endl; double f1 = 13.5; double f2 = 20.7; cout &lt;&lt; "Max(f1, f2): " &lt;&lt; Max(f1, f2) &lt;&lt; endl; string s1 = "Hello"; string s2 = "World"; cout &lt;&lt; "Max(s1, s2): " &lt;&lt; Max(s1, s2) &lt;&lt; endl; return 0; }

类模板

正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:

template <class type> class class-name {
.
.
.
}

在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。

下面的实例定义了类 Stack<>,并实现了泛型方法来对元素进行入栈出栈操作:

#include &lt;iostream&gt; #include &lt;vector&gt; #include &lt;cstdlib&gt; #include &lt;string&gt; #include &lt;stdexcept&gt; using namespace std; template &lt;class T&gt; class Stack { private: vector&lt;T&gt; elems; // 元素 public: void push(T const&amp;); // 入栈 void pop(); // 出栈 T top() const; // 返回栈顶元素 bool empty() const{ // 如果为空则返回真。 return elems.empty(); } }; template &lt;class T&gt; void Stack&lt;T&gt;::push (T const&amp; elem) { // 追加传入元素的副本 elems.push_back(elem); } template &lt;class T&gt; void Stack&lt;T&gt;::pop () { if (elems.empty()) { throw out_of_range("Stack&lt;&gt;::pop(): empty stack"); } // 删除最后一个元素 elems.pop_back(); } template &lt;class T&gt; T Stack&lt;T&gt;::top () const { if (elems.empty()) { throw out_of_range("Stack&lt;&gt;::top(): empty stack"); } // 返回最后一个元素的副本 return elems.back(); } int main() { try { Stack&lt;int&gt; intStack; // int 类型的栈 Stack&lt;string&gt; stringStack; // string 类型的栈 // 操作 int 类型的栈 intStack.push(7); cout &lt;&lt; intStack.top() &lt;&lt;endl; // 操作 string 类型的栈 stringStack.push("hello"); cout &lt;&lt; stringStack.top() &lt;&lt; std::endl; stringStack.pop(); stringStack.pop(); } catch (exception const&amp; ex) { cerr &lt;&lt; "Exception: " &lt;&lt; ex.what() &lt;&lt;endl; return -1; } } 
二.多线程
//参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
        int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
三.C/C++标准库
C标准函数库

image

C++ 标准函数库

继承了C标准函数库及做了一些线程上的修改

C++标准类库

STL,输入输出等,重点就是STL。

C点(一)C++基础点

基于C的基础上再补充C++的一些基础要点。

一.数组指针

#include &lt;iostream&gt; using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; for (int i = 0; i &lt; MAX; i++) { *var = i; // 这是正确的语法 *(var + i) = 20;//正确 var++; // 这是不正确的 } return 0; } 

二.引用 vs 指针
区别:

引用很容易与指针混淆,它们之间有三个主要的不同:

  • 不存在空引用。引用必须连接到一块合法的内存。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。
引用的使用:

作为形参使用:更加安全高效,不需要内存复制操作。

作为返回值:被引用的对象不能超出作用域。

int&amp; func() { int q; //! return q; // 在编译时发生错误 static int x; return x; // 安全,x 在函数作用域外依然是有效的 }

三.类对象-封装
拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象。

  • 复制对象把它作为参数传递给函数。

  • 复制对象,并从函数返回这个对象。

如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:

#include &lt;iostream&gt; using namespace std; class Box { double width; public: friend void printWidth( Box box ); void setWidth( double wid ); }; // 成员函数定义 void Box::setWidth( double wid ) { width = wid; } // 请注意:printWidth() 不是任何类的成员函数 void printWidth( Box box ) { /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */ cout &lt;&lt; "Width of box : " &lt;&lt; box.width &lt;&lt;endl; } // 程序的主函数 int main( ) { Box box; // 使用成员函数设置宽度 box.setWidth(10.0); // 使用友元函数输出宽度 printWidth( box ); return 0; }
内联函数

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。

类静态成员与静态函数

我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。

如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。

静态成员函数只能访问静态数据成员,不能访问其他静态成员函数和类外部的其他函数。

静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建.

#include <iostream> using namespace std; class Box { public: static int objectCount; // 构造函数定义 Box(double l=2.0, double b=2.0, double h=2.0) { cout <<"Constructor called." << endl; length = l; breadth = b; height = h; // 每次创建对象时增加 1 objectCount++; } double Volume() { return length * breadth * height; } static int getCount() { return objectCount; } private: double length; // 长度 double breadth; // 宽度 double height; // 高度 }; // 初始化类 Box 的静态成员 int Box::objectCount = 0; int main(void) { // 在创建对象之前输出对象的总数 cout << "Inital Stage Count: " << Box::getCount() << endl; Box Box1(3.3, 1.2, 1.5); // 声明 box1 Box Box2(8.5, 6.0, 2.0); // 声明 box2 // 在创建对象之后输出对象的总数 cout << "Final Stage Count: " << Box::getCount() << endl; return 0; }

四.继承+多态

继承:is-a

多态:以虚函数实现,进行面向对象的抽象。同时通过纯虚函数,实现接口设计编程。

五.异常处理

C++ 标准的异常

C++ 提供了一系列标准的异常,定义在 <exception> 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:

C++ 异常的层次结构

下表是对上面层次结构中出现的每个异常的说明:

image

定义新的异常

可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常

#include &lt;iostream&gt; #include &lt;exception&gt; using namespace std; struct MyException : public exception { const char * what () const throw () { return "C++ Exception"; } }; int main() { try { throw MyException(); } catch(MyException&amp; e) { std::cout &lt;&lt; "MyException caught" &lt;&lt; std::endl; std::cout &lt;&lt; e.what() &lt;&lt; std::endl; } catch(std::exception&amp; e) { //其他的错误 } }

抛出异常+捕获异常

throw:

double division(int a, int b) { if( b == 0 ) { throw "Division by zero condition!"; } return (a/b); }

try…catch…

try { // 保护代码 }catch( ExceptionName e1 ) { // catch 块 }catch( ExceptionName e2 ) { //任何异常,作为保护代码使用 }catch(... ) { // catch 块 }

六.内存管理

new 可以对内存进行初始化操作即有构造函数,malloc只申请分配内存。

七.预处理*宏

# 和 ## 运算符

# 和 ## 预处理运算符在 C++ 和 ANSI/ISO C 中都是可用的。# 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。

## 运算符用于连接两个令牌。

#include <iostream>
using namespace std;

#define concat(a, b) a ## b
int main()
{
   int xy = 100;
   
   cout << concat(x, y);
   return 0;
}

c点(一)C基础点

记录下C编程的一些基础注意点。常量,全局变量,局部变量,形参变量,作用域,运算符,修饰符。

编译,环境

一.常量

使用 #define 预处理器。:

#include &lt;stdio.h&gt; #define LENGTH 10 #define WIDTH 5 #define NEWLINE '\n' 

使用 const 关键字。:

const int LENGTH = 10; const int WIDTH = 5; const char NEWLINE = '\n';

二:变量+作用域

任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。C 语言中有三个地方可以声明变量:

  1. 在函数或块内部的局部变量
  2. 在所有函数外部的全局变量
  3. 形式参数的函数参数定义中

让我们来看看什么是局部变量、全局变量和形式参数。

局部变量

在某个函数或块的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的。

全局变量

全局变量是定义在函数外部,通常是在程序的顶部。全局变量在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。

全局变量可以被任何函数访问。也就是说,全局变量在声明后整个程序中都是可用的。

形式参数

函数的参数,形式参数,被当作该函数内的局部变量,它们会优先覆盖全局变量。

初始化局部变量和全局变量

当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动对其初始化。

image

#include &lt;stdio.h&gt; /* 全局变量声明 */ int a = 20; int main () { /* 在主函数中的局部变量声明 */ int a = 10; int b = 20; int c = 0; int sum(int, int); printf ("value of a in main() = %d\n", a); c = sum( a, b); printf ("value of c in main() = %d\n", c); return 0; } /* 添加两个整数的函数 */ int sum(int a, int b) { printf ("value of a in sum() = %d\n", a); printf ("value of b in sum() = %d\n", b); return a + b; }
三.存储类
auto 存储类:

auto 存储类是所有局部变量默认的存储类。

register 存储类

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。

static 存储类

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。

static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。

在 C 编程中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。

extern 存储类

extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 ‘extern’ 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。

四.运算符

位运算符

位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:

image

假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:

A = 0011 1100

B = 0000 1101

—————–

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A  = 1100 0011

下表显示了 C 语言支持的位运算符。假设变量 A 的值为 60,变量 B 的值为 13,则:

image

五.形参

image

引用:

/* 函数定义 */ void swap(int &amp;x, int &amp;y) { int temp; temp = x; /* 保存地址 x 的值 */ x = y; /* 把 y 赋值给 x */ y = temp; /* 把 temp 赋值给 y */ return; }

指针:

/* 函数定义 */ void swap(int *x, int *y) { int temp; temp = *x; /* 保存地址 x 的值 */ *x = *y; /* 把 y 赋值给 x */ *y = temp; /* 把 temp 赋值给 y */ return; }

六.指针+数组
数组:

#include &lt;stdio.h&gt; /* 函数声明 */ double getAverage(int arr[], int size); int main () { /* 带有 5 个元素的整型数组 */ int balance[5] = {1000, 2, 3, 17, 50}; double avg; /* 传递一个指向数组的指针作为参数 */ avg = getAverage( balance, 5 ) ; /* 输出返回值 */ printf( "平均值是: %f ", avg ); return 0; }

数组指针:

数组名是一个指向数组中第一个元素的常量指针。因此,在下面的声明中:

double balance[50];

balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。因此,下面的程序片段把 p 赋值为 balance 的第一个元素的地址:

double *p;
double balance[10];

p = balance;

使用数组名作为常量指针是合法的,反之亦然。因此,*(balance + 4) 是一种访问 balance[4] 数据的合法方式。

一旦您把第一个元素的地址存储在 p 中,您就可以使用 *p、*(p+1)、*(p+2) 等来访问数组元素。

指针的指针

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

C 中指向指针的指针

七.字符串
字符串

在 C 语言中,字符串实际上是使用 null 字符 ‘\0′ 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。

下面的声明和初始化创建了一个 “Hello” 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 “Hello” 的字符数多一个。

char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

依据数组初始化规则,您可以把上面的语句写成以下语句:

char greeting[] = "Hello";

以下是 C/C++ 中定义的字符串的内存表示:

C/C++ 中的字符串表示

其实,您不需要把 null 字符放在字符串常量的末尾。C 编译器会在初始化数组时,自动把 ‘\0′ 放在字符串的末尾。

操作字符串的函数

image

八.可变参数

有时,您可能会碰到这样的情况,您希望函数带有可变数量的参数,而不是预定义数量的参数。C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数。下面的实例演示了这种函数的定义。

int func(int, ... ) 
{
   .
   .
   .
}

int main()
{
   func(1, 2, 3);
   func(1, 2, 3, 4);
}

请注意,函数 func() 最后一个参数写成省略号,即三个点号(),省略号之前的那个参数总是 int,代表了要传递的可变参数的总数。为了使用这个功能,您需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。具体步骤如下:

  • 定义一个函数,最后一个参数为省略号,省略号前面的那个参数总是 int,表示了参数的个数。
  • 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
  • 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
  • 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
  • 使用宏 va_end 来清理赋予 va_list 变量的内存。

现在让我们按照上面的步骤,来编写一个带有可变数量参数的函数,并返回它们的平均值:

#include <stdio.h>
#include <stdarg.h>

double average(int num,...)
{

    va_list valist;
    double sum = 0.0;
    int i;

    /* 为 num 个参数初始化 valist */
    va_start(valist, num);

    /* 访问所有赋给 valist 的参数 */
    for (i = 0; i < num; i++)
    {
       sum += va_arg(valist, int);
    }
    /* 清理为 valist 保留的内存 */
    va_end(valist);

    return sum/num;
}

int main()
{
   printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

当上面的代码被编译和执行时,它会产生下列结果。应该指出的是,函数 average() 被调用两次,每次第一个参数都是表示被传的可变参数的总数。省略号被用来传递可变数量的参数。

Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000
九.字符串

 

strlen():返回的长度不包括空结束字符 sizeof() 运算符

int _tmain(int argc, _TCHAR* argv[]) { char str[10] = "1234567"; const char* cstr = "1234567"; void * pvoid; int len = strlen("1234567");// len = 7; len = sizeof("1234567");// len = 8 * sizeof(char) = 8; len = sizeof(str); // len = 10* sizeof(char) = 10; len = sizeof(pvoid);// len = 4 ;指针长度 len = sizeof(cstr);// len = 4 = sizeof(void *) len = strlen(cstr); // len = 7* sizeof(char) = 7; return 0; }