c++语法


实用链接
C++语言中文网
实例用法查看网址
c++在线编译器
google代码风格规范

0.0 编译运行

  • .c是C语言代码,.cpp是C++代码
  1. g++是执行c++文件的命令;gcc是执行c文件的命令
  2. bin文件运行
chmod +x filename.bin
./filename.bin ....

单个程序编辑调试

  • step 1: 写一个xx.cc或者xx.cpp的文件

  • step 2:编译文件g++ xx.cpp(编译单个文件),g++ xx1.cpp xx2.cpp(编译多个文件)

  • step 3:生成对应的可执行文件g++ xx1.cpp -o demodemo是可执行文件的名字
    如果c++文件有错误是不会生成对应的可执行文件的

  • step 3: ./demo运行文件(这样操作,才能看到里边的printf等信息,否则是没有的)

  • linux环境下,‘ctrl+d’是输入结束的字符

库文件编译调试

  • step 1: 一般新建一个build文件夹(其他也可以),用于存放编译过程中产生的文件
  • step 2 : cmake dir, dir是CMakeLists.txt文件所在的路径,cmake是跨平台编译的工具;
  • step 3: 然后会在当前路径(build)下生成一个Makefile的文件,执行make即生成对应的可执行文件
  • step 4: 对于具体的可执行文件的使用方式
>>>./demo_vc 
>>>输出    ./demo_vc vc.dat input.pcm lpcnet_feats.bin

1. 变量

1.1 变量的声明和定义

定义包含声明,声明不包含定义
1.变量的定义:变量的定义用于为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且仅有一个定义。

2.变量的声明:用于向程序表明变量的类型和名字。程序中变量可以声明多次,但只能定义一次。

3.两者联系与区别

(1)定义也是声明,因为当定义变量时我们也向程序表明了它的类型和名字;

(2)但声明不是定义,可以通过使用extern关键字声明变量而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern;extern位于函数外部的时候,才可以声明+定义,在函数内部不允许

4. 为什么要区分声明和定义
C++程序通常由许多文件组成。为了让多个文件访问相同的变量,C++区分了声明和定义
需要被别的文件用的变量,必须在本文件中定义好,比如在文件1中定义extern int i=0;然后才可以再别的文件中使用,使用方式是:在类体的外部使用extern int i;别的文件只能声明,而不能再定义


#include <iostream>
using namespace std;
 
// 变量声明   变量声明可以注释
extern int a, b;
extern int c;
extern float f;
  
int main ()
{
  // 变量定义   变量定义不能注释
  int a, b;
  int c;
  float f;
 
  // 实际初始化
  a = 10;
  b = 20;
  c = a + b;
 
  cout << c << endl ;
 
  f = 70.0/3.0;
  cout << f << endl ;
 
  return 0;
}

1.2 变量的作用域

全局变量: 在所有函数外部声明的变量
局部变量: 在函数或一个代码块内部声明的变量,局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值。
形式参数: 在函数参数的定义中声明的变量

变量的作用域有限,同时出现的时候,作用域小的屏蔽作用域大的,但是作用域小的结束作用区间后就失效了

#include <iostream>
using namespace std;
int b=5   ## 全局变量
int main()
{
    int b = 2;
    {
        int b = 1;
        cout << "b = " << b << endl;   // 作用域结束即失效
    }
    cout << "b = " << b << endl;
    cout << ::b << endl;   // 5 ## 通过域名在函数中引用到全局变量,不加域名解析则引用局部变量   
}

1.3 namespace命名空间

命名空间namespce定义:程序员各自在写代码时,可能会声明相同名字的函数或者变量,这样代码整体编译的时候就会报错—通过命名空间解决这一问题。
格式:

namespace name----命名空间的名字{
    //variables, functions, classes---变量/函数/define等
}

使用方法:

第一种:直接调用
Li :: fp = fopen("one.txt", "r");  //使用小李定义的变量 fp
Han :: fp = fopen("two.txt", "rb+");  //使用小韩定义的变量 fp

第二种: using调用
using Li :: fp;  --------“::”域操作解析符
fp = fopen("one.txt", "r");  //使用小李定义的变量 fp
Han :: fp = fopen("two.txt", "rb+");  //使用小韩定义的变量 fp

第三种:namespacea整体调用
using namespace Li;
fp = fopen("one.txt", "r");  //使用小李定义的变量 fp
Han :: fp = fopen("two.txt", "rb+");  //使用小韩定义的变量 fp

标准空间std

定义:将类、函数、宏等都统一纳入一个命名空间,这个命名空间的名字就是std。std 是 standard 的缩写,意思是“标准命名空间”。
使用:如果是在函数体中声明using namespace std;,那么作用域只在这个函数内部有效;如果想要全局有效,需要开始声明(但是可能会有冲突,不建议)。

2. 关键字

2.1 extern

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。
extern 用法说明

3. 常量

C++中设置一直不变的量叫常量,一般用大写字符表示常量
常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值

1. define 定义

#define identifier value define 预处理器
#define WIDTH  5
#define NEWLINE '\n'

2. const 定义

const type variable = value; 声明指定类型的常量
const int  WIDTH  = 5;
const char NEWLINE = '\n';

3. 区别

void f1()
{
  #define N 12
  const int n = 12;

  #undef N //取消宏定义后,即使在f1函数中,N也无效了
  #define N 21//取消后可以重新定义
}
void f2 ()
{
    cout<<N <<endl; //正确,N已经定义过,不受定义域限制
    cout<<n <<endl; //错误,n定义域只在f1函数中
}

4. 函数

  1. 函数调用的顺序:C/C++ 编译 cpp 文件是从上往下编译,所以 main 函数里面调用其他函数时,如果其他函数在 main 函数的下面,则要在 main 函数上面先声明这个函数。
    或者把 main 函数放在最下面,这个不仅限于 main 函数,其他函数的调用都是如此。被调用的函数要在调用的函数之前声明。

函数定义

return_type function_name( parameter list )
{
   body of the function
}
  • 返回类型: 一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
  • 函数名称: 这是函数的实际名称。函数名和参数列表一起构成了函数签名。
  • 参数: 参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
  • 函数主体: 函数主体包含一组定义函数执行任务的语句。
    其中,parameter list中参数的名称可以省去不写,但是参数的类型必须声明

参数的传递方式

在这里插入图片描述

默认参数

定义:所谓默认参数,指的是当函数调用中省略了实参时自动使用的一个值,这个值就是给形参指定的默认值。

# 正确使用
void func(int a, int b, int c=20){ }

# 错误使用:默认参数只能放在形参列表的最后,实参和形参的传值是从左到右依次匹配的,而且一旦为某个形参指定了默认值,那么它后面的所有形参都必须有默认值。
void func(int a, int b=10, int c, int d=20){ }

函数重载

  • 定义: 在实际开发中需要实现几个功能类似的函数,只是有些细节不同。比如交换两个数据(int/float/char等),不必要重新命名,可以让多个函数拥有相同的名字,只要它们的参数列表不同就可以。
  • 函数的重载的规则:
    (1)函数名称必须相同。
    (2)参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
    (3)函数的返回类型可以相同也可以不相同。
  • 函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。

引用调用

向函数传递参数的引用调用方法,把引用的地址复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。

#include <iostream>
using namespace std;
 
// 函数声明
void swap(int &x, int &y);
 
int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;
 
   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;
 
   /* 调用函数来交换值 */
   swap(a, b);
 
   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;
 
   return 0;
}

// 函数定义,实际上无返回值,但传参过来的a,b被修改了
void swap(int &x, int &y)
{
   int temp;
   temp = x; /* 保存地址 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 x 赋值给 y  */
  
   return;
}

指针调用

#include <iostream>
using namespace std;

// 函数声明,传递的指针相当于一个参数
void swap(int *x, int *y);

int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;
 
   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;

   /* 调用函数来交换值
    * &a 表示指向 a 的指针,即变量 a 的地址 
    * &b 表示指向 b 的指针,即变量 b 的地址 
    */
   swap(&a, &b);

   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;
 
   return 0;
}

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

setw() 函数

setw() 函数用于设置字段的宽度,仅对后方最近的起作用

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
    // 开头设置宽度为 4,后面的 runoob 字符长度大于 4,所以不起作用
    cout << setw(4) << "runoob" << endl;
    // 中间位置设置宽度为 4,后面的 runoob 字符长度大于 4,所以不起作用
    cout << "runoob" << setw(4) << "runoob" << endl;
    // 开头设置间距为 14,后面 runoob 字符数为6,前面补充 8 个空格
    cout << setw(14) << "runoob" << endl;
    // 中间位置设置间距为 14 ,后面 runoob 字符数为6,前面补充 8 个空格
    cout << "runoob" << setw(14) << "runoob" << endl;
    return 0;
}

代码输出结果为

runoob
runoobrunoob
        runoob
runoob        runoob

5. 数组/字符串

5.1 数组

定义:

double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
或者 
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

容器vector

  • 初始化 std::vector<double> values;
  • 元素增减
#include <vector>
using std::vector
vector.push_back(),尾部添加
vector.pop_back() 尾部移除
  • vector 输出不能一次性输出,循环打印
vector<int> path;
for (auto i : path)
    std::cout << i << ' ';
  • 容器的长度int length = vector.size()-1

5.2 字符串

定义

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

在这里插入图片描述

char

  • char是单个的字符
char a='a' ----注意使用的是单引号
string m="aaa" ----string使用的是双引号

5.3 string类

  • string变量的定义
string s1;   ******只是定义但没有初始化,默认是“”***********
string s2 = "c plus plus";
string s3 = s2; 
string s4 (5, 's'); ********s4='sssss'********
  • 字符串长度(string 的末尾没有’\0’字符)
string s2 = "sss";
int len = s.length();      *******len=3****
int len = s.size()  两种方法都可以

  • 可以调用C语言的fopen,但是需要将字符串转成c类型string.c_str()
string path = "D:\\demo.txt";
FILE *fp = fopen(path.c_str(), "rt");
  • string的输入输出:输入运算符>>默认会忽略空格,遇到空格就认为输入结束
using std::string;
string s
while (getline(cin, s)) ----一直读取到输入结束
while (cin>>s) -----每个单词读取,空格断开(开头的空格不读取) 

string类的访问和修改

  • 可以用数组的形式访问和修改string
#include <iostream>
#include <string>
using namespace std;
int main(){
    string s = "1234567890";
    for(int i=0,len=s.length(); i<len; i++){
        cout<<s[i]<<" ";
    }
    cout<<endl;   
    s[5] = '5';
    cout<<s<<endl;
    return 0;
}

输出

1 2 3 4 5 6 7 8 9 0
1234557890
  • 拼接:用+来拼接字符串
string s1 = "first ";
string s2 = "second ";
string s5 = s1 + s2;
  • 插入:格式string1.insert(position, string2)
string s1='12345';
string s2='ss';
s1.insert(2,s2);
cout<< s1 <<endl;

输出

12ss345
  • 删除字符串:string1.erase(position, delete_length)
    position表示开始删除的字符位置;delete_length表示需要删除的字符长度,如果不写,会删除到该字符串结束
int main(){
    string s1, s2, s3;
    s1 = s2 = s3 = "1234567890";
    s2.erase(5);
    s3.erase(5, 3);
    cout<< s1 <<endl;
    cout<< s2 <<endl;
    cout<< s3 <<endl;
    return 0;
}

输出

1234567890
12345
1234590
  • 提取字符串:string1.substr(position, length)
    position:开始提取的字符串位置;length:提取的字符串长度
string s1 = "first second third";
string s2;
s2 = s1.substr(6, 6);

输出

second s1不发生改变
  • 查找字符串:string1.find(string2, strart_position)
  • find_first_of() 函数:查找字符串共同所有的字符首次出现的位置

6. 表达式

作用域符号::

A::member就表示类A中的成员member

if

  • 只有else if,没有elif;
  • 注意分号的位置
if case1
	do;
else if case2
	do;
else
	do;

7. 指针

指针是一个变量,其值为另一个变量的地址type *var_name;

#include <iostream>
 
using namespace std;
 
int main ()
{
   int  var = 20;   // 实际变量的声明
   int  *ip;        // 指针变量的声明
 
   ip = &var;       // 在指针变量中存储 var 的地址
 
   cout << "Value of var variable: ";
   cout << var << endl;
 
   // 输出在指针变量中存储的地址
   cout << "Address stored in ip variable: ";
   cout << ip << endl;
 
   // 访问指针中地址的值
   cout << "Value of *ip variable: ";
   cout << *ip << endl;
 
   return 0;
}


Value of var variable: 20
Address stored in ip variable: 0xbfc601ac
Value of *ip variable: 20

指针类型

  • 空指针 *pr=NULL,pr=0
    所有初始化未定义的指针都指向空
  • 指针的算术运算:递增变量指针,以便顺序访问数组中的每一个元素
  • 数组与指针
#include <iostream>
 
using namespace std;
const int MAX = 3;


 例子1:
int main ()
{
   int  var[MAX] = {10, 100, 200};
   int  *ptr;
 
   // 指针中第一个元素的地址
   ptr = var;
   ptr++
}

#include <iostream>
 
using namespace std;
const int MAX = 3;
 
例子2:
int main ()
{
   int  var[MAX] = {10, 100, 200};
 
   for (int i = 0; i < MAX; i++)
   {
      *var = i;    // 这是正确的语法,数组名var本身就是地址
      var++;       // 修改 var 的值是非法的。这是因为 var 是一个指向数组开头的常量,不能作为左值。
   }
   return 0;
}

8、输入输出

fstream

  • std::ifstream ifs;

  • ifs.open(argv[2], std::ios_base::binary); //以二进制形式打开文件argv[2]
    //ifs.seekg()对输入文件定位,它有两个参数:第一个参数是偏移量,第二个参数是基地址。

  • ifs.seekg(0, ifs.end); //基地址为文件结束处,偏移地址为0,于是指针定位在文件结束处
    //ifs.tellg() 不需要带参数,返回当前定位指针的位置,也代表着输入流的大小。

  • size_t fsize = ifs.tellg(); //sp为定位指针,因为它在文件结束处,所以也就是文件的大小

  • ifs.seekg(0, ifs.beg); ///基地址为文件头,偏移量为0,于是定位在文件头
    //ios::beg:表示输入流的开始位置
    //ios::cur:表示输入流的当前位置
    //ios::end:表示输入流的结束位置

9. 类

本章节主要参考c语言中文网,好评!

9.1 类的声明

  • 类的定义,相当于定义一种新的数据格式,等同于int a; float b等声明;
  • 创建对象(等同于常说的变量名)Student liLei;
  • 类中有成员函数,成员变量;其中:成员函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前。
#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      double length;  //成员变量
      void setWidth( double wid ); //成员函数
      double getWidth( void );
 
   private:
      double width;
};
 
// 成员函数定义
double Box::getWidth(void)
{
    return width ;
}
 
void Box::setWidth( double wid )
{
    width = wid;
}
 
// 程序的主函数
int main( )
{
   Box box;
 
   // 不使用成员函数设置长度
   box.length = 10.0; // OK: 因为 length 是公有的
   cout << "Length of box : " << box.length <<endl;
 
   // 不使用成员函数设置宽度
   // box.width = 10.0; // Error: 因为 width 是私有的
   box.setWidth(10.0);  // 使用成员函数设置宽度
   cout << "Width of box : " << box.getWidth() <<endl;
 
   return 0;
}
9.2 成员访问限定符 private/public/protect
  • 如果既不写 private 也不写 public,就默认为 private
  • 类中向外暴露的接口(能通过对象访问的成员)都声明为 public
  • private 关键字的作用在于更好地隐藏类的内部实现,只在类内调用,或者通过类的public 成员函数调用,而不能通过对象直接调用
  • 成员变量大都以m_开头,这是约定成俗的写法,不是语法规定的内容。以m_开头既可以一眼看出这是成员变量,又可以和成员函数中的形参名字区分开。
  • 正确的示例
#include <iostream>
using namespace std;

//类的声明
class Student{
private:  //私有的
    char *m_name;
    int m_age;
    float m_score;

public:  //共有的
    void setname(char *name);
    void setage(int age);
    void setscore(float score);
    void show();
};

//成员函数的定义
void Student::setname(char *name){
    m_name = name;
}
void Student::setage(int age){
    m_age = age;
}
void Student::setscore(float score){
    m_score = score;
}
void Student::show(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}

int main(){
    //在栈上创建对象
    Student stu;
    stu.setname("小明"); //通过成员函数给私有变量m_name赋值
    stu.setage(15);
    stu.setscore(92.5f);
    stu.show();

    //在堆上创建对象
    Student *pstu = new Student;
    pstu -> setname("李华");
    pstu -> setage(16);
    pstu -> setscore(96);
    pstu -> show();

    return 0;
}
  • 错误示例
Student stu;
//m_name、m_age、m_score 是私有成员变量,不能在类外部通过对象访问
stu.m_name = "小明";
stu.m_age = 15;
stu.m_score = 92.5f;
stu.show();
9.3 构造函数
  • 定义:它的名字和类名相同,没有返回值,也不能被用户调用
  • ==构造函数没有返回值!!!==因此(1)不管是声明还是定义,函数名前面都不能出现返回值类型,即使是 void 也不允许;(2)函数体中不能有 return 语句。
  • 如果没有构造函数,需要多次调用getname,setname等才能为类内成员变量赋值;通过构造函数,可以在创建对象的同时为成员变量赋值;
  • 示例(结合上一小节代码阅读)
//定义构造函数
Student::Student(char *name, int age, float score){
    m_name = name;
    m_age = age;
    m_score = score;
}
int main()
{
    //创建对象时向构造函数传参
    Student stu("小明", 15, 92.5f);
 }
9.3.1 构造函数的重载
  • 一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。(一个对象只会调用一个构造函数,且创建对象时提供的实参必须和其中的一个构造函数匹配)
  • 调用没有参数的构造函数也可以省略括号
  • 默认构造函数:在调用时不需要传入实参的构造函数,包括(1)构造函数没有形参;(2)构造函数在定义行参的时候赋了初值;如果类内没有定义构造函数,编译器自动生成一个,称之为默认构造函数。
9.3.2 构造函数的参数初始化列表
  • 定义:
  • 格式:class_name::Constructor_name(形参列表):初始化参数列表 {函数体}
//采用参数初始化表
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
    //TODO:
}
  • 注释:m_name(name), m_age(age), m_score(score)相当于函数体内部的m_name = name; m_age = age; m_score = score;,也是赋值的意思。
  • 注意:
    (1)初始化参数列表要和类中成员变量的顺序保持一致,否则赋值会发生错误;
    (2)初始化 const 成员变量的唯一方法就是使用参数初始化表。
9.4 析构函数

定义:一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行;名字与类名相同,格式~class_name()

9.5 对象指针
  • 在栈上创建对象,形式和定义普通变量类似
Student stu;  //定义一个Student类型的对象
Student *pStu = &stu; //pStu 是一个指针,它指向 Student 类型的数据
  • 在堆上创建对象,必须要用一个指针指向它,记得 delete 掉不再使用的对象。
Student *pStu = new Student; 
//使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数。
  • 通过对象名字访问成员使用点号 .,通过对象指针访问成员使用箭头->
9.6 this指针
  • 定义:this是一个关键字,它指向当前对象,通过它可以访问当前对象的所有成员,包括 private、protected、public 属性的。
  • 也是一个 const 指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的。
  • this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的。是成员函数和成员变量关联的桥梁。
  • 只有当对象被创建后 this 才有意义,因此不能在 static 成员函数中使用(后续会讲到 static 成员)。
#include <iostream>
using namespace std;

class Student{
public:
    void setname(char *name);
    void setage(int age);
    void setscore(float score);
    void show();
private:
    char *name;
    int age;
    float score;
};

void Student::setname(char *name){
    this->name = name;
}
void Student::setage(int age){
    this->age = age;
}
void Student::setscore(float score){
    this->score = score;
}
void Student::show(){
    cout<<this->name<<"的年龄是"<<this->age<<",成绩是"<<this->score<<endl;
}

int main(){
    Student *pstu = new Student;
    pstu -> setname("李华");
    pstu -> setage(16);
    pstu -> setscore(96.5);
    pstu -> show();

    return 0;
}

运行结果李华的年龄是16,成绩是96.5

9.7 inline函数
  • 作用:函数之间的跳转是有时间和空间的开销,将那些短小的、频繁调用的函数声明为内联函数,可以消除函数调用的时空开销。
  • 在类体中和类体外定义成员函数是有区别的:在类体中定义的成员函数会自动成为内联函数(不需要加inline),在类体外定义的不会;
  • 如果既希望将函数定义在类体外部,又希望它是内联函数:在类体外定义函数时加 inline 关键字,声明时添加关键字无效
    举个例子
//声明内联函数
void swap1(int *a, int *b);  //也可以添加inline,但编译器会忽略
//定义内联函数
inline void swap1(int *a, int *b){
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}
  • Tips: 只有当函数只有 10 行甚至更少时才将其定义为内联函数(不允许有循环和开关语句),主要是为了程序运行的效率问题
//函数定义,这样say就变成一个内联函数
inline void Student::say(){
    cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
}
9.8 static静态成员变量
  • 定义:静态成员变量是一种特殊的成员变量,它被关键字static修饰,实现多个对象共享数据的目标
  • static 成员变量不占用对象的内存,而是在所有对象之外(全局数据区)开辟内存,即使不创建对象也可以访问。如果没有初始化赋值,默认是零;而对于堆区/栈区的数据,如果没有初始赋值,会默认是垃圾数据;
  • 注意: static 成员变量必须在类声明的外部初始化,格式type class::name = value;,比如int Student::m_total = 0;,静态成员变量在初始化时不能再加 static,但必须要有数据类型。被 private、protected、public 修饰的静态成员变量都可以用这种方式初始化。
  • static变量可以通过对象名访问,也可以通过类名访问,但要遵循private/public的访问原则
//通过类类访问 static 成员变量
Student::m_total = 10;
//通过对象来访问 static 成员变量
Student stu("小明", 15, 92.5f);
stu.m_total = 20;
//通过对象指针来访问 static 成员变量
Student *pstu = new Student("李华", 16, 96);
pstu -> m_total = 20;
9.9 静态成员函数
  • 静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
9.10 const关键字
const常量/常函数/const对象
  • 定义:const修饰的成员函数or成员变量不能被修改,
  • const 成员变量:只能通过参数初始化列表去初始化
  • const 成员函数:可以使用类中的所有成员变量,但是不能修改它们的值,需要在声明和定义的时候在函数头部的结尾加上 const 关键字
//类内声明常成员函数
 char *getname() const;
//定义常成员函数
char * Student::getname() const{
    return m_name;
}
  • const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员了。
const对象
const  class  object(params);
const指针
const class *p = new class(params);
9.11 拷贝函数
#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      int getLength( void );
      Line( int len );             // 简单的构造函数
      Line( const Line &obj);      // 拷贝构造函数
      ~Line();                     // 析构函数(默认构造函数和析构函数即使不写,在编译的时候也会自动生成)
 
   private:
      int *ptr;
};
 
// 成员函数定义,包括构造函数
Line::Line(int len)
{
    cout << "调用构造函数" << endl;
    // 为指针分配内存,为类中私有变量赋值,根据函数自行定义,不是必须的
    ptr = new int;
    *ptr = len;
}
 
Line::Line(const Line &obj)
{
    cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 拷贝值
}
 
Line::~Line(void)
{
    cout << "释放内存" << endl;
    delete ptr;
}
int Line::getLength( void )
{
    return *ptr;
}
 
void display(Line obj)
{
   cout << "line 大小 : " << obj.getLength() <<endl;
   // 在跳出函数之前,执行class Line的析构函数
}
 
// 程序的主函数
int main( )
{
   Line line(10);   
   // 调用构造函数 Line( int len );      执行  cout << "调用构造函数" << endl;
 
   display(line);   // Line obj=line, obj的对象是类 Line, 复制给line,因此是复制构造函数
 
   return 0;
}
9.12 friend友元函数和友元类
  • 定义:可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。
  • 注意:友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象
  • (1)当作全局函数用法示例,注意传的分别是&stu和pstu
#include <iostream>
using namespace std;

class Student{
public:
    Student(char *name, int age, float score);
public:
    friend void show(Student *pstu);  //将show()声明为友元函数
private:
    char *m_name;
    int m_age;
    float m_score;
};

Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }

//非成员函数,不能直接访问类的成员,而是通过对象
void show(Student *pstu){
    cout<<pstu->m_name<<"的年龄是 "<<pstu->m_age<<",成绩是 "<<pstu->m_score<<endl;
}

int main(){
    Student stu("小明", 15, 90.6);
    show(&stu);  //调用友元函数,通过对象,本身是传地址
    Student *pstu = new Student("李磊", 16, 80.5);
    show(pstu);  //调用友元函数

    return 0;
}

(2)将其他类的成员函数声明为友元函数

  • 定义:一个函数可以被多个类声明为友元函数,这样就可以访问多个类中的 private 成员
#include <iostream>
using namespace std;
****要在Student中使用Address类,所以提前声明*****
class Address;  //提前声明Address类

//声明Student类
class Student{
public:
    Student(char *name, int age, float score);
public:
	****Student 类的成员函数 show() 声明为 Address 类的友元函数*******
	*****show() 就可以访问 Address 类的 private 成员变量了****
    void show(Address *addr);
private:
    char *m_name;
    int m_age;
    float m_score;
};

//声明Address类
class Address{
private:
    char *m_province;  //省份
    char *m_city;  //城市
    char *m_district;  //区(市区)
public:
    Address(char *province, char *city, char *district);
    //将Student类中的成员函数show()声明为友元函数
    friend void Student::show(Address *addr);
};

//实现Student类
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(Address *addr){
    cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
    cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"区"<<endl;
}

//实现Address类
Address::Address(char *province, char *city, char *district){
    m_province = province;
    m_city = city;
    m_district = district;
}

int main(){
    Student stu("小明", 16, 95.5f);
    Address addr("陕西", "西安", "雁塔");
    stu.show(&addr);
   
    Student *pstu = new Student("李磊", 16, 80.5);
    Address *paddr = new Address("河北", "衡水", "桃城");
    pstu -> show(paddr);

    return 0;
}

注意:关于友元,有两点需要说明

  • 友元的关系是单向的而不是双向的。如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员。
  • 友元的关系不能传递。如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类。

模板

函数模板
  • 定义:函数模板实际上是一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型
using namespace std;
********template<typename T>被称为模板头*************
*********语法规定:其中typename 也可以替换成class,是一样的(是早期不严谨的用法,现基本用typename***********
template <typename 类型参数1 , typename 类型参数2 , ...> 返回值类型  函数名(形参列表){
    //在函数体中可以使用类型参数
}



***********函数模板的定义**************
template<typename T> void Swap(T *a, T *b){
    T temp = *a;
    *a = *b;
    *b = temp;
}

************也有先声明,再定义的**************
//声明函数模板
template<typename T> T max(T a, T b, T c);
类模板
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值