- 大家好,我在工作经常发现小伙伴们遇到一些C 的问题都是对基础知识不熟悉或理解混乱所导致的。正所谓万丈高楼平地起,作为一名合格的程序员来说,没有良好的基本功很难达到一定的高度。而工作中大部分编程问题都是基本功不扎实所导致,所以决定花些时间来整理C 相关的基本知识和基本概念供大家参考理解,每一个知识点都结合相关的代码进行验证。本文基本上涵盖了C 最常用的知识点,希望对小伙伴们有所帮助。
1. C 是一种面向对象的程序设计语言
- C 支持数据封装,支持数据封装就是支持数据抽象。在C 中,类是支持数据封装的工具,对象则是数据封装的实现。面向过程的程序设计方法与面向对象的程序设计方法在对待数据和函数关系上是不同的。在面向对象的程序设计中,将数据和对该数据进行合法操作的函数封装在一起作为一个类的定义,数据将被隐藏在封装体中,该封装体通过操作接口与外界交换信息。对象被说明具有一个给定类的变量,
类
类似于C语言中的结构,在C语言中可以定义结构,但这种结构中包含数据,而不包含函数。C 中的类是数据和函数的封装体。在C 中,结构可作为一种特殊的类,它虽然可以包含函数,但是它没有私有或受保护的成员。 - C 类中包含
私有
、公有
和受保护成员
,C 类中可定义三种不同访控制权限的成员。一种是私有(Private)成员,只有在类中说明的函数才能访问该类的私有成员,而在该类外的函数不可以访问私有成员;另一种是公有(Public)成员,类外面也可访问公有成员,成为该类的接口;还有一种是保护 (Protected)成员,这种成员只有该类的派生类可以访问,其余的在这个类外不能访问。
2. 命名空间
- c 所有标志符都在std命名空间作用域中才可见
using std::cout;
using std::endl; 或 using namepace std;
using std::cin;
- 命名空间中可以嵌套定义命名空间
namespace name_3
{
namespace
{
int k = 200;
};
};
- 命名空间取别名
namespace name = name_3;
3. C 中的struct结构体
- 对比C语言中结构体,C 中结构体不仅可以有变量还可以有函数。
例程中声明一个命名空间Test,Test中声明一个结构体Account,而Account中定义变量和声明函数。
代码语言:javascript复制namespace Test
{
struct Account
{
char name[30];
double balance;
void init(char *myname,double mybalance)
{
strcpy(name,myname);
balance = mybalance;
}
void deposit(double amount);
void withdraw(double amount);
inline double getBalance()
{
return balance;
}
};
}
4. Const属性修改
- 在c中const的意思是“一个不能被改变的普通变量“,在c中它总是占用存储空间而且它的名字是全局符。
- c 编译器通常并不为const分配空间,它把这个定义保存在符号表中。当const常量被使用时,编译的时候就进行
常量折叠
。 c 中 编译器不会为一般的const常量分配内存空间, 而是将它们存放符号表中。如果取了这个常量的地址,那么编译器将为此常量分配一个内存空间,生成一个常量副本, 所有通过地址对常量的操作都是针对副本。 常量折叠,又叫常量替换,c 编译器会在编译时,将const常量的字面值保存在符号表中,在编译时使用这个字面常量进行替换。
代码语言:javascript复制const_cast用法:const_cast<type_id> (expression) 该运算符用来修改类型的const或volatile属性。除了const或volatile修饰之外,要求type_id和expression的类型是一样的。 常量指针被转化成非常量的指针,并且仍然指向原来的对象; 常量引用被转换成非常量的引用,并且仍然指向原来的对象;
#include <iostream>
using namespace std;
int main(int argc, const char** argv)
{
const int a = 10;
const int *p = &a;
// (*p) = 11; // error 不可修改
std::cout << "a:" << a << "*p:" << *p << std::endl; // a:10 *p:10
const_cast<int&>(*p) = 11; // 强制修改
std::cout << "a:" << a << "*p:" << *p << std::endl; // a:10 *p:11
return 0;
}
5. 引用和指针
- 引用是一个别名,可以把它看作变量本身,但是指针本身也是一个变量
- 引用在定义的时候必须初始化,必须绑定一个对象,如果一个对象本身不存在则取别名也没有意义。所以对指针进行解引用(*)的时候要对指针进行非空检测,但是引用由于定义的时候肯定初始化了,则一定不为空。
- 非const引用不能绑定到const对象,但是const引用可以绑定到非const对象(对象本身可以修改自己,但是不能通过引用修改对象)
- 引用比指针安全,引用只能绑定到一个对象,指针可以指向多个地方,可能会造成内存溢出或悬挂指针等不安全的因素
- 形参传引用效率高,但引用为形参最好是一个const引用,防止实参本身被修改。
#include <iostream>
using namespace std;
int main(void)
{
int a = 10;
// int &ref; // error! 必须初始化
// int &ref = 10; // error!
// const int &ref = 10; // ok
const int &ref_a = a; // ok
// ref_a = 11;
a = 12;
cout <<"ref_a: "<<a<< endl;
/* const int b = 20;
int &ref_b = b; // error! 非const引用不能绑定到const变量
const int &res_b = b; // ok
int c = 11;
const int &ref_c = c; // ok
//ref_c ; // error!
*/
return 0;
}
6. 内联函数
- 内联函数:用inline关键字修饰的函数,不加inline默认叫外联。
- 使用了inline关键字,编译器并不一定把此函数当作内联函数处理 内联函数代码一般1-5行,并且函数体中不能出现循环或递归。
- 内联函数的声明和定义是在一起的
- 在类中声明和定义在一起的成员函数都默认为内联函数
- 内联函数和宏定义 宏定义:在预处理阶段替换,但是容易产生二义性,不能作为类的成员函数访问私有成员。 内联函数:在编译阶段函数代码替换函数名,在调用运行的时候就没有函数的压栈和出栈的操作,提高运行效率,是空间换时间。 如果要用某函数的指针调用它,则该函数不能是内联函数,如果动态链接库中有内联函数,那么该链接库升级的时候需要重新编译。内联函数和宏定义都不支持调试。
#include <iostream>
using namespace std;
#define max(a,b) (a>b?a:b)
int max1(int a,int b)
{
return a>b?a:b;
}
inline int max2(int a,int b)
{
return a>b?a:b;
}
int main(void)
{
cout <<"#define: "<<max(11,12)<<endl; //预处理阶段替换
cout <<"max1: "<<max1(12,11)<<endl; //函数的压栈出栈
cout <<"max2: "<<max2(12,11)<<endl; //运行时内联的展开
return 0;
}
7. 重载
- 什么是函数重载: 1.函数名相同 2.参数列表必须不同(参数类型,参数个数,参数的顺序不同) 3.跟函数返回值没有关系
#include <iostream>
using namespace std;
/*
*默认参数:如果某个参数被默认初始化了,其右边不能出现没有被默认初始化的参数
* Error:
* int average(double a=0.5, double b =1.1, double c)
* {
* return (a b c)/2;
* }
*/
int average(double a=0.5, double b =1.1, double c=2.1)
{
return (a b c)/2;
}
int average(int a, int b)
{
cout <<"average(int, int)" << endl;
return (a b)/2;
}
int average(double a, double b)
{
cout <<"average(double, double)" << endl;
return (a b)/2;
}
int average(double a, double b, double c)
{
cout <<"average(double,double,double)" << endl;
return (a b c)/2;
}
int main(void)
{
int a = 10, b = 11;
average(a, b);
cout <<"--------------" << endl;
double c = 10.1, d = 10.2, e = 10.3;
average(c, d);
cout <<"--------------" << endl;
average(c, d, e);
return 0;
}
8. const成员函数和const对象
- const成员函数(常成员函数) 不修改成员变量的函数我们一般定义为const属性的成员函数,常成员函数不能修改成员变量的值。
- const对象 (常对象) const属性的对象(如:const Person p),常对象所有的成员变量都是const属性,不能用常对象调用非const的成员函数(常对象只能调用常成员函数)
#include <iostream>
using namespace std;
class Person{
public:
Person(){}
~Person(){}
void set(int var)
{
this->m_var = var;
}
int get() const //常成员函数
{
// m_var2 = 12; //error! 不修改成员变量
return m_var;
}
private:
int m_var;
int m_var2;
};
int main(void)
{
const Person p; // 常对象
//p.set(11); // error! const对象不能修改非const成员函数
const_cast<Person &>(p).set(11); //ok 只对当前生效
cout <<"const_cast: "<< p.get() <<endl;
//p.set(12); //error! 同样是不对的
return 0;
}
9. static成员
- static:静态成员:静态成员变量 和 静态成员函数
- 静态成员属于整个类不属于某一个对象,在静态存储区分配内存空间,被所有实例对象共享,如果是公共的(public)则可以在外部加类作用域直接访问。
- 静态成员变量只能被初始化一次,不要在头文件中定义,为了避免重复定义。不要在构造函数中定义(构造函数可能被调用多次)另外是因为类的声明不分配内存空间.静态成员变量的初始化方式:int Person::m_var = 10;静态成员变量只在静态存储区保留一份拷贝,静态成员变量可以声明为本来的类类型
- 静态成员函数没有this指针,不能访问普通成员变量。非静态成员函数即可以访问普通成员变量也可以访问静态成员变量
#include <iostream>
using namespace std;
class Person{
public:
Person(){
cout <<"constructor" << endl;
m_counter ;
}
~Person(){
cout <<"distructor" << endl;
m_counter--;
}
static void out_counter(){
//cout <<m_var<< endl; // error 不能访问普通成员变量
cout <<"people counter: "<<m_counter << endl; //ok
}
// private:
int m_var;
static int m_counter;
};
int Person::m_counter = 0; // 静态成员变量初始化
int main(void)
{
Person p1; //constructor
Person p2; //constructor
Person p3; //constructor
// p1.out_counter();
// p3.out_counter();
Person *p4 = new Person; //constructor
cout <<"people counter: "<<Person::m_counter << endl; // people counter:4
delete p4; //distructor
cout <<"people counter: "<<Person::m_counter << endl; // people counter:3
return 0;
}
10. 面向对象
- OOP(Object Oriented Programming) 特征:封装、继承、多态、抽象 private和public针对类本身和调用者 struct的缺省作用域是public class的缺省作用域是private
person.h
namespace mystd
{
class Person
{
private:
char name[30];
int age;
bool gender;
public:
Person(char *myname, int age, bool gender);
~Person();
void show(); // 显示基本属性信息
int afterYear(int n); // n年后多少岁
inline int getAge()
{
return age;
}
};
};
person.cpp
#include "person.h"
#include <iostream>
#include <string.h>
using namespace std;
namespace mystd
{
Person::Person(char *myname, int myage, bool mygender)
{
strcpy(name, myname);
age = myage;
gender = mygender;
}
Person::~Person(){}
void Person::show() // 显示基本属性信息
{
cout <<"name: "<<name<<" age: "<<age<<" gender: "<<gender << endl;
}
int Person::afterYear(int n)// n年后多少岁
{
return age = n;
}
};
main.cpp
#include "person.h"
#include <iostream>
using namespace std;
using namespace mystd;
int main(void)
{
Person p1("Lin", 24, true);
p1.show();
cout <<"after ten years: " << p1.afterYear(10)<<endl;
cout <<p1.age<< endl;
return 0;
}
11. 构造函数和析构函数
- 构造函数 1.特殊的成员函数,名字跟类名相同,没有返回类型,必须为public,构造函数的作用是初始化对象的属性。 2.如果没有显示的定义任何构造函数,则编译器会自动合成一个构造函数,如果定义类构造函数,则编译器不再自己合成构造函数。 3.构造函数可以重载 4.构造函数初始化可以使用初始化参数列表,成员变量的初始化顺序跟初始化列表的顺序无关,是按照成员变量的声明顺序。 5.什么时候一定要用初始化列表 有const成员变量和引用成员变量的时候一定要用初始化列表初始化这两种变量
- 析构函数 没有返回值,析构函数名称类型前加~ 1.如果是栈对象 作用域结束的时候自动调用析构函数 2.如果是堆对象(new出来的对象),则要程序员delete的时候才调用析构函数 3.如果是静态对象(在静态存储区/静态数据区中的对象)则在程序装入内存的时候就构造,进程结束的时候析析构。
- 浅拷贝、深拷贝 当由一个已有的对象来构造一个新的对象时,需要调用拷贝构造函数 浅拷贝(位拷贝):对象成员变量没有使用动态分配内存空间的时候,对象和对象之间进行拷贝构造的时候使用浅拷贝就行 深拷贝:如果对象内存使用了动态分配内存空间,则要使用深拷贝(显示定义进行内存拷贝的拷贝构造函数) 如果不进行深拷贝,则会造成悬挂指针,多次析构同一块堆内存
- 什么时候会调用拷贝构造函数: 1.把一个对象作为参数进行值传递的时候(所以说我们一般把对象作为参数的时候传对象的const引用) 2.把一个对象作为返回值的时候 如果存在拷贝构造函数的需求,没有显示的定义拷贝构造函数,则编译器会自动生成一个拷贝构造函数。如果显示定义了拷贝构造函数之后,则会调用该显示定义的拷贝构造函数,但是编译器还是会自动生成一个拷贝构造函数。新创建的对象去调用拷贝构造函数。
constructor.cpp
#include <iostream>
using namespace std;
class Person
{
public:
Person(){cout <<"---constructor---"<< endl;}
// Person(int a){m_var1 = a;}
Person(int a):m_var1(a),m_var2(m_var2),m_var3(a)
{
// m_var3 = 3; //error
cout <<"Person(int, int) constructor" << endl;
}
~Person(){cout <<"---distructor---" << endl;}
void show(){cout <<"m_var1: "<<m_var1 <<" m_var2: "<<m_var2<< endl;}
private:
int m_var1;
int m_var2;
const int m_var3;
// int &ref;
};
Person p1(10);
int main()
{
// Person p(10, 30);
// p.show();
cout <<"----------------" << endl;
Person *p = new Person(10);
p->show();
delete p;
p1.show();
cout <<"----------------" << endl;
return 0;
}
copy_constructor.cpp
#include <iostream>
#include <string>
using namespace std;
class Computer{
public:
Computer(){}
Computer(Computer&){
cout <<"----computer constructor----" << endl;
}
~Computer(){}
private:
string name;
};
class Student{
public:
Student(){}
Student(string name, int age){
cout <<"----constructor----" << endl;
Computer com;
m_comp = com;
this->name = name;
this->age = age;
}
//拷贝构造函数
Student(const Student &s){
cout <<"----copy constructor----" << endl;
this->name = s.name;
this->age = s.age;
}
~Student(){}
//把一个对象作为返回值的时候会调用该对象的一个拷贝构造函数
Computer getComp(){
cout <<"getComp()"<< endl;
return m_comp;
}
Student getst(){
return *this; //会调用该对象的拷贝构造函数
}
void disp(){
cout <<"name: "<<name<<" age: "<<age << endl;
}
private:
string name;
int age;
Computer m_comp;
};
//把对象作为参数进行值传递的时候拷贝构造函数会被调用
void disp_out(Student s)
{
s.disp();
}
int main(void)
{
Student s1("Lin", 20);
s1.disp();
Student s2 = s1; // 自动调用拷贝构造函数
s2.disp();
Student s3(s2); // 显式使用拷贝构造函数
s3.disp();
cout <<"******************" << endl;
disp_out(s3); // 把一个对象作为值传递的时候会调用拷贝构造函数
cout <<"------------------" << endl;
s1.getComp();
s3.getst();
return 0;
}
deep_copy.cpp
#include <iostream>
#include <string.h>
using namespace std;
class Mystring{
public:
Mystring(char *str, int counter){
m_str = new char[counter 1];
memcpy(m_str, str, counter);
m_counter = counter;
m_str[counter 1] = '